PropertyParser refactor
This commit is contained in:
parent
fc7ff70090
commit
357c8a18bd
20 changed files with 987 additions and 1094 deletions
|
@ -7,7 +7,7 @@ use blockquote_style::AuthorPos::After;
|
|||
use blockquote_style::AuthorPos::Before;
|
||||
use blockquote_style::BlockquoteStyle;
|
||||
use lsp::semantic::Semantics;
|
||||
use regex::Match;
|
||||
use parser::util::escape_source;
|
||||
use regex::Regex;
|
||||
use runtime_format::FormatArgs;
|
||||
use runtime_format::FormatKey;
|
||||
|
@ -24,6 +24,8 @@ use crate::elements::paragraph::Paragraph;
|
|||
use crate::elements::text::Text;
|
||||
use crate::parser::parser::ParseMode;
|
||||
use crate::parser::parser::ParserState;
|
||||
use crate::parser::property::Property;
|
||||
use crate::parser::property::PropertyParser;
|
||||
use crate::parser::reports::macros::*;
|
||||
use crate::parser::reports::*;
|
||||
use crate::parser::rule::Rule;
|
||||
|
@ -31,9 +33,6 @@ use crate::parser::source::Cursor;
|
|||
use crate::parser::source::Token;
|
||||
use crate::parser::source::VirtualSource;
|
||||
use crate::parser::style::StyleHolder;
|
||||
use crate::parser::util::escape_text;
|
||||
use crate::parser::util::Property;
|
||||
use crate::parser::util::PropertyParser;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Blockquote {
|
||||
|
@ -185,15 +184,15 @@ impl BlockquoteRule {
|
|||
let mut props = HashMap::new();
|
||||
props.insert(
|
||||
"author".to_string(),
|
||||
Property::new(false, "Quote author".to_string(), None),
|
||||
Property::new("Quote author".to_string(), None),
|
||||
);
|
||||
props.insert(
|
||||
"cite".to_string(),
|
||||
Property::new(false, "Quote source".to_string(), None),
|
||||
Property::new("Quote source".to_string(), None),
|
||||
);
|
||||
props.insert(
|
||||
"url".to_string(),
|
||||
Property::new(false, "Quote source url".to_string(), None),
|
||||
Property::new("Quote source url".to_string(), None),
|
||||
);
|
||||
|
||||
Self {
|
||||
|
@ -202,29 +201,6 @@ impl BlockquoteRule {
|
|||
properties: PropertyParser { properties: props },
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_properties(
|
||||
&self,
|
||||
m: Match,
|
||||
) -> Result<(Option<String>, Option<String>, Option<String>), String> {
|
||||
let processed = escape_text('\\', "]", m.as_str());
|
||||
let pm = self.properties.parse(processed.as_str())?;
|
||||
|
||||
let author = pm
|
||||
.get("author", |_, s| -> Result<String, ()> { Ok(s.to_string()) })
|
||||
.map(|(_, s)| s)
|
||||
.ok();
|
||||
let cite = pm
|
||||
.get("cite", |_, s| -> Result<String, ()> { Ok(s.to_string()) })
|
||||
.map(|(_, s)| s)
|
||||
.ok();
|
||||
let url = pm
|
||||
.get("url", |_, s| -> Result<String, ()> { Ok(s.to_string()) })
|
||||
.map(|(_, s)| s)
|
||||
.ok();
|
||||
|
||||
Ok((author, cite, url))
|
||||
}
|
||||
}
|
||||
|
||||
impl Rule for BlockquoteRule {
|
||||
|
@ -265,23 +241,35 @@ impl Rule for BlockquoteRule {
|
|||
end_cursor = end_cursor.at(captures.get(0).unwrap().end());
|
||||
|
||||
// Properties
|
||||
let mut author = None;
|
||||
let mut cite = None;
|
||||
let mut url = None;
|
||||
if let Some(properties) = captures.get(1) {
|
||||
match self.parse_properties(properties) {
|
||||
Err(err) => {
|
||||
report_err!(
|
||||
&mut reports,
|
||||
cursor.source.clone(),
|
||||
"Invalid Blockquote Properties".into(),
|
||||
span(properties.range(), err)
|
||||
);
|
||||
return (end_cursor, reports);
|
||||
}
|
||||
Ok(props) => (author, cite, url) = props,
|
||||
}
|
||||
}
|
||||
let prop_source = escape_source(
|
||||
end_cursor.source.clone(),
|
||||
captures.get(1).map_or(0..0, |m| m.range()),
|
||||
"Blockquote Properties".into(),
|
||||
'\\',
|
||||
"]",
|
||||
);
|
||||
let properties =
|
||||
match self
|
||||
.properties
|
||||
.parse("Blockquote", &mut reports, state, prop_source.into())
|
||||
{
|
||||
Some(props) => props,
|
||||
None => return (end_cursor, reports),
|
||||
};
|
||||
let (author, cite, url) = match (
|
||||
properties.get_opt(&mut reports, "author", |_, value| {
|
||||
Result::<_, String>::Ok(value.value.clone())
|
||||
}),
|
||||
properties.get_opt(&mut reports, "cite", |_, value| {
|
||||
Result::<_, String>::Ok(value.value.clone())
|
||||
}),
|
||||
properties.get_opt(&mut reports, "url", |_, value| {
|
||||
Result::<_, String>::Ok(value.value.clone())
|
||||
}),
|
||||
) {
|
||||
(Some(author), Some(cite), Some(url)) => (author, cite, url),
|
||||
_ => return (end_cursor, reports),
|
||||
};
|
||||
|
||||
if let Some((sems, tokens)) =
|
||||
Semantics::from_source(cursor.source.clone(), &state.shared.lsp)
|
||||
|
@ -295,7 +283,6 @@ impl Rule for BlockquoteRule {
|
|||
sems.add(start..start + 1, tokens.blockquote_marker);
|
||||
if let Some(props) = captures.get(1).map(|m| m.range()) {
|
||||
sems.add(props.start - 1..props.start, tokens.blockquote_props_sep);
|
||||
sems.add(props.clone(), tokens.blockquote_props);
|
||||
sems.add(props.end..props.end + 1, tokens.blockquote_props_sep);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ use crypto::digest::Digest;
|
|||
use crypto::sha2::Sha512;
|
||||
use mlua::Function;
|
||||
use mlua::Lua;
|
||||
use parser::util::escape_source;
|
||||
use regex::Captures;
|
||||
use regex::Regex;
|
||||
use syntect::easy::HighlightLines;
|
||||
|
@ -23,13 +24,12 @@ use crate::lsp::semantic::Semantics;
|
|||
use crate::lua::kernel::CTX;
|
||||
use crate::parser::parser::ParseMode;
|
||||
use crate::parser::parser::ParserState;
|
||||
use crate::parser::property::Property;
|
||||
use crate::parser::property::PropertyParser;
|
||||
use crate::parser::reports::macros::*;
|
||||
use crate::parser::reports::*;
|
||||
use crate::parser::rule::RegexRule;
|
||||
use crate::parser::source::Token;
|
||||
use crate::parser::util::Property;
|
||||
use crate::parser::util::PropertyMapError;
|
||||
use crate::parser::util::PropertyParser;
|
||||
use crate::parser::util::{self};
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
|
@ -291,11 +291,7 @@ impl CodeRule {
|
|||
let mut props = HashMap::new();
|
||||
props.insert(
|
||||
"line_offset".to_string(),
|
||||
Property::new(
|
||||
true,
|
||||
"Line number offset".to_string(),
|
||||
Some("1".to_string()),
|
||||
),
|
||||
Property::new("Line number offset".to_string(), Some("1".to_string())),
|
||||
);
|
||||
Self {
|
||||
re: [
|
||||
|
@ -332,39 +328,22 @@ impl RegexRule for CodeRule {
|
|||
) -> Vec<Report> {
|
||||
let mut reports = vec![];
|
||||
|
||||
let properties = match matches.get(1) {
|
||||
None => match self.properties.default() {
|
||||
Ok(properties) => properties,
|
||||
Err(e) => {
|
||||
report_err!(
|
||||
&mut reports,
|
||||
token.source(),
|
||||
"Invalid Code Properties".into(),
|
||||
span(
|
||||
token.range.clone(),
|
||||
format!("Code is missing properties: {e}")
|
||||
)
|
||||
);
|
||||
return reports;
|
||||
}
|
||||
},
|
||||
Some(props) => {
|
||||
let processed =
|
||||
util::escape_text('\\', "]", props.as_str().trim_start().trim_end());
|
||||
match self.properties.parse(processed.as_str()) {
|
||||
Err(e) => {
|
||||
report_err!(
|
||||
&mut reports,
|
||||
token.source(),
|
||||
"Invalid Code Properties".into(),
|
||||
span(props.range(), e)
|
||||
);
|
||||
return reports;
|
||||
}
|
||||
Ok(properties) => properties,
|
||||
}
|
||||
}
|
||||
};
|
||||
// Properties
|
||||
let prop_source = escape_source(
|
||||
token.source(),
|
||||
matches.get(1).map_or(0..0, |m| m.range()),
|
||||
"Code Properties".into(),
|
||||
'\\',
|
||||
"]",
|
||||
);
|
||||
let properties =
|
||||
match self
|
||||
.properties
|
||||
.parse("Code", &mut reports, state, prop_source.into())
|
||||
{
|
||||
Some(props) => props,
|
||||
None => return reports,
|
||||
};
|
||||
|
||||
let code_lang = match matches.get(2) {
|
||||
None => "Plain Text".to_string(),
|
||||
|
@ -429,43 +408,11 @@ impl RegexRule for CodeRule {
|
|||
let code_name = name.as_str().trim_end().trim_start().to_string();
|
||||
(!code_name.is_empty()).then_some(code_name)
|
||||
});
|
||||
let line_offset = match properties.get("line_offset", |prop, value| {
|
||||
value.parse::<usize>().map_err(|e| (prop, e))
|
||||
let line_offset = match properties.get(&mut reports, "line_offset", |_, value| {
|
||||
value.value.parse::<usize>()
|
||||
}) {
|
||||
Ok((_prop, offset)) => offset,
|
||||
Err(e) => match e {
|
||||
PropertyMapError::ParseError((prop, err)) => {
|
||||
report_err!(
|
||||
&mut reports,
|
||||
token.source(),
|
||||
"Invalid Code Property".into(),
|
||||
span(
|
||||
token.start() + 1..token.end(),
|
||||
format!(
|
||||
"Property `line_offset: {}` cannot be converted: {}",
|
||||
prop.fg(state.parser.colors().info),
|
||||
err.fg(state.parser.colors().error)
|
||||
)
|
||||
)
|
||||
);
|
||||
return reports;
|
||||
}
|
||||
PropertyMapError::NotFoundError(err) => {
|
||||
report_err!(
|
||||
&mut reports,
|
||||
token.source(),
|
||||
"Invalid Code Property".into(),
|
||||
span(
|
||||
token.start() + 1..token.end(),
|
||||
format!(
|
||||
"Property `{}` doesn't exist",
|
||||
err.fg(state.parser.colors().info)
|
||||
)
|
||||
)
|
||||
);
|
||||
return reports;
|
||||
}
|
||||
},
|
||||
Some(line_offset) => line_offset,
|
||||
_ => return reports,
|
||||
};
|
||||
|
||||
state.push(
|
||||
|
@ -520,7 +467,6 @@ impl RegexRule for CodeRule {
|
|||
);
|
||||
if let Some(props) = matches.get(1).map(|m| m.range()) {
|
||||
sems.add(props.start - 1..props.start, tokens.code_props_sep);
|
||||
sems.add(props.clone(), tokens.code_props);
|
||||
sems.add(props.end..props.end + 1, tokens.code_props_sep);
|
||||
}
|
||||
if let Some(lang) = matches.get(2).map(|m| m.range()) {
|
||||
|
@ -794,8 +740,10 @@ test code
|
|||
validate_semantics!(state, source.clone(), 0,
|
||||
code_sep { delta_line == 1, delta_start == 0, length == 3 };
|
||||
code_props_sep { delta_line == 0, delta_start == 3, length == 1 };
|
||||
code_props { delta_line == 0, delta_start == 1, length == 14 };
|
||||
code_props_sep { delta_line == 0, delta_start == 14, length == 1 };
|
||||
prop_name { delta_line == 0, delta_start == 1, length == 11 };
|
||||
prop_equal { delta_line == 0, delta_start == 11, length == 1 };
|
||||
prop_value { delta_line == 0, delta_start == 1, length == 2 };
|
||||
code_props_sep { delta_line == 0, delta_start == 2, length == 1 };
|
||||
code_lang { delta_line == 0, delta_start == 1, length == 2 };
|
||||
code_title { delta_line == 0, delta_start == 3, length == 6 };
|
||||
code_content { delta_line == 1, delta_start == 0, length == 10 };
|
||||
|
|
|
@ -55,9 +55,8 @@ impl CustomStyle for LuaCustomStyle {
|
|||
let mut ctx = KernelContext::new(location.clone(), state, document);
|
||||
|
||||
let mut reports = vec![];
|
||||
kernel.run_with_context(&mut ctx, |lua| {
|
||||
if let Err(err) = self.start.call::<_, ()>(())
|
||||
{
|
||||
kernel.run_with_context(&mut ctx, |_lua| {
|
||||
if let Err(err) = self.start.call::<_, ()>(()) {
|
||||
report_err!(
|
||||
&mut reports,
|
||||
location.source(),
|
||||
|
@ -71,6 +70,7 @@ impl CustomStyle for LuaCustomStyle {
|
|||
}
|
||||
});
|
||||
|
||||
reports.extend(ctx.reports);
|
||||
reports
|
||||
}
|
||||
|
||||
|
@ -85,9 +85,8 @@ impl CustomStyle for LuaCustomStyle {
|
|||
let mut ctx = KernelContext::new(location.clone(), state, document);
|
||||
|
||||
let mut reports = vec![];
|
||||
kernel.run_with_context(&mut ctx, |lua| {
|
||||
if let Err(err) = self.end.call::<_, ()>(())
|
||||
{
|
||||
kernel.run_with_context(&mut ctx, |_lua| {
|
||||
if let Err(err) = self.end.call::<_, ()>(()) {
|
||||
report_err!(
|
||||
&mut reports,
|
||||
location.source(),
|
||||
|
@ -101,6 +100,7 @@ impl CustomStyle for LuaCustomStyle {
|
|||
}
|
||||
});
|
||||
|
||||
reports.extend(ctx.reports);
|
||||
reports
|
||||
}
|
||||
}
|
||||
|
@ -344,7 +344,13 @@ impl Rule for CustomStyleRule {
|
|||
bindings.push((
|
||||
"define_toggled".into(),
|
||||
lua.create_function(
|
||||
|_, (name, token, on_start, on_end): (String, String, mlua::Function, mlua::Function)| {
|
||||
|_,
|
||||
(name, token, on_start, on_end): (
|
||||
String,
|
||||
String,
|
||||
mlua::Function,
|
||||
mlua::Function,
|
||||
)| {
|
||||
let mut result = Ok(());
|
||||
|
||||
let style = LuaCustomStyle {
|
||||
|
|
|
@ -5,9 +5,8 @@ use std::sync::Once;
|
|||
use crate::lua::kernel::CTX;
|
||||
use crate::parser::parser::ParseMode;
|
||||
use crate::parser::parser::ParserState;
|
||||
use crate::parser::util::Property;
|
||||
use crate::parser::util::PropertyMapError;
|
||||
use crate::parser::util::PropertyParser;
|
||||
use crate::parser::property::Property;
|
||||
use crate::parser::property::PropertyParser;
|
||||
use ariadne::Fmt;
|
||||
use crypto::digest::Digest;
|
||||
use crypto::sha2::Sha512;
|
||||
|
@ -18,6 +17,7 @@ use lsp::semantic::Semantics;
|
|||
use mlua::Error::BadArgument;
|
||||
use mlua::Function;
|
||||
use mlua::Lua;
|
||||
use parser::util::escape_source;
|
||||
use regex::Captures;
|
||||
use regex::Regex;
|
||||
|
||||
|
@ -166,14 +166,13 @@ impl GraphRule {
|
|||
props.insert(
|
||||
"layout".to_string(),
|
||||
Property::new(
|
||||
true,
|
||||
"Graphviz layout engine see <https://graphviz.org/docs/layouts/>".to_string(),
|
||||
Some("dot".to_string()),
|
||||
),
|
||||
);
|
||||
props.insert(
|
||||
"width".to_string(),
|
||||
Property::new(true, "SVG width".to_string(), Some("100%".to_string())),
|
||||
Property::new("SVG width".to_string(), Some("100%".to_string())),
|
||||
);
|
||||
Self {
|
||||
re: [Regex::new(
|
||||
|
@ -239,98 +238,31 @@ impl RegexRule for GraphRule {
|
|||
};
|
||||
|
||||
// Properties
|
||||
let properties = match matches.get(1) {
|
||||
None => match self.properties.default() {
|
||||
Ok(properties) => properties,
|
||||
Err(e) => {
|
||||
report_err!(
|
||||
&mut reports,
|
||||
token.source(),
|
||||
"Invalid Graph Properties".into(),
|
||||
span(
|
||||
token.range.clone(),
|
||||
format!("Graph is missing property: {e}")
|
||||
)
|
||||
);
|
||||
return reports;
|
||||
}
|
||||
},
|
||||
Some(props) => {
|
||||
let processed =
|
||||
util::escape_text('\\', "]", props.as_str().trim_start().trim_end());
|
||||
match self.properties.parse(processed.as_str()) {
|
||||
Err(e) => {
|
||||
report_err!(
|
||||
&mut reports,
|
||||
token.source(),
|
||||
"Invalid Graph Properties".into(),
|
||||
span(props.range(), e)
|
||||
);
|
||||
return reports;
|
||||
}
|
||||
Ok(properties) => properties,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Property "layout"
|
||||
let graph_layout = match properties.get("layout", |prop, value| {
|
||||
layout_from_str(value.as_str()).map_err(|e| (prop, e))
|
||||
}) {
|
||||
Ok((_prop, kind)) => kind,
|
||||
Err(e) => match e {
|
||||
PropertyMapError::ParseError((prop, err)) => {
|
||||
report_err!(
|
||||
&mut reports,
|
||||
token.source(),
|
||||
"Invalid Graph Property".into(),
|
||||
span(
|
||||
token.range.clone(),
|
||||
format!(
|
||||
"Property `{}` cannot be converted: {}",
|
||||
prop.fg(state.parser.colors().info),
|
||||
err.fg(state.parser.colors().error)
|
||||
)
|
||||
)
|
||||
);
|
||||
return reports;
|
||||
}
|
||||
PropertyMapError::NotFoundError(err) => {
|
||||
report_err!(
|
||||
&mut reports,
|
||||
token.source(),
|
||||
"Invalid Graph Property".into(),
|
||||
span(token.start() + 1..token.end(), err)
|
||||
);
|
||||
return reports;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// FIXME: You can escape html, make sure we escape single "
|
||||
// Property "width"
|
||||
let graph_width = match properties.get("width", |_, value| -> Result<String, ()> {
|
||||
Ok(value.clone())
|
||||
}) {
|
||||
Ok((_, kind)) => kind,
|
||||
Err(e) => match e {
|
||||
PropertyMapError::NotFoundError(err) => {
|
||||
report_err!(
|
||||
&mut reports,
|
||||
token.source(),
|
||||
"Invalid Graph Property".into(),
|
||||
span(
|
||||
token.start() + 1..token.end(),
|
||||
format!(
|
||||
"Property `{}` is missing",
|
||||
err.fg(state.parser.colors().info)
|
||||
)
|
||||
)
|
||||
);
|
||||
return reports;
|
||||
}
|
||||
_ => panic!("Unknown error"),
|
||||
},
|
||||
let prop_source = escape_source(
|
||||
token.source(),
|
||||
matches.get(1).map_or(0..0, |m| m.range()),
|
||||
"Graphviz Properties".into(),
|
||||
'\\',
|
||||
"]",
|
||||
);
|
||||
let properties =
|
||||
match self
|
||||
.properties
|
||||
.parse("Graphviz", &mut reports, state, prop_source.into())
|
||||
{
|
||||
Some(props) => props,
|
||||
None => return reports,
|
||||
};
|
||||
let (graph_layout, graph_width) = match (
|
||||
properties.get(&mut reports, "layout", |_, value| {
|
||||
layout_from_str(value.value.as_str())
|
||||
}),
|
||||
properties.get(&mut reports, "width", |_, value| {
|
||||
Result::<_, String>::Ok(value.value.clone())
|
||||
}),
|
||||
) {
|
||||
(Some(graph_layout), Some(graph_width)) => (graph_layout, graph_width),
|
||||
_ => return reports,
|
||||
};
|
||||
|
||||
state.push(
|
||||
|
@ -348,7 +280,6 @@ impl RegexRule for GraphRule {
|
|||
sems.add(range.start..range.start + 7, tokens.graph_sep);
|
||||
if let Some(props) = matches.get(1).map(|m| m.range()) {
|
||||
sems.add(props.start - 1..props.start, tokens.graph_props_sep);
|
||||
sems.add(props.clone(), tokens.graph_props);
|
||||
sems.add(props.end..props.end + 1, tokens.graph_props_sep);
|
||||
}
|
||||
sems.add(matches.get(2).unwrap().range(), tokens.graph_content);
|
||||
|
@ -490,8 +421,10 @@ digraph {
|
|||
validate_semantics!(state, source.clone(), 0,
|
||||
graph_sep { delta_line == 1, delta_start == 0, length == 7 };
|
||||
graph_props_sep { delta_line == 0, delta_start == 7, length == 1 };
|
||||
graph_props { delta_line == 0, delta_start == 1, length == 9 };
|
||||
graph_props_sep { delta_line == 0, delta_start == 9, length == 1 };
|
||||
prop_name { delta_line == 0, delta_start == 1, length == 5 };
|
||||
prop_equal { delta_line == 0, delta_start == 5, length == 1 };
|
||||
prop_value { delta_line == 0, delta_start == 1, length == 3 };
|
||||
graph_props_sep { delta_line == 0, delta_start == 3, length == 1 };
|
||||
graph_content { delta_line == 0, delta_start == 1, length == 1 };
|
||||
graph_content { delta_line == 1, delta_start == 0, length == 10 };
|
||||
graph_content { delta_line == 1, delta_start == 0, length == 2 };
|
||||
|
|
|
@ -15,11 +15,13 @@ use crate::parser::rule::RegexRule;
|
|||
use crate::parser::source::Token;
|
||||
use crate::parser::state::RuleState;
|
||||
use crate::parser::state::Scope;
|
||||
use crate::parser::util::escape_text;
|
||||
use ariadne::Fmt;
|
||||
use mlua::Error::BadArgument;
|
||||
use mlua::Function;
|
||||
use mlua::Lua;
|
||||
use parser::source::Source;
|
||||
use parser::source::VirtualSource;
|
||||
use parser::util::escape_source;
|
||||
use regex::Captures;
|
||||
use regex::Match;
|
||||
use regex::Regex;
|
||||
|
@ -54,8 +56,8 @@ impl FromStr for LayoutToken {
|
|||
|
||||
mod default_layouts {
|
||||
use crate::parser::layout::LayoutType;
|
||||
use crate::parser::util::Property;
|
||||
use crate::parser::util::PropertyParser;
|
||||
use crate::parser::property::Property;
|
||||
use crate::parser::property::PropertyParser;
|
||||
|
||||
use super::*;
|
||||
|
||||
|
@ -68,7 +70,6 @@ mod default_layouts {
|
|||
properties.insert(
|
||||
"style".to_string(),
|
||||
Property::new(
|
||||
true,
|
||||
"Additional style for the split".to_string(),
|
||||
Some("".to_string()),
|
||||
),
|
||||
|
@ -83,46 +84,38 @@ mod default_layouts {
|
|||
|
||||
fn expects(&self) -> Range<usize> { 1..1 }
|
||||
|
||||
fn parse_properties(&self, properties: &str) -> Result<Option<Box<dyn Any>>, String> {
|
||||
let props = if properties.is_empty() {
|
||||
self.0.default()
|
||||
} else {
|
||||
self.0.parse(properties)
|
||||
}
|
||||
.map_err(|err| {
|
||||
format!(
|
||||
"Failed to parse properties for layout {}: {err}",
|
||||
self.name()
|
||||
)
|
||||
})?;
|
||||
fn parse_properties(
|
||||
&self,
|
||||
reports: &mut Vec<Report>,
|
||||
state: &ParserState,
|
||||
token: Token,
|
||||
) -> Option<Box<dyn Any>> {
|
||||
let properties = match self.0.parse("Centered Layout", reports, state, token) {
|
||||
Some(props) => props,
|
||||
None => return None,
|
||||
};
|
||||
|
||||
let style = props
|
||||
.get("style", |_, value| -> Result<String, ()> {
|
||||
Ok(value.clone())
|
||||
})
|
||||
.map_err(|err| format!("Failed to parse style: {err:#?}"))
|
||||
.map(|(_, value)| value)?;
|
||||
let style = match properties.get(reports, "style", |_, value| {
|
||||
Result::<_, String>::Ok(value.value.clone())
|
||||
}) {
|
||||
Some(style) => style,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
Ok(Some(Box::new(style)))
|
||||
Some(Box::new(style))
|
||||
}
|
||||
|
||||
fn compile(
|
||||
&self,
|
||||
token: LayoutToken,
|
||||
_id: usize,
|
||||
properties: &Option<Box<dyn Any>>,
|
||||
properties: &Box<dyn Any>,
|
||||
compiler: &Compiler,
|
||||
_document: &dyn Document,
|
||||
) -> Result<String, String> {
|
||||
match compiler.target() {
|
||||
Target::HTML => {
|
||||
let style = match properties
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.downcast_ref::<String>()
|
||||
.unwrap()
|
||||
.as_str()
|
||||
{
|
||||
let style = match properties.downcast_ref::<String>().unwrap().as_str() {
|
||||
"" => "".to_string(),
|
||||
str => format!(r#" style={}"#, Compiler::sanitize(compiler.target(), str)),
|
||||
};
|
||||
|
@ -146,7 +139,6 @@ mod default_layouts {
|
|||
properties.insert(
|
||||
"style".to_string(),
|
||||
Property::new(
|
||||
true,
|
||||
"Additional style for the split".to_string(),
|
||||
Some("".to_string()),
|
||||
),
|
||||
|
@ -161,46 +153,38 @@ mod default_layouts {
|
|||
|
||||
fn expects(&self) -> Range<usize> { 2..usize::MAX }
|
||||
|
||||
fn parse_properties(&self, properties: &str) -> Result<Option<Box<dyn Any>>, String> {
|
||||
let props = if properties.is_empty() {
|
||||
self.0.default()
|
||||
} else {
|
||||
self.0.parse(properties)
|
||||
}
|
||||
.map_err(|err| {
|
||||
format!(
|
||||
"Failed to parse properties for layout {}: {err}",
|
||||
self.name()
|
||||
)
|
||||
})?;
|
||||
fn parse_properties(
|
||||
&self,
|
||||
reports: &mut Vec<Report>,
|
||||
state: &ParserState,
|
||||
token: Token,
|
||||
) -> Option<Box<dyn Any>> {
|
||||
let properties = match self.0.parse("Split Layout", reports, state, token) {
|
||||
Some(props) => props,
|
||||
None => return None,
|
||||
};
|
||||
|
||||
let style = props
|
||||
.get("style", |_, value| -> Result<String, ()> {
|
||||
Ok(value.clone())
|
||||
})
|
||||
.map_err(|err| format!("Failed to parse style: {err:#?}"))
|
||||
.map(|(_, value)| value)?;
|
||||
let style = match properties.get(reports, "style", |_, value| {
|
||||
Result::<_, String>::Ok(value.value.clone())
|
||||
}) {
|
||||
Some(style) => style,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
Ok(Some(Box::new(style)))
|
||||
Some(Box::new(style))
|
||||
}
|
||||
|
||||
fn compile(
|
||||
&self,
|
||||
token: LayoutToken,
|
||||
_id: usize,
|
||||
properties: &Option<Box<dyn Any>>,
|
||||
properties: &Box<dyn Any>,
|
||||
compiler: &Compiler,
|
||||
_document: &dyn Document,
|
||||
) -> Result<String, String> {
|
||||
match compiler.target() {
|
||||
Target::HTML => {
|
||||
let style = match properties
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.downcast_ref::<String>()
|
||||
.unwrap()
|
||||
.as_str()
|
||||
{
|
||||
let style = match properties.downcast_ref::<String>().unwrap().as_str() {
|
||||
"" => "".to_string(),
|
||||
str => format!(r#" style={}"#, Compiler::sanitize(compiler.target(), str)),
|
||||
};
|
||||
|
@ -225,11 +209,7 @@ mod default_layouts {
|
|||
let mut properties = HashMap::new();
|
||||
properties.insert(
|
||||
"title".to_string(),
|
||||
Property::new(
|
||||
true,
|
||||
"Spoiler title".to_string(),
|
||||
Some("".to_string())
|
||||
),
|
||||
Property::new("Spoiler title".to_string(), Some("".to_string())),
|
||||
);
|
||||
|
||||
Self(PropertyParser { properties })
|
||||
|
@ -241,50 +221,45 @@ mod default_layouts {
|
|||
|
||||
fn expects(&self) -> Range<usize> { 1..1 }
|
||||
|
||||
fn parse_properties(&self, properties: &str) -> Result<Option<Box<dyn Any>>, String> {
|
||||
let props = if properties.is_empty() {
|
||||
self.0.default()
|
||||
} else {
|
||||
self.0.parse(properties)
|
||||
}
|
||||
.map_err(|err| {
|
||||
format!(
|
||||
"Failed to parse properties for layout {}: {err}",
|
||||
self.name()
|
||||
)
|
||||
})?;
|
||||
fn parse_properties(
|
||||
&self,
|
||||
reports: &mut Vec<Report>,
|
||||
state: &ParserState,
|
||||
token: Token,
|
||||
) -> Option<Box<dyn Any>> {
|
||||
let properties = match self.0.parse("Spoiler Layout", reports, state, token) {
|
||||
Some(props) => props,
|
||||
None => return None,
|
||||
};
|
||||
|
||||
let title = props
|
||||
.get("title", |_, value| -> Result<String, ()> {
|
||||
Ok(value.clone())
|
||||
})
|
||||
.map_err(|err| format!("Failed to parse style: {err:#?}"))
|
||||
.map(|(_, value)| value)?;
|
||||
let title = match properties.get(reports, "title", |_, value| {
|
||||
Result::<_, String>::Ok(value.value.clone())
|
||||
}) {
|
||||
Some(title) => title,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
Ok(Some(Box::new(title)))
|
||||
Some(Box::new(title))
|
||||
}
|
||||
|
||||
fn compile(
|
||||
&self,
|
||||
token: LayoutToken,
|
||||
_id: usize,
|
||||
properties: &Option<Box<dyn Any>>,
|
||||
properties: &Box<dyn Any>,
|
||||
compiler: &Compiler,
|
||||
_document: &dyn Document,
|
||||
) -> Result<String, String> {
|
||||
match compiler.target() {
|
||||
Target::HTML => {
|
||||
let title = properties
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.downcast_ref::<String>()
|
||||
.unwrap();
|
||||
let title = properties.downcast_ref::<String>().unwrap();
|
||||
match token {
|
||||
LayoutToken::Begin => Ok(format!(
|
||||
r#"<details class="spoiler"><summary>{}</summary>"#, Compiler::sanitize(compiler.target(), title)
|
||||
r#"<details class="spoiler"><summary>{}</summary>"#,
|
||||
Compiler::sanitize(compiler.target(), title)
|
||||
)),
|
||||
LayoutToken::End => Ok(r#"</details>"#.to_string()),
|
||||
_ => panic!()
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
_ => todo!(""),
|
||||
|
@ -299,7 +274,7 @@ struct Layout {
|
|||
pub(self) layout: Rc<dyn LayoutType>,
|
||||
pub(self) id: usize,
|
||||
pub(self) token: LayoutToken,
|
||||
pub(self) properties: Option<Box<dyn Any>>,
|
||||
pub(self) properties: Box<dyn Any>,
|
||||
}
|
||||
|
||||
impl Element for Layout {
|
||||
|
@ -330,9 +305,12 @@ impl RuleState for LayoutState {
|
|||
|
||||
let doc_borrow = document.content().borrow();
|
||||
let at = doc_borrow.last().map_or(
|
||||
Token::new(document.source().content().len()..document.source().content().len(), document.source()),
|
||||
|last| last.location().to_owned()
|
||||
);
|
||||
Token::new(
|
||||
document.source().content().len()..document.source().content().len(),
|
||||
document.source(),
|
||||
),
|
||||
|last| last.location().to_owned(),
|
||||
);
|
||||
|
||||
for (tokens, layout_type) in &self.stack {
|
||||
let start = tokens.first().unwrap();
|
||||
|
@ -406,42 +384,23 @@ impl LayoutRule {
|
|||
|
||||
pub fn parse_properties<'a>(
|
||||
mut reports: &mut Vec<Report>,
|
||||
state: &ParserState,
|
||||
token: &Token,
|
||||
layout_type: Rc<dyn LayoutType>,
|
||||
properties: Option<Match>,
|
||||
) -> Result<Option<Box<dyn Any>>, ()> {
|
||||
match properties {
|
||||
None => match layout_type.parse_properties("") {
|
||||
Ok(props) => Ok(props),
|
||||
Err(err) => {
|
||||
report_err!(
|
||||
&mut reports,
|
||||
token.source(),
|
||||
"Invalid Layout Properties".into(),
|
||||
span(
|
||||
token.start() + 1..token.end(),
|
||||
format!("Layout is missing required property: {err}")
|
||||
)
|
||||
);
|
||||
Err(())
|
||||
}
|
||||
},
|
||||
Some(props) => {
|
||||
let content = escape_text('\\', "]", props.as_str());
|
||||
match layout_type.parse_properties(content.as_str()) {
|
||||
Ok(props) => Ok(props),
|
||||
Err(err) => {
|
||||
report_err!(
|
||||
&mut reports,
|
||||
token.source(),
|
||||
"Invalid Layout Properties".into(),
|
||||
span(props.range(), err)
|
||||
);
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
m: Option<Match>,
|
||||
) -> Option<Box<dyn Any>> {
|
||||
let prop_source = escape_source(
|
||||
token.source(),
|
||||
m.map_or(0..0, |m| m.range()),
|
||||
format!("Layout {} Properties", layout_type.name()),
|
||||
'\\',
|
||||
"]",
|
||||
);
|
||||
layout_type.parse_properties(
|
||||
reports,
|
||||
state,
|
||||
Token::new(0..prop_source.content().len(), prop_source),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -546,12 +505,13 @@ impl RegexRule for LayoutRule {
|
|||
// Parse properties
|
||||
let properties = match LayoutRule::parse_properties(
|
||||
&mut reports,
|
||||
state,
|
||||
&token,
|
||||
layout_type.clone(),
|
||||
matches.get(1),
|
||||
) {
|
||||
Ok(props) => props,
|
||||
Err(()) => return reports,
|
||||
Some(props) => props,
|
||||
None => return reports,
|
||||
};
|
||||
|
||||
state.push(
|
||||
|
@ -590,7 +550,6 @@ impl RegexRule for LayoutRule {
|
|||
);
|
||||
if let Some(props) = matches.get(1).map(|m| m.range()) {
|
||||
sems.add(props.start - 1..props.start, tokens.layout_props_sep);
|
||||
sems.add(props.clone(), tokens.layout_props);
|
||||
sems.add(props.end..props.end + 1, tokens.layout_props_sep);
|
||||
}
|
||||
sems.add(matches.get(2).unwrap().range(), tokens.layout_type);
|
||||
|
@ -650,12 +609,13 @@ impl RegexRule for LayoutRule {
|
|||
// Parse properties
|
||||
let properties = match LayoutRule::parse_properties(
|
||||
&mut reports,
|
||||
state,
|
||||
&token,
|
||||
layout_type.clone(),
|
||||
matches.get(1),
|
||||
) {
|
||||
Ok(props) => props,
|
||||
Err(()) => return reports,
|
||||
Some(props) => props,
|
||||
None => return reports,
|
||||
};
|
||||
|
||||
if let Some((sems, tokens)) = Semantics::from_source(token.source(), &state.shared.lsp)
|
||||
|
@ -671,7 +631,6 @@ impl RegexRule for LayoutRule {
|
|||
);
|
||||
if let Some(props) = matches.get(1).map(|m| m.range()) {
|
||||
sems.add(props.start - 1..props.start, tokens.layout_props_sep);
|
||||
sems.add(props.clone(), tokens.layout_props);
|
||||
sems.add(props.end..props.end + 1, tokens.layout_props_sep);
|
||||
}
|
||||
}
|
||||
|
@ -732,12 +691,13 @@ impl RegexRule for LayoutRule {
|
|||
// Parse properties
|
||||
let properties = match LayoutRule::parse_properties(
|
||||
&mut reports,
|
||||
state,
|
||||
&token,
|
||||
layout_type.clone(),
|
||||
matches.get(1),
|
||||
) {
|
||||
Ok(props) => props,
|
||||
Err(()) => return reports,
|
||||
Some(props) => props,
|
||||
None => return reports,
|
||||
};
|
||||
|
||||
let layout_type = layout_type.clone();
|
||||
|
@ -757,7 +717,6 @@ impl RegexRule for LayoutRule {
|
|||
);
|
||||
if let Some(props) = matches.get(1).map(|m| m.range()) {
|
||||
sems.add(props.start - 1..props.start, tokens.layout_props_sep);
|
||||
sems.add(props.clone(), tokens.layout_props);
|
||||
sems.add(props.end..props.end + 1, tokens.layout_props_sep);
|
||||
}
|
||||
}
|
||||
|
@ -803,8 +762,8 @@ impl RegexRule for LayoutRule {
|
|||
Ok(token) => token,
|
||||
};
|
||||
|
||||
CTX.with_borrow(|ctx| {
|
||||
ctx.as_ref().map(|ctx| {
|
||||
CTX.with_borrow_mut(|ctx| {
|
||||
ctx.as_mut().map(|ctx| {
|
||||
// Make sure the rule state has been initialized
|
||||
let rule_state = LayoutRule::initialize_state(ctx.state);
|
||||
|
||||
|
@ -827,17 +786,18 @@ impl RegexRule for LayoutRule {
|
|||
};
|
||||
|
||||
// Parse properties
|
||||
let layout_properties = match layout_type.parse_properties(properties.as_str()) {
|
||||
Err(err) => {
|
||||
let prop_source = Rc::new(VirtualSource::new(ctx.location.clone(), ":LUA:Layout Properties".into(), properties)) as Rc<dyn Source>;
|
||||
let layout_properties = match layout_type.parse_properties(&mut ctx.reports, ctx.state, prop_source.into()) {
|
||||
None => {
|
||||
result = Err(BadArgument {
|
||||
to: Some("push".to_string()),
|
||||
pos: 3,
|
||||
name: Some("properties".to_string()),
|
||||
cause: Arc::new(mlua::Error::external(err)),
|
||||
cause: Arc::new(mlua::Error::external("Failed to parse properties")),
|
||||
});
|
||||
return;
|
||||
},
|
||||
Ok(properties) => properties,
|
||||
Some(properties) => properties,
|
||||
};
|
||||
|
||||
let id = match layout_token {
|
||||
|
@ -1162,8 +1122,10 @@ mod tests {
|
|||
layout_sep { delta_line == 1, delta_start == 1, length == 2 };
|
||||
layout_token { delta_line == 0, delta_start == 2, length == 11 };
|
||||
layout_props_sep { delta_line == 0, delta_start == 11, length == 1 };
|
||||
layout_props { delta_line == 0, delta_start == 1, length == 8 };
|
||||
layout_props_sep { delta_line == 0, delta_start == 8, length == 1 };
|
||||
prop_name { delta_line == 0, delta_start == 1, length == 5 };
|
||||
prop_equal { delta_line == 0, delta_start == 5, length == 1 };
|
||||
prop_value { delta_line == 0, delta_start == 1, length == 2 };
|
||||
layout_props_sep { delta_line == 0, delta_start == 2, length == 1 };
|
||||
layout_sep { delta_line == 1, delta_start == 0, length == 2 };
|
||||
layout_token { delta_line == 0, delta_start == 2, length == 10 };
|
||||
);
|
||||
|
|
|
@ -13,6 +13,8 @@ use crate::document::element::Element;
|
|||
use crate::lsp::semantic::Semantics;
|
||||
use crate::parser::parser::ParseMode;
|
||||
use crate::parser::parser::ParserState;
|
||||
use crate::parser::property::Property;
|
||||
use crate::parser::property::PropertyParser;
|
||||
use crate::parser::reports::macros::*;
|
||||
use crate::parser::reports::Report;
|
||||
use crate::parser::reports::*;
|
||||
|
@ -21,11 +23,7 @@ use crate::parser::source::Cursor;
|
|||
use crate::parser::source::Token;
|
||||
use crate::parser::source::VirtualSource;
|
||||
use crate::parser::util;
|
||||
use crate::parser::util::escape_text;
|
||||
use crate::parser::util::Property;
|
||||
use crate::parser::util::PropertyMapError;
|
||||
use crate::parser::util::PropertyParser;
|
||||
use regex::Match;
|
||||
use parser::util::escape_source;
|
||||
use regex::Regex;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
|
@ -137,11 +135,11 @@ impl ListRule {
|
|||
let mut props = HashMap::new();
|
||||
props.insert(
|
||||
"offset".to_string(),
|
||||
Property::new(false, "Entry numbering offset".to_string(), None),
|
||||
Property::new("Entry numbering offset".to_string(), None),
|
||||
);
|
||||
props.insert(
|
||||
"bullet".to_string(),
|
||||
Property::new(false, "Entry bullet".to_string(), None),
|
||||
Property::new("Entry bullet".to_string(), None),
|
||||
);
|
||||
|
||||
Self {
|
||||
|
@ -193,28 +191,6 @@ impl ListRule {
|
|||
}
|
||||
}
|
||||
|
||||
fn parse_properties(&self, m: Match) -> Result<(Option<usize>, Option<String>), String> {
|
||||
let processed = escape_text('\\', "]", m.as_str());
|
||||
let pm = self.properties.parse(processed.as_str())?;
|
||||
|
||||
let offset = match pm.get("offset", |_, s| s.parse::<usize>()) {
|
||||
Ok((_, val)) => Some(val),
|
||||
Err(err) => match err {
|
||||
PropertyMapError::ParseError(err) => {
|
||||
return Err(format!("Failed to parse `offset`: {err}"))
|
||||
}
|
||||
PropertyMapError::NotFoundError(_) => None,
|
||||
},
|
||||
};
|
||||
|
||||
let bullet = pm
|
||||
.get("bullet", |_, s| -> Result<String, ()> { Ok(s.to_string()) })
|
||||
.map(|(_, s)| s)
|
||||
.ok();
|
||||
|
||||
Ok((offset, bullet))
|
||||
}
|
||||
|
||||
fn parse_depth(depth: &str, document: &dyn Document, offset: usize) -> Vec<(bool, usize)> {
|
||||
let mut parsed = vec![];
|
||||
let prev_entry = document
|
||||
|
@ -305,28 +281,46 @@ impl Rule for ListRule {
|
|||
end_cursor = end_cursor.at(captures.get(0).unwrap().end());
|
||||
|
||||
// Properties
|
||||
let mut offset = None;
|
||||
let mut bullet = None;
|
||||
if let Some(properties) = captures.get(2) {
|
||||
match self.parse_properties(properties) {
|
||||
Err(err) => {
|
||||
report_err!(
|
||||
&mut reports,
|
||||
cursor.source.clone(),
|
||||
"Invalid List Entry Properties".into(),
|
||||
span(properties.range(), err)
|
||||
);
|
||||
return (cursor.at(captures.get(0).unwrap().end()), reports);
|
||||
let prop_source = escape_source(
|
||||
end_cursor.source.clone(),
|
||||
captures.get(2).map_or(0..0, |m| m.range()),
|
||||
"List Properties".into(),
|
||||
'\\',
|
||||
"]",
|
||||
);
|
||||
let properties = match self.properties.parse(
|
||||
"List",
|
||||
&mut reports,
|
||||
state,
|
||||
Token::new(0..prop_source.content().len(), prop_source),
|
||||
) {
|
||||
Some(props) => props,
|
||||
None => return (end_cursor, reports),
|
||||
};
|
||||
|
||||
let (offset, bullet) = match (
|
||||
properties.get_opt(&mut reports, "offset", |_, value| {
|
||||
value.value.parse::<usize>()
|
||||
}),
|
||||
properties.get_opt(&mut reports, "bullet", |_, value| {
|
||||
Result::<_, String>::Ok(value.value.clone())
|
||||
}),
|
||||
) {
|
||||
(Some(offset), Some(bullet)) => {
|
||||
// Get bullet from previous entry if it exists
|
||||
if bullet.is_none() {
|
||||
(
|
||||
offset,
|
||||
document
|
||||
.last_element::<ListEntry>()
|
||||
.and_then(|prev| prev.bullet.clone()),
|
||||
)
|
||||
} else {
|
||||
(offset, bullet)
|
||||
}
|
||||
Ok(props) => (offset, bullet) = props,
|
||||
}
|
||||
}
|
||||
// Get bullet from previous entry if it exists
|
||||
if bullet.is_none() {
|
||||
bullet = document
|
||||
.last_element::<ListEntry>()
|
||||
.and_then(|prev| prev.bullet.clone())
|
||||
}
|
||||
_ => return (end_cursor, reports),
|
||||
};
|
||||
|
||||
// Depth
|
||||
let depth = ListRule::parse_depth(
|
||||
|
@ -341,7 +335,6 @@ impl Rule for ListRule {
|
|||
sems.add(captures.get(1).unwrap().range(), tokens.list_bullet);
|
||||
if let Some(props) = captures.get(2).map(|m| m.range()) {
|
||||
sems.add(props.start - 1..props.start, tokens.list_props_sep);
|
||||
sems.add(props.clone(), tokens.list_props);
|
||||
sems.add(props.end..props.end + 1, tokens.list_props_sep);
|
||||
}
|
||||
}
|
||||
|
@ -528,8 +521,10 @@ mod tests {
|
|||
validate_semantics!(state, source.clone(), 0,
|
||||
list_bullet { delta_line == 1, delta_start == 1, length == 1 };
|
||||
list_props_sep { delta_line == 0, delta_start == 1, length == 1 };
|
||||
list_props { delta_line == 0, delta_start == 1, length == 8 };
|
||||
list_props_sep { delta_line == 0, delta_start == 8, length == 1 };
|
||||
prop_name { delta_line == 0, delta_start == 1, length == 6 };
|
||||
prop_equal { delta_line == 0, delta_start == 6, length == 1 };
|
||||
prop_value { delta_line == 0, delta_start == 1, length == 1 };
|
||||
list_props_sep { delta_line == 0, delta_start == 1, length == 1 };
|
||||
style_marker { delta_line == 0, delta_start == 8, length == 2 };
|
||||
style_marker { delta_line == 0, delta_start == 6, length == 2 };
|
||||
list_bullet { delta_line == 2, delta_start == 1, length == 2 };
|
||||
|
|
|
@ -5,7 +5,6 @@ use ariadne::Fmt;
|
|||
use lsp::semantic::Semantics;
|
||||
use parser::util::escape_source;
|
||||
use regex::Captures;
|
||||
use regex::Match;
|
||||
use regex::Regex;
|
||||
use regex::RegexBuilder;
|
||||
|
||||
|
@ -20,16 +19,14 @@ use crate::document::element::ReferenceableElement;
|
|||
use crate::document::references::validate_refname;
|
||||
use crate::parser::parser::ParseMode;
|
||||
use crate::parser::parser::ParserState;
|
||||
use crate::parser::property::Property;
|
||||
use crate::parser::property::PropertyParser;
|
||||
use crate::parser::reports::macros::*;
|
||||
use crate::parser::reports::*;
|
||||
use crate::parser::rule::RegexRule;
|
||||
use crate::parser::source::Token;
|
||||
use crate::parser::util;
|
||||
use crate::parser::util::parse_paragraph;
|
||||
use crate::parser::util::Property;
|
||||
use crate::parser::util::PropertyMap;
|
||||
use crate::parser::util::PropertyMapError;
|
||||
use crate::parser::util::PropertyParser;
|
||||
|
||||
use super::paragraph::Paragraph;
|
||||
use super::reference::InternalReference;
|
||||
|
@ -245,19 +242,15 @@ impl MediaRule {
|
|||
let mut props = HashMap::new();
|
||||
props.insert(
|
||||
"type".to_string(),
|
||||
Property::new(
|
||||
false,
|
||||
"Override for the media type detection".to_string(),
|
||||
None,
|
||||
),
|
||||
Property::new("Override for the media type detection".to_string(), None),
|
||||
);
|
||||
props.insert(
|
||||
"width".to_string(),
|
||||
Property::new(false, "Override for the media width".to_string(), None),
|
||||
Property::new("Override for the media width".to_string(), None),
|
||||
);
|
||||
props.insert(
|
||||
"caption".to_string(),
|
||||
Property::new(false, "Medium caption".to_string(), None),
|
||||
Property::new("Medium caption".to_string(), None),
|
||||
);
|
||||
Self {
|
||||
re: [RegexBuilder::new(
|
||||
|
@ -280,47 +273,6 @@ impl MediaRule {
|
|||
Ok(trimmed)
|
||||
}
|
||||
|
||||
fn parse_properties(
|
||||
&self,
|
||||
mut reports: &mut Vec<Report>,
|
||||
token: &Token,
|
||||
m: &Option<Match>,
|
||||
) -> Option<PropertyMap> {
|
||||
match m {
|
||||
None => match self.properties.default() {
|
||||
Ok(properties) => Some(properties),
|
||||
Err(e) => {
|
||||
report_err!(
|
||||
&mut reports,
|
||||
token.source(),
|
||||
"Invalid Media Properties".into(),
|
||||
span(
|
||||
token.range.clone(),
|
||||
format!("Media is missing required property: {e}")
|
||||
)
|
||||
);
|
||||
None
|
||||
}
|
||||
},
|
||||
Some(props) => {
|
||||
let processed =
|
||||
util::escape_text('\\', "]", props.as_str().trim_start().trim_end());
|
||||
match self.properties.parse(processed.as_str()) {
|
||||
Err(e) => {
|
||||
report_err!(
|
||||
&mut reports,
|
||||
token.source(),
|
||||
"Invalid Media Properties".into(),
|
||||
span(props.range(), e)
|
||||
);
|
||||
None
|
||||
}
|
||||
Ok(properties) => Some(properties),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn detect_filetype(filename: &str) -> Option<MediaType> {
|
||||
let sep = match filename.rfind('.') {
|
||||
Some(pos) => pos,
|
||||
|
@ -390,64 +342,61 @@ impl RegexRule for MediaRule {
|
|||
};
|
||||
|
||||
// Properties
|
||||
let properties = match self.parse_properties(&mut reports, &token, &matches.get(3)) {
|
||||
Some(pm) => pm,
|
||||
let prop_source = escape_source(
|
||||
token.source(),
|
||||
matches.get(3).map_or(0..0, |m| m.range()),
|
||||
"Media Properties".into(),
|
||||
'\\',
|
||||
"]",
|
||||
);
|
||||
let properties = match self.properties.parse(
|
||||
"Media",
|
||||
&mut reports,
|
||||
state,
|
||||
Token::new(0..prop_source.content().len(), prop_source),
|
||||
) {
|
||||
Some(props) => props,
|
||||
None => return reports,
|
||||
};
|
||||
|
||||
let media_type = match Self::detect_filetype(uri.as_str()) {
|
||||
Some(media_type) => media_type,
|
||||
None => match properties.get("type", |prop, value| {
|
||||
MediaType::from_str(value.as_str()).map_err(|e| (prop, e))
|
||||
}) {
|
||||
Ok((_prop, kind)) => kind,
|
||||
Err(e) => match e {
|
||||
PropertyMapError::ParseError((prop, err)) => {
|
||||
report_err!(
|
||||
&mut reports,
|
||||
token.source(),
|
||||
"Invalid Media Property".into(),
|
||||
span(
|
||||
token.start() + 1..token.end(),
|
||||
format!(
|
||||
"Property `type: {}` cannot be converted: {}",
|
||||
prop.fg(state.parser.colors().info),
|
||||
err.fg(state.parser.colors().error)
|
||||
let (media_type, caption, width) = match (
|
||||
properties.get_opt(&mut reports, "type", |_, value| {
|
||||
MediaType::from_str(value.value.as_str())
|
||||
}),
|
||||
properties.get_opt(&mut reports, "caption", |_, value| {
|
||||
Result::<_, String>::Ok(value.value.clone())
|
||||
}),
|
||||
properties.get_opt(&mut reports, "width", |_, value| {
|
||||
Result::<_, String>::Ok(value.value.clone())
|
||||
}),
|
||||
) {
|
||||
(Some(media_type), Some(caption), Some(width)) => {
|
||||
if media_type.is_none() {
|
||||
match Self::detect_filetype(uri.as_str()) {
|
||||
None => {
|
||||
report_err!(
|
||||
&mut reports,
|
||||
token.source(),
|
||||
"Invalid Media Property".into(),
|
||||
span(
|
||||
token.start() + 1..token.end(),
|
||||
format!(
|
||||
"Failed to detect media type for `{}`",
|
||||
uri.fg(state.parser.colors().info)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
return reports;
|
||||
);
|
||||
return reports;
|
||||
}
|
||||
Some(media_type) => (media_type, caption, width),
|
||||
}
|
||||
PropertyMapError::NotFoundError(err) => {
|
||||
report_err!(
|
||||
&mut reports,
|
||||
token.source(),
|
||||
"Invalid Media Property".into(),
|
||||
span(
|
||||
token.start() + 1..token.end(),
|
||||
format!("{err}. Required because mediatype could not be detected")
|
||||
)
|
||||
);
|
||||
return reports;
|
||||
}
|
||||
},
|
||||
},
|
||||
} else {
|
||||
(media_type.unwrap(), caption, width)
|
||||
}
|
||||
}
|
||||
_ => return reports,
|
||||
};
|
||||
|
||||
let width = properties
|
||||
.get("width", |_, value| -> Result<String, ()> {
|
||||
Ok(value.clone())
|
||||
})
|
||||
.ok()
|
||||
.map(|(_, s)| s);
|
||||
|
||||
let caption = properties
|
||||
.get("caption", |_, value| -> Result<String, ()> {
|
||||
Ok(value.clone())
|
||||
})
|
||||
.ok()
|
||||
.map(|(_, value)| value);
|
||||
|
||||
if let Some((sems, tokens)) = Semantics::from_source(token.source(), &state.shared.lsp) {
|
||||
sems.add(
|
||||
matches.get(0).unwrap().start()..matches.get(0).unwrap().start() + 1,
|
||||
|
@ -476,7 +425,6 @@ impl RegexRule for MediaRule {
|
|||
// Props
|
||||
if let Some(props) = matches.get(3) {
|
||||
sems.add(props.start() - 1..props.start(), tokens.media_props_sep);
|
||||
sems.add(props.range(), tokens.media_props);
|
||||
sems.add(props.end()..props.end() + 1, tokens.media_props_sep);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,5 +17,5 @@ pub mod section;
|
|||
pub mod style;
|
||||
pub mod tex;
|
||||
pub mod text;
|
||||
pub mod variable;
|
||||
pub mod toc;
|
||||
pub mod variable;
|
||||
|
|
|
@ -6,18 +6,18 @@ use crate::lsp::semantic::Semantics;
|
|||
use crate::lua::kernel::CTX;
|
||||
use crate::parser::parser::ParseMode;
|
||||
use crate::parser::parser::ParserState;
|
||||
use crate::parser::property::Property;
|
||||
use crate::parser::property::PropertyParser;
|
||||
use crate::parser::reports::macros::*;
|
||||
use crate::parser::reports::*;
|
||||
use crate::parser::rule::RegexRule;
|
||||
use crate::parser::source::Token;
|
||||
use crate::parser::util::Property;
|
||||
use crate::parser::util::PropertyMapError;
|
||||
use crate::parser::util::PropertyParser;
|
||||
use crate::parser::util::{self};
|
||||
use ariadne::Fmt;
|
||||
use mlua::Error::BadArgument;
|
||||
use mlua::Function;
|
||||
use mlua::Lua;
|
||||
use parser::util::escape_source;
|
||||
use regex::Captures;
|
||||
use regex::Regex;
|
||||
use std::collections::HashMap;
|
||||
|
@ -59,7 +59,6 @@ impl RawRule {
|
|||
props.insert(
|
||||
"kind".to_string(),
|
||||
Property::new(
|
||||
true,
|
||||
"Element display kind".to_string(),
|
||||
Some("inline".to_string()),
|
||||
),
|
||||
|
@ -127,77 +126,28 @@ impl RegexRule for RawRule {
|
|||
}
|
||||
};
|
||||
|
||||
let properties = match matches.get(1) {
|
||||
None => match self.properties.default() {
|
||||
Ok(properties) => properties,
|
||||
Err(e) => {
|
||||
report_err!(
|
||||
&mut reports,
|
||||
token.source(),
|
||||
"Invalid Raw Code".into(),
|
||||
span(
|
||||
token.range.clone(),
|
||||
format!("Raw code is missing properties: {e}")
|
||||
)
|
||||
);
|
||||
return reports;
|
||||
}
|
||||
},
|
||||
Some(props) => {
|
||||
let processed =
|
||||
util::escape_text('\\', "]", props.as_str().trim_start().trim_end());
|
||||
match self.properties.parse(processed.as_str()) {
|
||||
Err(e) => {
|
||||
report_err!(
|
||||
&mut reports,
|
||||
token.source(),
|
||||
"Invalid Raw Code Properties".into(),
|
||||
span(props.range(), e)
|
||||
);
|
||||
return reports;
|
||||
}
|
||||
Ok(properties) => properties,
|
||||
}
|
||||
}
|
||||
let prop_source = escape_source(
|
||||
token.source(),
|
||||
matches.get(1).map_or(0..0, |m| m.range()),
|
||||
"Raw Properties".into(),
|
||||
'\\',
|
||||
"]",
|
||||
);
|
||||
let properties = match self.properties.parse(
|
||||
"Raw Code",
|
||||
&mut reports,
|
||||
state,
|
||||
Token::new(0..prop_source.content().len(), prop_source),
|
||||
) {
|
||||
Some(props) => props,
|
||||
None => return reports,
|
||||
};
|
||||
|
||||
let raw_kind: ElemKind = match properties.get("kind", |prop, value| {
|
||||
ElemKind::from_str(value.as_str()).map_err(|e| (prop, e))
|
||||
let raw_kind = match properties.get(&mut reports, "kind", |_, value| {
|
||||
ElemKind::from_str(value.value.as_str())
|
||||
}) {
|
||||
Ok((_prop, kind)) => kind,
|
||||
Err(e) => match e {
|
||||
PropertyMapError::ParseError((prop, err)) => {
|
||||
report_err!(
|
||||
&mut reports,
|
||||
token.source(),
|
||||
"Invalid Raw Code Properties".into(),
|
||||
span(
|
||||
token.range.clone(),
|
||||
format!(
|
||||
"Property `kind: {}` cannot be converted: {}",
|
||||
prop.fg(state.parser.colors().info),
|
||||
err.fg(state.parser.colors().error)
|
||||
)
|
||||
)
|
||||
);
|
||||
return reports;
|
||||
}
|
||||
PropertyMapError::NotFoundError(err) => {
|
||||
report_err!(
|
||||
&mut reports,
|
||||
token.source(),
|
||||
"Invalid Raw Code Properties".into(),
|
||||
span(
|
||||
token.range.clone(),
|
||||
format!(
|
||||
"Property `{}` is missing",
|
||||
err.fg(state.parser.colors().info)
|
||||
)
|
||||
)
|
||||
);
|
||||
return reports;
|
||||
}
|
||||
},
|
||||
None => return reports,
|
||||
Some(raw_kind) => raw_kind,
|
||||
};
|
||||
|
||||
state.push(
|
||||
|
@ -214,7 +164,6 @@ impl RegexRule for RawRule {
|
|||
sems.add(range.start..range.start + 2, tokens.raw_sep);
|
||||
if let Some(props) = matches.get(1).map(|m| m.range()) {
|
||||
sems.add(props.start - 1..props.start, tokens.raw_props_sep);
|
||||
sems.add(props.clone(), tokens.raw_props);
|
||||
sems.add(props.end..props.end + 1, tokens.raw_props_sep);
|
||||
}
|
||||
sems.add(matches.get(2).unwrap().range(), tokens.raw_content);
|
||||
|
@ -356,8 +305,10 @@ Break%<nml.raw.push("block", "Raw")>%NewParagraph%<nml.raw.push("inline", "<b>")
|
|||
validate_semantics!(state, source.clone(), 0,
|
||||
raw_sep { delta_line == 1, delta_start == 0, length == 2 };
|
||||
raw_props_sep { delta_line == 0, delta_start == 2, length == 1 };
|
||||
raw_props { delta_line == 0, delta_start == 1, length == 10 };
|
||||
raw_props_sep { delta_line == 0, delta_start == 10, length == 1 };
|
||||
prop_name { delta_line == 0, delta_start == 1, length == 4 };
|
||||
prop_equal { delta_line == 0, delta_start == 4, length == 1 };
|
||||
prop_value { delta_line == 0, delta_start == 1, length == 5 };
|
||||
raw_props_sep { delta_line == 0, delta_start == 5, length == 1 };
|
||||
raw_content { delta_line == 0, delta_start == 1, length == 4 };
|
||||
raw_sep { delta_line == 0, delta_start == 4, length == 2 };
|
||||
raw_sep { delta_line == 1, delta_start == 0, length == 2 };
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
|
||||
use parser::util::escape_source;
|
||||
use reference_style::ExternalReferenceStyle;
|
||||
use regex::Captures;
|
||||
use regex::Match;
|
||||
use regex::Regex;
|
||||
use runtime_format::FormatArgs;
|
||||
use runtime_format::FormatKey;
|
||||
|
@ -19,15 +19,13 @@ use crate::document::references::validate_refname;
|
|||
use crate::lsp::semantic::Semantics;
|
||||
use crate::parser::parser::ParseMode;
|
||||
use crate::parser::parser::ParserState;
|
||||
use crate::parser::property::Property;
|
||||
use crate::parser::property::PropertyParser;
|
||||
use crate::parser::reports::macros::*;
|
||||
use crate::parser::reports::*;
|
||||
use crate::parser::rule::RegexRule;
|
||||
use crate::parser::source::Token;
|
||||
use crate::parser::style::StyleHolder;
|
||||
use crate::parser::util;
|
||||
use crate::parser::util::Property;
|
||||
use crate::parser::util::PropertyMap;
|
||||
use crate::parser::util::PropertyParser;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct InternalReference {
|
||||
|
@ -168,58 +166,13 @@ impl ReferenceRule {
|
|||
let mut props = HashMap::new();
|
||||
props.insert(
|
||||
"caption".to_string(),
|
||||
Property::new(
|
||||
false,
|
||||
"Override the display of the reference".to_string(),
|
||||
None,
|
||||
),
|
||||
Property::new("Override the display of the reference".to_string(), None),
|
||||
);
|
||||
Self {
|
||||
re: [Regex::new(r"&\{(.*?)\}(?:\[((?:\\.|[^\\\\])*?)\])?").unwrap()],
|
||||
properties: PropertyParser { properties: props },
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_properties(
|
||||
&self,
|
||||
mut reports: &mut Vec<Report>,
|
||||
token: &Token,
|
||||
m: &Option<Match>,
|
||||
) -> Option<PropertyMap> {
|
||||
match m {
|
||||
None => match self.properties.default() {
|
||||
Ok(properties) => Some(properties),
|
||||
Err(e) => {
|
||||
report_err!(
|
||||
&mut reports,
|
||||
token.source(),
|
||||
"Invalid Reference Properties".into(),
|
||||
span(
|
||||
token.range.clone(),
|
||||
format!("Reference is missing required property: {e}")
|
||||
)
|
||||
);
|
||||
None
|
||||
}
|
||||
},
|
||||
Some(props) => {
|
||||
let processed =
|
||||
util::escape_text('\\', "]", props.as_str().trim_start().trim_end());
|
||||
match self.properties.parse(processed.as_str()) {
|
||||
Err(e) => {
|
||||
report_err!(
|
||||
&mut reports,
|
||||
token.source(),
|
||||
"Invalid Reference Properties".into(),
|
||||
span(props.range(), e)
|
||||
);
|
||||
None
|
||||
}
|
||||
Ok(properties) => Some(properties),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RegexRule for ReferenceRule {
|
||||
|
@ -280,17 +233,29 @@ impl RegexRule for ReferenceRule {
|
|||
};
|
||||
|
||||
// Properties
|
||||
let properties = match self.parse_properties(&mut reports, &token, &matches.get(2)) {
|
||||
Some(pm) => pm,
|
||||
let prop_source = escape_source(
|
||||
token.source(),
|
||||
matches.get(2).map_or(0..0, |m| m.range()),
|
||||
"Reference Properties".into(),
|
||||
'\\',
|
||||
"]",
|
||||
);
|
||||
let properties = match self.properties.parse(
|
||||
"Reference",
|
||||
&mut reports,
|
||||
state,
|
||||
Token::new(0..prop_source.content().len(), prop_source),
|
||||
) {
|
||||
Some(props) => props,
|
||||
None => return reports,
|
||||
};
|
||||
|
||||
let caption = properties
|
||||
.get("caption", |_, value| -> Result<String, ()> {
|
||||
Ok(value.clone())
|
||||
})
|
||||
.ok()
|
||||
.map(|(_, s)| s);
|
||||
let caption = match properties.get_opt(&mut reports, "caption", |_, value| {
|
||||
Result::<_, String>::Ok(value.value.clone())
|
||||
}) {
|
||||
Some(caption) => caption,
|
||||
None => return reports,
|
||||
};
|
||||
|
||||
if let Some(refdoc) = refdoc {
|
||||
// Get style
|
||||
|
@ -370,7 +335,6 @@ impl RegexRule for ReferenceRule {
|
|||
matches.get(2).map(|m| m.range()),
|
||||
) {
|
||||
sems.add(props.start - 1..props.start, tokens.reference_props_sep);
|
||||
sems.add(props.clone(), tokens.reference_props);
|
||||
sems.add(props.end..props.end + 1, tokens.reference_props_sep);
|
||||
}
|
||||
|
||||
|
|
|
@ -213,13 +213,17 @@ impl RegexRule for ScriptRule {
|
|||
);
|
||||
}
|
||||
|
||||
if let Some(hints) = Hints::from_source(token.source(), &state.shared.lsp) {
|
||||
if let Some(hints) =
|
||||
Hints::from_source(token.source(), &state.shared.lsp)
|
||||
{
|
||||
hints.add(matches.get(0).unwrap().end(), result);
|
||||
}
|
||||
} else if kind == 2
|
||||
// Eval and Parse
|
||||
{
|
||||
if let Some(hints) = Hints::from_source(token.source(), &state.shared.lsp) {
|
||||
if let Some(hints) =
|
||||
Hints::from_source(token.source(), &state.shared.lsp)
|
||||
{
|
||||
hints.add(matches.get(0).unwrap().end(), result.clone());
|
||||
}
|
||||
|
||||
|
@ -229,7 +233,6 @@ impl RegexRule for ScriptRule {
|
|||
result,
|
||||
)) as Rc<dyn Source>;
|
||||
|
||||
|
||||
state.with_state(|new_state| {
|
||||
new_state.parser.parse_into(
|
||||
new_state,
|
||||
|
@ -295,6 +298,7 @@ impl RegexRule for ScriptRule {
|
|||
sems.add(range.end - 2..range.end, tokens.script_sep);
|
||||
}
|
||||
|
||||
// Process redirects as hints
|
||||
if let Some(hints) = Hints::from_source(token.source(), &state.shared.lsp) {
|
||||
let mut label = String::new();
|
||||
ctx.redirects.iter().for_each(|redir| {
|
||||
|
@ -305,6 +309,8 @@ impl RegexRule for ScriptRule {
|
|||
hints.add(matches.get(0).unwrap().end(), label);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Process reports
|
||||
reports
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,8 +12,8 @@ use crypto::digest::Digest;
|
|||
use crypto::sha2::Sha512;
|
||||
use mlua::Function;
|
||||
use mlua::Lua;
|
||||
use parser::util::escape_source;
|
||||
use regex::Captures;
|
||||
use regex::Match;
|
||||
use regex::Regex;
|
||||
|
||||
use crate::cache::cache::Cached;
|
||||
|
@ -27,15 +27,13 @@ use crate::lsp::semantic::Semantics;
|
|||
use crate::lua::kernel::CTX;
|
||||
use crate::parser::parser::ParseMode;
|
||||
use crate::parser::parser::ParserState;
|
||||
use crate::parser::property::Property;
|
||||
use crate::parser::property::PropertyParser;
|
||||
use crate::parser::reports::macros::*;
|
||||
use crate::parser::reports::*;
|
||||
use crate::parser::rule::RegexRule;
|
||||
use crate::parser::source::Token;
|
||||
use crate::parser::util;
|
||||
use crate::parser::util::Property;
|
||||
use crate::parser::util::PropertyMap;
|
||||
use crate::parser::util::PropertyMapError;
|
||||
use crate::parser::util::PropertyParser;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum TexKind {
|
||||
|
@ -235,19 +233,15 @@ impl TexRule {
|
|||
let mut props = HashMap::new();
|
||||
props.insert(
|
||||
"env".to_string(),
|
||||
Property::new(
|
||||
true,
|
||||
"Tex environment".to_string(),
|
||||
Some("main".to_string()),
|
||||
),
|
||||
Property::new("Tex environment".to_string(), Some("main".to_string())),
|
||||
);
|
||||
props.insert(
|
||||
"kind".to_string(),
|
||||
Property::new(false, "Element display kind".to_string(), None),
|
||||
Property::new("Element display kind".to_string(), None),
|
||||
);
|
||||
props.insert(
|
||||
"caption".to_string(),
|
||||
Property::new(false, "Latex caption".to_string(), None),
|
||||
Property::new("Latex caption".to_string(), None),
|
||||
);
|
||||
Self {
|
||||
re: [
|
||||
|
@ -258,47 +252,6 @@ impl TexRule {
|
|||
properties: PropertyParser { properties: props },
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_properties(
|
||||
&self,
|
||||
mut reports: &mut Vec<Report>,
|
||||
token: &Token,
|
||||
m: &Option<Match>,
|
||||
) -> Option<PropertyMap> {
|
||||
match m {
|
||||
None => match self.properties.default() {
|
||||
Ok(properties) => Some(properties),
|
||||
Err(e) => {
|
||||
report_err!(
|
||||
&mut reports,
|
||||
token.source(),
|
||||
"Invalid Tex Properties".into(),
|
||||
span(
|
||||
token.range.clone(),
|
||||
format!("Tex is missing required property: {e}")
|
||||
)
|
||||
);
|
||||
None
|
||||
}
|
||||
},
|
||||
Some(props) => {
|
||||
let processed =
|
||||
util::escape_text('\\', "]", props.as_str().trim_start().trim_end());
|
||||
match self.properties.parse(processed.as_str()) {
|
||||
Err(e) => {
|
||||
report_err!(
|
||||
&mut reports,
|
||||
token.source(),
|
||||
"Invalid Tex Properties".into(),
|
||||
span(props.range(), e)
|
||||
);
|
||||
None
|
||||
}
|
||||
Ok(properties) => Some(properties),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RegexRule for TexRule {
|
||||
|
@ -358,67 +311,52 @@ impl RegexRule for TexRule {
|
|||
};
|
||||
|
||||
// Properties
|
||||
let properties = match self.parse_properties(&mut reports, &token, &matches.get(1)) {
|
||||
Some(pm) => pm,
|
||||
let prop_source = escape_source(
|
||||
token.source(),
|
||||
matches.get(1).map_or(0..0, |m| m.range()),
|
||||
"Tex Properties".into(),
|
||||
'\\',
|
||||
"]",
|
||||
);
|
||||
let properties = match self.properties.parse(
|
||||
"Raw Code",
|
||||
&mut reports,
|
||||
state,
|
||||
Token::new(0..prop_source.content().len(), prop_source),
|
||||
) {
|
||||
Some(props) => props,
|
||||
None => return reports,
|
||||
};
|
||||
|
||||
// Tex kind
|
||||
let tex_kind = match properties.get("kind", |prop, value| {
|
||||
TexKind::from_str(value.as_str()).map_err(|e| (prop, e))
|
||||
}) {
|
||||
Ok((_prop, kind)) => kind,
|
||||
Err(e) => match e {
|
||||
PropertyMapError::ParseError((prop, err)) => {
|
||||
report_err!(
|
||||
&mut reports,
|
||||
token.source(),
|
||||
"Invalid Tex Property".into(),
|
||||
span(
|
||||
token.range.clone(),
|
||||
format!(
|
||||
"Property `kind: {}` cannot be converted: {}",
|
||||
prop.fg(state.parser.colors().info),
|
||||
err.fg(state.parser.colors().error)
|
||||
)
|
||||
)
|
||||
);
|
||||
return reports;
|
||||
}
|
||||
PropertyMapError::NotFoundError(_) => {
|
||||
if index == 1 {
|
||||
TexKind::Inline
|
||||
} else {
|
||||
TexKind::Block
|
||||
}
|
||||
}
|
||||
},
|
||||
let (tex_kind, caption, tex_env) = match (
|
||||
properties.get_or(
|
||||
&mut reports,
|
||||
"kind",
|
||||
if index == 1 {
|
||||
TexKind::Inline
|
||||
} else {
|
||||
TexKind::Block
|
||||
},
|
||||
|_, value| TexKind::from_str(value.value.as_str()),
|
||||
),
|
||||
properties.get_opt(&mut reports, "caption", |_, value| {
|
||||
Result::<_, String>::Ok(value.value.clone())
|
||||
}),
|
||||
properties.get(&mut reports, "env", |_, value| {
|
||||
Result::<_, String>::Ok(value.value.clone())
|
||||
}),
|
||||
) {
|
||||
(Some(tex_kind), Some(caption), Some(tex_env)) => (tex_kind, caption, tex_env),
|
||||
_ => return reports,
|
||||
};
|
||||
|
||||
// Caption
|
||||
let caption = properties
|
||||
.get("caption", |_, value| -> Result<String, ()> {
|
||||
Ok(value.clone())
|
||||
})
|
||||
.ok()
|
||||
.map(|(_, value)| value);
|
||||
|
||||
// Environ
|
||||
let tex_env = properties
|
||||
.get("env", |_, value| -> Result<String, ()> {
|
||||
Ok(value.clone())
|
||||
})
|
||||
.ok()
|
||||
.map(|(_, value)| value)
|
||||
.unwrap();
|
||||
|
||||
state.push(
|
||||
document,
|
||||
Box::new(Tex {
|
||||
mathmode: index == 1,
|
||||
location: token.clone(),
|
||||
kind: tex_kind,
|
||||
env: tex_env.to_string(),
|
||||
env: tex_env,
|
||||
tex: tex_content,
|
||||
caption,
|
||||
}),
|
||||
|
@ -432,7 +370,6 @@ impl RegexRule for TexRule {
|
|||
);
|
||||
if let Some(props) = matches.get(1).map(|m| m.range()) {
|
||||
sems.add(props.start - 1..props.start, tokens.tex_props_sep);
|
||||
sems.add(props.clone(), tokens.tex_props);
|
||||
sems.add(props.end..props.end + 1, tokens.tex_props_sep);
|
||||
}
|
||||
sems.add(matches.get(2).unwrap().range(), tokens.tex_content);
|
||||
|
@ -570,7 +507,7 @@ $[kind=block,env=another] e^{i\pi}=-1$
|
|||
);
|
||||
|
||||
validate_document!(doc.content().borrow(), 0,
|
||||
Tex { mathmode == true, tex == "1+1=2", env == "main", caption == Some("Some, text\\".to_string()) };
|
||||
Tex { mathmode == true, tex == "1+1=2", env == "main", caption == Some("Some, text\\\\".to_string()) };
|
||||
Tex { mathmode == false, tex == "Non Math \\LaTeX", env == "another" };
|
||||
Tex { mathmode == true, tex == "e^{i\\pi}=-1", env == "another" };
|
||||
Tex { mathmode == true, tex == "1+1=2", env == "main", caption == Some("Some, text\\".to_string()) };
|
||||
|
@ -604,7 +541,7 @@ $[env=another] e^{i\pi}=-1$
|
|||
|
||||
validate_document!(doc.content().borrow(), 0,
|
||||
Paragraph {
|
||||
Tex { mathmode == true, tex == "1+1=2", env == "main", caption == Some("Some, text\\".to_string()) };
|
||||
Tex { mathmode == true, tex == "1+1=2", env == "main", caption == Some("Some, text\\\\".to_string()) };
|
||||
Tex { mathmode == false, tex == "Non Math \\LaTeX", env == "another", caption == Some("Enclosed ].".to_string()) };
|
||||
Tex { mathmode == true, tex == "e^{i\\pi}=-1", env == "another" };
|
||||
Tex { mathmode == true, tex == "1+1=2", env == "main", caption == Some("Some, text\\".to_string()) };
|
||||
|
@ -634,8 +571,10 @@ $[kind=inline]\LaTeX$
|
|||
validate_semantics!(state, source.clone(), 0,
|
||||
tex_sep { delta_line == 1, delta_start == 0, length == 1 };
|
||||
tex_props_sep { delta_line == 0, delta_start == 1, length == 1 };
|
||||
tex_props { delta_line == 0, delta_start == 1, length == 11 };
|
||||
tex_props_sep { delta_line == 0, delta_start == 11, length == 1 };
|
||||
prop_name { delta_line == 0, delta_start == 1, length == 4 };
|
||||
prop_equal { delta_line == 0, delta_start == 4, length == 1 };
|
||||
prop_value { delta_line == 0, delta_start == 1, length == 6 };
|
||||
tex_props_sep { delta_line == 0, delta_start == 6, length == 1 };
|
||||
tex_content { delta_line == 0, delta_start == 1, length == 6 };
|
||||
tex_sep { delta_line == 0, delta_start == 6, length == 1 };
|
||||
);
|
||||
|
|
|
@ -96,6 +96,11 @@ pub struct Tokens {
|
|||
pub section_kind: (u32, u32),
|
||||
pub section_name: (u32, u32),
|
||||
|
||||
pub prop_equal: (u32, u32),
|
||||
pub prop_comma: (u32, u32),
|
||||
pub prop_name: (u32, u32),
|
||||
pub prop_value: (u32, u32),
|
||||
|
||||
pub comment: (u32, u32),
|
||||
|
||||
pub link_display_sep: (u32, u32),
|
||||
|
@ -117,7 +122,6 @@ pub struct Tokens {
|
|||
pub reference_doc: (u32, u32),
|
||||
pub reference_link: (u32, u32),
|
||||
pub reference_props_sep: (u32, u32),
|
||||
pub reference_props: (u32, u32),
|
||||
|
||||
pub variable_operator: (u32, u32),
|
||||
pub variable_kind: (u32, u32),
|
||||
|
@ -135,7 +139,6 @@ pub struct Tokens {
|
|||
|
||||
pub code_sep: (u32, u32),
|
||||
pub code_props_sep: (u32, u32),
|
||||
pub code_props: (u32, u32),
|
||||
pub code_lang: (u32, u32),
|
||||
pub code_title: (u32, u32),
|
||||
pub code_content: (u32, u32),
|
||||
|
@ -148,31 +151,25 @@ pub struct Tokens {
|
|||
|
||||
pub list_bullet: (u32, u32),
|
||||
pub list_props_sep: (u32, u32),
|
||||
pub list_props: (u32, u32),
|
||||
|
||||
pub blockquote_marker: (u32, u32),
|
||||
pub blockquote_props_sep: (u32, u32),
|
||||
pub blockquote_props: (u32, u32),
|
||||
|
||||
pub raw_sep: (u32, u32),
|
||||
pub raw_props_sep: (u32, u32),
|
||||
pub raw_props: (u32, u32),
|
||||
pub raw_content: (u32, u32),
|
||||
|
||||
pub tex_sep: (u32, u32),
|
||||
pub tex_props_sep: (u32, u32),
|
||||
pub tex_props: (u32, u32),
|
||||
pub tex_content: (u32, u32),
|
||||
|
||||
pub graph_sep: (u32, u32),
|
||||
pub graph_props_sep: (u32, u32),
|
||||
pub graph_props: (u32, u32),
|
||||
pub graph_content: (u32, u32),
|
||||
|
||||
pub layout_sep: (u32, u32),
|
||||
pub layout_token: (u32, u32),
|
||||
pub layout_props_sep: (u32, u32),
|
||||
pub layout_props: (u32, u32),
|
||||
pub layout_type: (u32, u32),
|
||||
|
||||
pub toc_sep: (u32, u32),
|
||||
|
@ -185,7 +182,6 @@ pub struct Tokens {
|
|||
pub media_uri_sep: (u32, u32),
|
||||
pub media_uri: (u32, u32),
|
||||
pub media_props_sep: (u32, u32),
|
||||
pub media_props: (u32, u32),
|
||||
}
|
||||
|
||||
impl Tokens {
|
||||
|
@ -196,6 +192,11 @@ impl Tokens {
|
|||
section_kind: token!("enum"),
|
||||
section_name: token!("string"),
|
||||
|
||||
prop_equal: token!("operator"),
|
||||
prop_comma: token!("operator"),
|
||||
prop_name: token!("class"),
|
||||
prop_value: token!("enum"),
|
||||
|
||||
comment: token!("comment"),
|
||||
|
||||
link_display_sep: token!("macro"),
|
||||
|
@ -217,7 +218,6 @@ impl Tokens {
|
|||
reference_doc: token!("function"),
|
||||
reference_link: token!("macro"),
|
||||
reference_props_sep: token!("operator"),
|
||||
reference_props: token!("enum"),
|
||||
|
||||
variable_operator: token!("operator"),
|
||||
variable_kind: token!("operator"),
|
||||
|
@ -235,7 +235,6 @@ impl Tokens {
|
|||
|
||||
code_sep: token!("operator"),
|
||||
code_props_sep: token!("operator"),
|
||||
code_props: token!("enum"),
|
||||
code_lang: token!("function"),
|
||||
code_title: token!("number"),
|
||||
code_content: token!("string"),
|
||||
|
@ -248,31 +247,25 @@ impl Tokens {
|
|||
|
||||
list_bullet: token!("macro"),
|
||||
list_props_sep: token!("operator"),
|
||||
list_props: token!("enum"),
|
||||
|
||||
blockquote_marker: token!("macro"),
|
||||
blockquote_props_sep: token!("operator"),
|
||||
blockquote_props: token!("enum"),
|
||||
|
||||
raw_sep: token!("operator"),
|
||||
raw_props_sep: token!("operator"),
|
||||
raw_props: token!("enum"),
|
||||
raw_content: token!("string"),
|
||||
|
||||
tex_sep: token!("modifier"),
|
||||
tex_props_sep: token!("operator"),
|
||||
tex_props: token!("enum"),
|
||||
tex_content: token!("string"),
|
||||
|
||||
graph_sep: token!("modifier"),
|
||||
graph_props_sep: token!("operator"),
|
||||
graph_props: token!("enum"),
|
||||
graph_content: token!("string"),
|
||||
|
||||
layout_sep: token!("number"),
|
||||
layout_token: token!("number"),
|
||||
layout_props_sep: token!("operator"),
|
||||
layout_props: token!("enum"),
|
||||
layout_type: token!("function"),
|
||||
|
||||
toc_sep: token!("number"),
|
||||
|
@ -285,7 +278,6 @@ impl Tokens {
|
|||
media_uri_sep: token!("macro"),
|
||||
media_uri: token!("function"),
|
||||
media_props_sep: token!("operator"),
|
||||
media_props: token!("enum"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ use mlua::Lua;
|
|||
use crate::document::document::Document;
|
||||
use crate::parser::parser::Parser;
|
||||
use crate::parser::parser::ParserState;
|
||||
use crate::parser::reports::Report;
|
||||
use crate::parser::source::Token;
|
||||
|
||||
/// Redirected data from lua execution
|
||||
|
@ -21,6 +22,7 @@ pub struct KernelContext<'a, 'b, 'c> {
|
|||
pub state: &'a ParserState<'a, 'b>,
|
||||
pub document: &'c dyn Document<'c>,
|
||||
pub redirects: Vec<KernelRedirect>,
|
||||
pub reports: Vec<Report>,
|
||||
}
|
||||
|
||||
impl<'a, 'b, 'c> KernelContext<'a, 'b, 'c> {
|
||||
|
@ -34,6 +36,7 @@ impl<'a, 'b, 'c> KernelContext<'a, 'b, 'c> {
|
|||
state,
|
||||
document,
|
||||
redirects: vec![],
|
||||
reports: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -105,12 +105,11 @@ impl<'b> Parser for LangParser<'b> {
|
|||
.downcast_rc::<SourceFile>()
|
||||
.ok()
|
||||
.map(|source| {
|
||||
if source.path().is_empty() // Test mode
|
||||
if source.path().is_empty()
|
||||
// Test mode
|
||||
{
|
||||
None
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
let start = if source.path().starts_with("file:///") {
|
||||
7
|
||||
} else {
|
||||
|
@ -119,7 +118,9 @@ impl<'b> Parser for LangParser<'b> {
|
|||
let mut path = PathBuf::from(&source.path()[start..]);
|
||||
match path.canonicalize() {
|
||||
Ok(cano) => path = cano,
|
||||
Err(err) => eprintln!("Failed to canonicalize path `{}`: {err}", source.path()),
|
||||
Err(err) => {
|
||||
eprintln!("Failed to canonicalize path `{}`: {err}", source.path())
|
||||
}
|
||||
}
|
||||
path.pop();
|
||||
Some(path)
|
||||
|
@ -217,8 +218,7 @@ impl<'b> Parser for LangParser<'b> {
|
|||
);
|
||||
}
|
||||
|
||||
if path.is_some()
|
||||
{
|
||||
if path.is_some() {
|
||||
if let Err(err) = std::env::set_current_dir(¤t_dir) {
|
||||
println!(
|
||||
"Failed to set working directory to `{}`: {err} {source:#?}",
|
||||
|
|
|
@ -7,13 +7,22 @@ use crate::compiler::compiler::Compiler;
|
|||
use crate::document::document::Document;
|
||||
use crate::elements::layout::LayoutToken;
|
||||
|
||||
use super::parser::ParserState;
|
||||
use super::reports::Report;
|
||||
use super::source::Token;
|
||||
|
||||
/// Represents the type of a layout
|
||||
pub trait LayoutType: core::fmt::Debug {
|
||||
/// Name of the layout
|
||||
fn name(&self) -> &'static str;
|
||||
|
||||
/// Parses layout properties
|
||||
fn parse_properties(&self, properties: &str) -> Result<Option<Box<dyn Any>>, String>;
|
||||
fn parse_properties(
|
||||
&self,
|
||||
reports: &mut Vec<Report>,
|
||||
state: &ParserState,
|
||||
token: Token,
|
||||
) -> Option<Box<dyn Any>>;
|
||||
|
||||
/// Expected number of blocks
|
||||
fn expects(&self) -> Range<usize>;
|
||||
|
@ -23,7 +32,7 @@ pub trait LayoutType: core::fmt::Debug {
|
|||
&self,
|
||||
token: LayoutToken,
|
||||
id: usize,
|
||||
properties: &Option<Box<dyn Any>>,
|
||||
properties: &Box<dyn Any>,
|
||||
compiler: &Compiler,
|
||||
document: &dyn Document,
|
||||
) -> Result<String, String>;
|
||||
|
|
|
@ -2,6 +2,7 @@ pub mod customstyle;
|
|||
pub mod langparser;
|
||||
pub mod layout;
|
||||
pub mod parser;
|
||||
pub mod property;
|
||||
pub mod reports;
|
||||
pub mod rule;
|
||||
pub mod source;
|
||||
|
|
522
src/parser/property.rs
Normal file
522
src/parser/property.rs
Normal file
|
@ -0,0 +1,522 @@
|
|||
use std::collections::HashMap;
|
||||
use std::fmt::Display;
|
||||
use std::ops::Range;
|
||||
|
||||
use ariadne::Fmt;
|
||||
use lsp::semantic::Semantics;
|
||||
|
||||
use crate::parser::reports::macros::*;
|
||||
use crate::parser::reports::*;
|
||||
|
||||
use super::parser::ParserState;
|
||||
use super::reports::Report;
|
||||
use super::source::Token;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Property {
|
||||
description: String,
|
||||
default: Option<String>,
|
||||
}
|
||||
|
||||
impl Property {
|
||||
pub fn new(description: String, default: Option<String>) -> Self {
|
||||
Self {
|
||||
description,
|
||||
default,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Display for Property {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self.default.as_ref() {
|
||||
None => write!(f, "{}", self.description),
|
||||
Some(default) => write!(f, "{} (Default: {})", self.description, default),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The parsed value of a property
|
||||
///
|
||||
/// If property was set using default, the ranges correspond to the total range of the property string
|
||||
/// The ranges are used to provide diagnostics as well as semantics via the language server.
|
||||
#[derive(Debug)]
|
||||
pub struct PropertyValue {
|
||||
pub name_range: Range<usize>,
|
||||
pub value_range: Range<usize>,
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
pub struct PropertyMap<'a> {
|
||||
pub token: Token,
|
||||
pub rule_name: &'a str,
|
||||
pub state: &'a ParserState<'a, 'a>,
|
||||
pub properties: HashMap<String, (&'a Property, PropertyValue)>,
|
||||
}
|
||||
|
||||
impl<'a> PropertyMap<'a> {
|
||||
pub fn new(token: Token, rule_name: &'a str, state: &'a ParserState<'a, 'a>) -> Self {
|
||||
Self {
|
||||
token,
|
||||
rule_name,
|
||||
state,
|
||||
properties: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a value by key
|
||||
///
|
||||
/// # Returned value
|
||||
///
|
||||
/// * `Some(T)` on success
|
||||
/// * `None` if the key is not found or the parsing function `f` fails
|
||||
/// (Note) In this case, reports should have been set
|
||||
pub fn get<T, E, F>(&self, mut reports: &mut Vec<Report>, key: &str, f: F) -> Option<T>
|
||||
where
|
||||
F: FnOnce(&Property, &PropertyValue) -> Result<T, E>,
|
||||
E: Display,
|
||||
{
|
||||
match self.properties.get(key) {
|
||||
None => report_err!(
|
||||
&mut reports,
|
||||
self.token.source(),
|
||||
format!("Failed to parse {} properties", self.rule_name),
|
||||
span(
|
||||
self.token.range.clone(),
|
||||
format!(
|
||||
"Missing property {}",
|
||||
key.fg(self.state.parser.colors().info),
|
||||
)
|
||||
),
|
||||
),
|
||||
Some((prop, val)) => match f(prop, val) {
|
||||
Err(err) => report_err!(
|
||||
&mut reports,
|
||||
self.token.source(),
|
||||
format!("Failed to parse {} properties", self.rule_name),
|
||||
span(
|
||||
val.value_range.clone(),
|
||||
format!(
|
||||
"Unable to parse property {}: {err}",
|
||||
key.fg(self.state.parser.colors().info),
|
||||
)
|
||||
),
|
||||
),
|
||||
Ok(parsed) => return Some(parsed),
|
||||
},
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Get a value by key with a default
|
||||
///
|
||||
/// # Returned value
|
||||
///
|
||||
/// * `Some(T)` on success
|
||||
/// * `None` if the parsing function `f` fails
|
||||
/// (Note) In this case, reports should have been set
|
||||
pub fn get_or<T, E, F>(
|
||||
&self,
|
||||
mut reports: &mut Vec<Report>,
|
||||
key: &str,
|
||||
default: T,
|
||||
f: F,
|
||||
) -> Option<T>
|
||||
where
|
||||
F: FnOnce(&Property, &PropertyValue) -> Result<T, E>,
|
||||
E: Display,
|
||||
{
|
||||
match self.properties.get(key) {
|
||||
None => return Some(default),
|
||||
Some((prop, val)) => match f(prop, val) {
|
||||
Err(err) => report_err!(
|
||||
&mut reports,
|
||||
self.token.source(),
|
||||
format!("Failed to parse {} properties", self.rule_name),
|
||||
span(
|
||||
val.value_range.clone(),
|
||||
format!(
|
||||
"Unable to parse property {}: {err}",
|
||||
key.fg(self.state.parser.colors().info),
|
||||
)
|
||||
),
|
||||
),
|
||||
Ok(parsed) => return Some(parsed),
|
||||
},
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Get an optional value by key
|
||||
///
|
||||
/// # Returned value
|
||||
///
|
||||
/// * `Some(Option<T>)` on success
|
||||
/// * `None` if the parsing function `f` fails
|
||||
/// (Note) In this case, reports should have been set
|
||||
pub fn get_opt<T, E, F>(
|
||||
&self,
|
||||
mut reports: &mut Vec<Report>,
|
||||
key: &str,
|
||||
f: F,
|
||||
) -> Option<Option<T>>
|
||||
where
|
||||
F: FnOnce(&Property, &PropertyValue) -> Result<T, E>,
|
||||
E: Display,
|
||||
{
|
||||
match self.properties.get(key) {
|
||||
None => return Some(None),
|
||||
Some((prop, val)) => match f(prop, val) {
|
||||
Err(err) => report_err!(
|
||||
&mut reports,
|
||||
self.token.source(),
|
||||
format!("Failed to parse {} properties", self.rule_name),
|
||||
span(
|
||||
val.value_range.clone(),
|
||||
format!(
|
||||
"Unable to parse property {}: {err}",
|
||||
key.fg(self.state.parser.colors().info),
|
||||
)
|
||||
),
|
||||
),
|
||||
Ok(parsed) => return Some(Some(parsed)),
|
||||
},
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PropertyParser {
|
||||
pub properties: HashMap<String, Property>,
|
||||
}
|
||||
|
||||
impl PropertyParser {
|
||||
fn allowed_properties(&self, state: &ParserState) -> String {
|
||||
self.properties
|
||||
.iter()
|
||||
.fold(String::new(), |out, (name, prop)| {
|
||||
out + format!(
|
||||
"\n - {} : {}",
|
||||
name.fg(state.parser.colors().info),
|
||||
prop.description
|
||||
)
|
||||
.as_str()
|
||||
})
|
||||
}
|
||||
|
||||
/// Parses properties string "prop1=value1, prop2 = val\,2" -> {prop1: value1, prop2: val,2}
|
||||
///
|
||||
/// # Key-value pair
|
||||
///
|
||||
/// Property names/values are separated by a single '=' that cannot be escaped.
|
||||
/// Therefore names cannot contain the '=' character.
|
||||
///
|
||||
/// # Language Server
|
||||
///
|
||||
/// This function also processes properties to add them to the language server's semantics.
|
||||
/// It uses the [`Semantics::add_to_queue()`] so it is safe to add other semantics after this function call.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// let mut properties = HashMap::new();
|
||||
/// properties.insert("width".to_string(),
|
||||
/// Property::new("Width of the element in em".to_string(), None));
|
||||
///
|
||||
/// let parser = PropertyParser { properties };
|
||||
/// let source = VirtualSource::new(.. "width=15" ..)
|
||||
/// let properties = match parser.parse("Element", &mut reports, &state, source).unwrap()
|
||||
/// {
|
||||
/// Some(properties) => properties,
|
||||
/// None => return reports,
|
||||
/// };
|
||||
///
|
||||
/// assert_eq!(properties.get(&mut reports, "width", |_, value| value.parse::<i32>()).unwrap(), 15);
|
||||
/// ```
|
||||
/// # Return value
|
||||
///
|
||||
/// `Some(properties)` is returned on success. On failure, `None` is returned and the reports will have been populated.
|
||||
///
|
||||
/// Note: Only ',' inside values can be escaped, other '\' are treated literally
|
||||
pub fn parse<'a>(
|
||||
&'a self,
|
||||
rule_name: &'a str,
|
||||
mut reports: &mut Vec<Report>,
|
||||
state: &'a ParserState<'a, 'a>,
|
||||
token: Token,
|
||||
) -> Option<PropertyMap<'a>> {
|
||||
let mut pm = PropertyMap::new(token.clone(), rule_name, state);
|
||||
let mut try_insert = |name: &String,
|
||||
name_range: Range<usize>,
|
||||
value: &String,
|
||||
value_range: Range<usize>|
|
||||
-> bool {
|
||||
let trimmed_name = name.trim_start().trim_end();
|
||||
let trimmed_value = value.trim_start().trim_end();
|
||||
let prop = match self.properties.get(trimmed_name) {
|
||||
None => {
|
||||
report_err!(
|
||||
&mut reports,
|
||||
token.source(),
|
||||
format!("Failed to parse {rule_name} properties"),
|
||||
span(
|
||||
name_range,
|
||||
format!(
|
||||
"Unknown property {}, allowed properties:{}",
|
||||
name.fg(state.parser.colors().info),
|
||||
self.allowed_properties(state)
|
||||
)
|
||||
),
|
||||
);
|
||||
return false;
|
||||
}
|
||||
Some(prop) => prop,
|
||||
};
|
||||
|
||||
if let Some((_, previous)) = pm.properties.insert(
|
||||
trimmed_name.to_string(),
|
||||
(
|
||||
prop,
|
||||
PropertyValue {
|
||||
name_range: name_range.clone(),
|
||||
value_range: value_range.clone(),
|
||||
value: trimmed_value.to_string(),
|
||||
},
|
||||
),
|
||||
) {
|
||||
report_err!(
|
||||
&mut reports,
|
||||
token.source(),
|
||||
format!("Failed to parse {rule_name} properties"),
|
||||
span(
|
||||
name_range.start..value_range.end,
|
||||
format!(
|
||||
"Duplicate property {}, current value: {}",
|
||||
name.fg(state.parser.colors().info),
|
||||
trimmed_value.fg(state.parser.colors().info),
|
||||
)
|
||||
),
|
||||
span(
|
||||
previous.value_range.clone(),
|
||||
format!(
|
||||
"Previous value: {}",
|
||||
previous.value.fg(state.parser.colors().info),
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
true
|
||||
};
|
||||
|
||||
if token.range.len() != 0 {
|
||||
let mut in_name = true;
|
||||
let mut name = String::new();
|
||||
let mut name_range = token.start()..token.start();
|
||||
let mut value = String::new();
|
||||
let mut value_range = token.start()..token.start();
|
||||
let mut escaped = 0usize;
|
||||
for (pos, c) in token.source().content()[token.range.clone()].char_indices() {
|
||||
if c == '\\' {
|
||||
escaped += 1;
|
||||
} else if c == '=' && in_name {
|
||||
name_range.end = pos;
|
||||
value_range.start = pos + 1;
|
||||
in_name = false;
|
||||
(0..escaped).for_each(|_| name.push('\\'));
|
||||
escaped = 0;
|
||||
} else if c == ',' && !in_name {
|
||||
if escaped % 2 == 0
|
||||
// Not escaped
|
||||
{
|
||||
(0..escaped / 2).for_each(|_| value.push('\\'));
|
||||
escaped = 0;
|
||||
in_name = true;
|
||||
value_range.end = pos;
|
||||
|
||||
if !try_insert(&name, name_range.clone(), &value, value_range.clone()) {
|
||||
return None;
|
||||
}
|
||||
name_range.start = pos + 1;
|
||||
|
||||
name.clear();
|
||||
value.clear();
|
||||
} else {
|
||||
(0..(escaped - 1) / 2).for_each(|_| value.push('\\'));
|
||||
value.push(',');
|
||||
escaped = 0;
|
||||
}
|
||||
} else {
|
||||
if in_name {
|
||||
(0..escaped).for_each(|_| name.push('\\'));
|
||||
name.push(c);
|
||||
} else {
|
||||
(0..escaped).for_each(|_| value.push('\\'));
|
||||
value.push(c);
|
||||
}
|
||||
escaped = 0;
|
||||
}
|
||||
}
|
||||
(0..escaped).for_each(|_| value.push('\\'));
|
||||
if !in_name && value.trim_end().trim_start().is_empty() {
|
||||
report_err!(
|
||||
&mut reports,
|
||||
token.source(),
|
||||
format!("Failed to parse {rule_name} properties"),
|
||||
span(
|
||||
value_range.start..token.end(),
|
||||
format!("Expected value after last '='",)
|
||||
),
|
||||
);
|
||||
return None;
|
||||
} else if name.is_empty() || value.is_empty() {
|
||||
report_err!(
|
||||
&mut reports,
|
||||
token.source(),
|
||||
format!("Failed to parse {rule_name} properties"),
|
||||
span(
|
||||
name_range.start..token.end(),
|
||||
format!("Expected name/value pair after last ','",)
|
||||
),
|
||||
);
|
||||
return None;
|
||||
}
|
||||
|
||||
value_range.end = token.end();
|
||||
if !try_insert(&name, name_range.clone(), &value, value_range.clone()) {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((sems, tokens)) = Semantics::from_source(token.source(), &state.shared.lsp) {
|
||||
for (_, (_, value)) in &pm.properties {
|
||||
if value.name_range.start != 0 {
|
||||
sems.add_to_queue(
|
||||
value.name_range.start - 1..value.name_range.start,
|
||||
tokens.prop_comma,
|
||||
);
|
||||
}
|
||||
sems.add_to_queue(value.name_range.clone(), tokens.prop_name);
|
||||
sems.add_to_queue(
|
||||
value.name_range.end..value.value_range.start,
|
||||
tokens.prop_equal,
|
||||
);
|
||||
sems.add_to_queue(value.value_range.clone(), tokens.prop_value);
|
||||
}
|
||||
//sems.add(matches.get(0).unwrap().start()..matches.get(0).unwrap().start()+1, tokens.media_sep);
|
||||
// Refname
|
||||
//sems.add(matches.get(0).unwrap().start()+1..matches.get(0).unwrap().start()+2, tokens.media_refname_sep);
|
||||
//sems.add(matches.get(1).unwrap().range(), tokens.media_refname);
|
||||
//sems.add(matches.get(1).unwrap().end()..matches.get(1).unwrap().end()+1, tokens.media_refname_sep);
|
||||
}
|
||||
|
||||
// Insert missing properties with a default
|
||||
for (name, prop) in &self.properties {
|
||||
if pm.properties.contains_key(name) {
|
||||
continue;
|
||||
}
|
||||
if let Some(default) = &prop.default {
|
||||
pm.properties.insert(
|
||||
name.to_owned(),
|
||||
(
|
||||
prop,
|
||||
PropertyValue {
|
||||
name_range: token.range.clone(),
|
||||
value_range: token.range.clone(),
|
||||
value: default.to_owned(),
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Some(pm)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::rc::Rc;
|
||||
|
||||
use parser::langparser::LangParser;
|
||||
use parser::source::Source;
|
||||
use parser::source::SourceFile;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn property_parser_tests() {
|
||||
let mut properties = HashMap::new();
|
||||
properties.insert(
|
||||
"width".to_string(),
|
||||
Property::new("Width of the element in em".to_string(), None),
|
||||
);
|
||||
properties.insert(
|
||||
"length".to_string(),
|
||||
Property::new("Length in cm".to_string(), None),
|
||||
);
|
||||
properties.insert(
|
||||
"angle".to_string(),
|
||||
Property::new("Angle in degrees".to_string(), Some("180".to_string())),
|
||||
);
|
||||
properties.insert(
|
||||
"weight".to_string(),
|
||||
Property::new("Weight in %".to_string(), Some("0.42".to_string())),
|
||||
);
|
||||
|
||||
let langparser = LangParser::default();
|
||||
let state = ParserState::new(&langparser, None);
|
||||
let mut reports = vec![];
|
||||
|
||||
let parser = PropertyParser { properties };
|
||||
let source = Rc::new(SourceFile::with_content(
|
||||
"".into(),
|
||||
"width=15,length=-10".into(),
|
||||
None,
|
||||
)) as Rc<dyn Source>;
|
||||
let pm = parser
|
||||
.parse("Test", &mut reports, &state, source.into())
|
||||
.unwrap();
|
||||
|
||||
// Ok
|
||||
assert_eq!(
|
||||
pm.get(&mut reports, "width", |_, s| s.value.parse::<i32>())
|
||||
.unwrap(),
|
||||
15
|
||||
);
|
||||
assert_eq!(
|
||||
pm.get(&mut reports, "length", |_, s| s.value.parse::<i32>())
|
||||
.unwrap(),
|
||||
-10
|
||||
);
|
||||
assert_eq!(
|
||||
pm.get(&mut reports, "angle", |_, s| s.value.parse::<f64>())
|
||||
.unwrap(),
|
||||
180f64
|
||||
);
|
||||
assert_eq!(
|
||||
pm.get(&mut reports, "angle", |_, s| s.value.parse::<i32>())
|
||||
.unwrap(),
|
||||
180
|
||||
);
|
||||
assert_eq!(
|
||||
pm.get(&mut reports, "weight", |_, s| s.value.parse::<f32>())
|
||||
.unwrap(),
|
||||
0.42f32
|
||||
);
|
||||
assert_eq!(
|
||||
pm.get(&mut reports, "weight", |_, s| s.value.parse::<f64>())
|
||||
.unwrap(),
|
||||
0.42f64
|
||||
);
|
||||
|
||||
// Error
|
||||
assert!(pm
|
||||
.get(&mut reports, "length", |_, s| s.value.parse::<u32>())
|
||||
.is_none());
|
||||
assert!(pm
|
||||
.get(&mut reports, "height", |_, s| s.value.parse::<f64>())
|
||||
.is_none());
|
||||
}
|
||||
}
|
|
@ -322,3 +322,12 @@ impl Token {
|
|||
|
||||
pub fn end(&self) -> usize { self.range.end }
|
||||
}
|
||||
|
||||
impl From<Rc<dyn Source>> for Token {
|
||||
fn from(source: Rc<dyn Source>) -> Self {
|
||||
Self {
|
||||
range: 0..source.content().len(),
|
||||
source,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
use std::collections::HashMap;
|
||||
use std::ops::Range;
|
||||
use std::rc::Rc;
|
||||
|
||||
|
@ -233,226 +232,6 @@ pub fn parse_paragraph<'a>(
|
|||
Ok(paragraph.downcast::<Paragraph>().unwrap())
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Property {
|
||||
required: bool,
|
||||
description: String,
|
||||
default: Option<String>,
|
||||
}
|
||||
|
||||
impl Property {
|
||||
pub fn new(required: bool, description: String, default: Option<String>) -> Self {
|
||||
Self {
|
||||
required,
|
||||
description,
|
||||
default,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Display for Property {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self.default.as_ref() {
|
||||
None => write!(
|
||||
f,
|
||||
"{} {}",
|
||||
["[Opt]", "[Req]"][self.required as usize],
|
||||
self.description
|
||||
),
|
||||
Some(default) => write!(
|
||||
f,
|
||||
"{} {} (Deafult: {})",
|
||||
["[Opt]", "[Req]"][self.required as usize],
|
||||
self.description,
|
||||
default
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum PropertyMapError<E> {
|
||||
ParseError(E),
|
||||
NotFoundError(String),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PropertyMap<'a> {
|
||||
pub(crate) properties: HashMap<String, (&'a Property, String)>,
|
||||
}
|
||||
|
||||
impl<'a> PropertyMap<'a> {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
properties: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get<T, Error, F: FnOnce(&'a Property, &String) -> Result<T, Error>>(
|
||||
&self,
|
||||
name: &str,
|
||||
f: F,
|
||||
) -> Result<(&'a Property, T), PropertyMapError<Error>> {
|
||||
let (prop, value) = match self.properties.get(name) {
|
||||
Some(found) => found,
|
||||
None => {
|
||||
return Err(PropertyMapError::NotFoundError(format!(
|
||||
"Property `{name}` not found"
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
match f(prop, value) {
|
||||
Ok(parsed) => Ok((*prop, parsed)),
|
||||
Err(err) => Err(PropertyMapError::ParseError(err)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PropertyParser {
|
||||
pub properties: HashMap<String, Property>,
|
||||
}
|
||||
|
||||
impl PropertyParser {
|
||||
/// Attempts to build a default propertymap
|
||||
///
|
||||
/// Returns an error if at least one [`Property`] is required and doesn't provide a default
|
||||
pub fn default(&self) -> Result<PropertyMap<'_>, String> {
|
||||
let mut properties = PropertyMap::new();
|
||||
|
||||
for (name, prop) in &self.properties {
|
||||
match (prop.required, prop.default.as_ref()) {
|
||||
(true, None) => return Err(format!("Missing property `{name}` {prop}")),
|
||||
(false, None) => {}
|
||||
(_, Some(default)) => {
|
||||
properties
|
||||
.properties
|
||||
.insert(name.clone(), (prop, default.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(properties)
|
||||
}
|
||||
|
||||
/// Parses properties string "prop1=value1, prop2 = val\,2" -> {prop1: value1, prop2: val,2}
|
||||
///
|
||||
/// # Key-value pair
|
||||
///
|
||||
/// Property names/values are separated by a single '=' that cannot be escaped.
|
||||
/// Therefore names cannot contain the '=' character.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// let mut properties = HashMap::new();
|
||||
/// properties.insert("width".to_string(),
|
||||
/// Property::new(true, "Width of the element in em".to_string(), None));
|
||||
///
|
||||
/// let parser = PropertyParser { properties };
|
||||
/// let pm = parser.parse("width=15").unwrap();
|
||||
///
|
||||
/// assert_eq!(pm.get("width", |_, s| s.parse::<i32>()).unwrap().1, 15);
|
||||
/// ```
|
||||
/// # Return value
|
||||
///
|
||||
/// Returns the parsed property map, or an error if either:
|
||||
/// * A required property is missing
|
||||
/// * An unknown property is present
|
||||
/// * A duplicate property is present
|
||||
///
|
||||
/// Note: Only ',' inside values can be escaped, other '\' are treated literally
|
||||
pub fn parse(&self, content: &str) -> Result<PropertyMap<'_>, String> {
|
||||
let mut properties = PropertyMap::new();
|
||||
let mut try_insert = |name: &String, value: &String| -> Result<(), String> {
|
||||
let trimmed_name = name.trim_end().trim_start();
|
||||
let trimmed_value = value.trim_end().trim_start();
|
||||
let prop = match self.properties.get(trimmed_name)
|
||||
{
|
||||
None => return Err(format!("Unknown property name: `{trimmed_name}` (with value: `{trimmed_value}`). Valid properties are:\n{}",
|
||||
self.properties.iter().fold(String::new(),
|
||||
|out, (name, prop)| out + format!(" - {name}: {prop}\n").as_str()))),
|
||||
Some(prop) => prop
|
||||
};
|
||||
|
||||
if let Some((_, previous)) = properties
|
||||
.properties
|
||||
.insert(trimmed_name.to_string(), (prop, trimmed_value.to_string()))
|
||||
{
|
||||
return Err(format!("Duplicate property `{trimmed_name}`, previous value: `{previous}` current value: `{trimmed_value}`"));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
};
|
||||
|
||||
let mut in_name = true;
|
||||
let mut name = String::new();
|
||||
let mut value = String::new();
|
||||
let mut escaped = 0usize;
|
||||
for c in content.chars() {
|
||||
if c == '\\' {
|
||||
escaped += 1;
|
||||
} else if c == '=' && in_name {
|
||||
in_name = false;
|
||||
(0..escaped).for_each(|_| name.push('\\'));
|
||||
escaped = 0;
|
||||
} else if c == ',' && !in_name {
|
||||
if escaped % 2 == 0
|
||||
// Not escaped
|
||||
{
|
||||
(0..escaped / 2).for_each(|_| value.push('\\'));
|
||||
escaped = 0;
|
||||
in_name = true;
|
||||
|
||||
try_insert(&name, &value)?;
|
||||
|
||||
name.clear();
|
||||
value.clear();
|
||||
} else {
|
||||
(0..(escaped - 1) / 2).for_each(|_| value.push('\\'));
|
||||
value.push(',');
|
||||
escaped = 0;
|
||||
}
|
||||
} else {
|
||||
if in_name {
|
||||
(0..escaped).for_each(|_| name.push('\\'));
|
||||
name.push(c)
|
||||
} else {
|
||||
(0..escaped).for_each(|_| value.push('\\'));
|
||||
value.push(c)
|
||||
}
|
||||
escaped = 0;
|
||||
}
|
||||
}
|
||||
(0..escaped).for_each(|_| value.push('\\'));
|
||||
if !in_name && value.trim_end().trim_start().is_empty() {
|
||||
return Err("Expected a value after last `=`".to_string());
|
||||
} else if name.is_empty() || value.is_empty() {
|
||||
return Err("Expected non empty property list.".to_string());
|
||||
}
|
||||
|
||||
try_insert(&name, &value)?;
|
||||
|
||||
if let Err(e) = self.properties.iter().try_for_each(|(key, prop)| {
|
||||
if !properties.properties.contains_key(key) {
|
||||
if let Some(default) = &prop.default {
|
||||
properties
|
||||
.properties
|
||||
.insert(key.clone(), (prop, default.clone()));
|
||||
} else if prop.required {
|
||||
return Err(format!("Missing required property: {prop}"));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}) {
|
||||
Err(e)
|
||||
} else {
|
||||
Ok(properties)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -548,65 +327,4 @@ mod tests {
|
|||
assert_eq!(escape_text('\\', ")", "A\\)B\\"), "A)B".to_string(),);
|
||||
assert_eq!(escape_text('\\', ")", "A\\)B\\\\"), "A)B\\".to_string(),);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn property_parser_tests() {
|
||||
let mut properties = HashMap::new();
|
||||
properties.insert(
|
||||
"width".to_string(),
|
||||
Property::new(true, "Width of the element in em".to_string(), None),
|
||||
);
|
||||
properties.insert(
|
||||
"length".to_string(),
|
||||
Property::new(false, "Length in cm".to_string(), None),
|
||||
);
|
||||
properties.insert(
|
||||
"angle".to_string(),
|
||||
Property::new(
|
||||
true,
|
||||
"Angle in degrees".to_string(),
|
||||
Some("180".to_string()),
|
||||
),
|
||||
);
|
||||
properties.insert(
|
||||
"weight".to_string(),
|
||||
Property::new(false, "Weight in %".to_string(), Some("0.42".to_string())),
|
||||
);
|
||||
|
||||
let parser = PropertyParser { properties };
|
||||
let pm = parser.parse("width=15,length=-10").unwrap();
|
||||
|
||||
// Ok
|
||||
assert_eq!(pm.get("width", |_, s| s.parse::<i32>()).unwrap().1, 15);
|
||||
assert_eq!(pm.get("length", |_, s| s.parse::<i32>()).unwrap().1, -10);
|
||||
assert_eq!(pm.get("angle", |_, s| s.parse::<f64>()).unwrap().1, 180f64);
|
||||
assert_eq!(pm.get("angle", |_, s| s.parse::<i32>()).unwrap().1, 180);
|
||||
assert_eq!(
|
||||
pm.get("weight", |_, s| s.parse::<f32>()).unwrap().1,
|
||||
0.42f32
|
||||
);
|
||||
assert_eq!(
|
||||
pm.get("weight", |_, s| s.parse::<f64>()).unwrap().1,
|
||||
0.42f64
|
||||
);
|
||||
|
||||
// Error
|
||||
assert!(pm.get("length", |_, s| s.parse::<u32>()).is_err());
|
||||
assert!(pm.get("height", |_, s| s.parse::<f64>()).is_err());
|
||||
|
||||
// Missing property
|
||||
assert!(parser.parse("length=15").is_err());
|
||||
|
||||
// Defaults
|
||||
assert!(parser.parse("width=15").is_ok());
|
||||
assert_eq!(
|
||||
parser
|
||||
.parse("width=0,weight=0.15")
|
||||
.unwrap()
|
||||
.get("weight", |_, s| s.parse::<f32>())
|
||||
.unwrap()
|
||||
.1,
|
||||
0.15f32
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue