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::AuthorPos::Before;
|
||||||
use blockquote_style::BlockquoteStyle;
|
use blockquote_style::BlockquoteStyle;
|
||||||
use lsp::semantic::Semantics;
|
use lsp::semantic::Semantics;
|
||||||
use regex::Match;
|
use parser::util::escape_source;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use runtime_format::FormatArgs;
|
use runtime_format::FormatArgs;
|
||||||
use runtime_format::FormatKey;
|
use runtime_format::FormatKey;
|
||||||
|
@ -24,6 +24,8 @@ use crate::elements::paragraph::Paragraph;
|
||||||
use crate::elements::text::Text;
|
use crate::elements::text::Text;
|
||||||
use crate::parser::parser::ParseMode;
|
use crate::parser::parser::ParseMode;
|
||||||
use crate::parser::parser::ParserState;
|
use crate::parser::parser::ParserState;
|
||||||
|
use crate::parser::property::Property;
|
||||||
|
use crate::parser::property::PropertyParser;
|
||||||
use crate::parser::reports::macros::*;
|
use crate::parser::reports::macros::*;
|
||||||
use crate::parser::reports::*;
|
use crate::parser::reports::*;
|
||||||
use crate::parser::rule::Rule;
|
use crate::parser::rule::Rule;
|
||||||
|
@ -31,9 +33,6 @@ use crate::parser::source::Cursor;
|
||||||
use crate::parser::source::Token;
|
use crate::parser::source::Token;
|
||||||
use crate::parser::source::VirtualSource;
|
use crate::parser::source::VirtualSource;
|
||||||
use crate::parser::style::StyleHolder;
|
use crate::parser::style::StyleHolder;
|
||||||
use crate::parser::util::escape_text;
|
|
||||||
use crate::parser::util::Property;
|
|
||||||
use crate::parser::util::PropertyParser;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Blockquote {
|
pub struct Blockquote {
|
||||||
|
@ -185,15 +184,15 @@ impl BlockquoteRule {
|
||||||
let mut props = HashMap::new();
|
let mut props = HashMap::new();
|
||||||
props.insert(
|
props.insert(
|
||||||
"author".to_string(),
|
"author".to_string(),
|
||||||
Property::new(false, "Quote author".to_string(), None),
|
Property::new("Quote author".to_string(), None),
|
||||||
);
|
);
|
||||||
props.insert(
|
props.insert(
|
||||||
"cite".to_string(),
|
"cite".to_string(),
|
||||||
Property::new(false, "Quote source".to_string(), None),
|
Property::new("Quote source".to_string(), None),
|
||||||
);
|
);
|
||||||
props.insert(
|
props.insert(
|
||||||
"url".to_string(),
|
"url".to_string(),
|
||||||
Property::new(false, "Quote source url".to_string(), None),
|
Property::new("Quote source url".to_string(), None),
|
||||||
);
|
);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
@ -202,29 +201,6 @@ impl BlockquoteRule {
|
||||||
properties: PropertyParser { properties: props },
|
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 {
|
impl Rule for BlockquoteRule {
|
||||||
|
@ -265,23 +241,35 @@ impl Rule for BlockquoteRule {
|
||||||
end_cursor = end_cursor.at(captures.get(0).unwrap().end());
|
end_cursor = end_cursor.at(captures.get(0).unwrap().end());
|
||||||
|
|
||||||
// Properties
|
// Properties
|
||||||
let mut author = None;
|
let prop_source = escape_source(
|
||||||
let mut cite = None;
|
end_cursor.source.clone(),
|
||||||
let mut url = None;
|
captures.get(1).map_or(0..0, |m| m.range()),
|
||||||
if let Some(properties) = captures.get(1) {
|
"Blockquote Properties".into(),
|
||||||
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);
|
let properties =
|
||||||
}
|
match self
|
||||||
Ok(props) => (author, cite, url) = props,
|
.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)) =
|
if let Some((sems, tokens)) =
|
||||||
Semantics::from_source(cursor.source.clone(), &state.shared.lsp)
|
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);
|
sems.add(start..start + 1, tokens.blockquote_marker);
|
||||||
if let Some(props) = captures.get(1).map(|m| m.range()) {
|
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.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);
|
sems.add(props.end..props.end + 1, tokens.blockquote_props_sep);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ use crypto::digest::Digest;
|
||||||
use crypto::sha2::Sha512;
|
use crypto::sha2::Sha512;
|
||||||
use mlua::Function;
|
use mlua::Function;
|
||||||
use mlua::Lua;
|
use mlua::Lua;
|
||||||
|
use parser::util::escape_source;
|
||||||
use regex::Captures;
|
use regex::Captures;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use syntect::easy::HighlightLines;
|
use syntect::easy::HighlightLines;
|
||||||
|
@ -23,13 +24,12 @@ use crate::lsp::semantic::Semantics;
|
||||||
use crate::lua::kernel::CTX;
|
use crate::lua::kernel::CTX;
|
||||||
use crate::parser::parser::ParseMode;
|
use crate::parser::parser::ParseMode;
|
||||||
use crate::parser::parser::ParserState;
|
use crate::parser::parser::ParserState;
|
||||||
|
use crate::parser::property::Property;
|
||||||
|
use crate::parser::property::PropertyParser;
|
||||||
use crate::parser::reports::macros::*;
|
use crate::parser::reports::macros::*;
|
||||||
use crate::parser::reports::*;
|
use crate::parser::reports::*;
|
||||||
use crate::parser::rule::RegexRule;
|
use crate::parser::rule::RegexRule;
|
||||||
use crate::parser::source::Token;
|
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 crate::parser::util::{self};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
|
@ -291,11 +291,7 @@ impl CodeRule {
|
||||||
let mut props = HashMap::new();
|
let mut props = HashMap::new();
|
||||||
props.insert(
|
props.insert(
|
||||||
"line_offset".to_string(),
|
"line_offset".to_string(),
|
||||||
Property::new(
|
Property::new("Line number offset".to_string(), Some("1".to_string())),
|
||||||
true,
|
|
||||||
"Line number offset".to_string(),
|
|
||||||
Some("1".to_string()),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
Self {
|
Self {
|
||||||
re: [
|
re: [
|
||||||
|
@ -332,38 +328,21 @@ impl RegexRule for CodeRule {
|
||||||
) -> Vec<Report> {
|
) -> Vec<Report> {
|
||||||
let mut reports = vec![];
|
let mut reports = vec![];
|
||||||
|
|
||||||
let properties = match matches.get(1) {
|
// Properties
|
||||||
None => match self.properties.default() {
|
let prop_source = escape_source(
|
||||||
Ok(properties) => properties,
|
|
||||||
Err(e) => {
|
|
||||||
report_err!(
|
|
||||||
&mut reports,
|
|
||||||
token.source(),
|
token.source(),
|
||||||
"Invalid Code Properties".into(),
|
matches.get(1).map_or(0..0, |m| m.range()),
|
||||||
span(
|
"Code Properties".into(),
|
||||||
token.range.clone(),
|
'\\',
|
||||||
format!("Code is missing properties: {e}")
|
"]",
|
||||||
)
|
|
||||||
);
|
);
|
||||||
return reports;
|
let properties =
|
||||||
}
|
match self
|
||||||
},
|
.properties
|
||||||
Some(props) => {
|
.parse("Code", &mut reports, state, prop_source.into())
|
||||||
let processed =
|
{
|
||||||
util::escape_text('\\', "]", props.as_str().trim_start().trim_end());
|
Some(props) => props,
|
||||||
match self.properties.parse(processed.as_str()) {
|
None => return reports,
|
||||||
Err(e) => {
|
|
||||||
report_err!(
|
|
||||||
&mut reports,
|
|
||||||
token.source(),
|
|
||||||
"Invalid Code Properties".into(),
|
|
||||||
span(props.range(), e)
|
|
||||||
);
|
|
||||||
return reports;
|
|
||||||
}
|
|
||||||
Ok(properties) => properties,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let code_lang = match matches.get(2) {
|
let code_lang = match matches.get(2) {
|
||||||
|
@ -429,43 +408,11 @@ impl RegexRule for CodeRule {
|
||||||
let code_name = name.as_str().trim_end().trim_start().to_string();
|
let code_name = name.as_str().trim_end().trim_start().to_string();
|
||||||
(!code_name.is_empty()).then_some(code_name)
|
(!code_name.is_empty()).then_some(code_name)
|
||||||
});
|
});
|
||||||
let line_offset = match properties.get("line_offset", |prop, value| {
|
let line_offset = match properties.get(&mut reports, "line_offset", |_, value| {
|
||||||
value.parse::<usize>().map_err(|e| (prop, e))
|
value.value.parse::<usize>()
|
||||||
}) {
|
}) {
|
||||||
Ok((_prop, offset)) => offset,
|
Some(line_offset) => line_offset,
|
||||||
Err(e) => match e {
|
_ => return reports,
|
||||||
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;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
state.push(
|
state.push(
|
||||||
|
@ -520,7 +467,6 @@ impl RegexRule for CodeRule {
|
||||||
);
|
);
|
||||||
if let Some(props) = matches.get(1).map(|m| m.range()) {
|
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.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);
|
sems.add(props.end..props.end + 1, tokens.code_props_sep);
|
||||||
}
|
}
|
||||||
if let Some(lang) = matches.get(2).map(|m| m.range()) {
|
if let Some(lang) = matches.get(2).map(|m| m.range()) {
|
||||||
|
@ -794,8 +740,10 @@ test code
|
||||||
validate_semantics!(state, source.clone(), 0,
|
validate_semantics!(state, source.clone(), 0,
|
||||||
code_sep { delta_line == 1, delta_start == 0, length == 3 };
|
code_sep { delta_line == 1, delta_start == 0, length == 3 };
|
||||||
code_props_sep { delta_line == 0, delta_start == 3, length == 1 };
|
code_props_sep { delta_line == 0, delta_start == 3, length == 1 };
|
||||||
code_props { delta_line == 0, delta_start == 1, length == 14 };
|
prop_name { delta_line == 0, delta_start == 1, length == 11 };
|
||||||
code_props_sep { delta_line == 0, delta_start == 14, length == 1 };
|
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_lang { delta_line == 0, delta_start == 1, length == 2 };
|
||||||
code_title { delta_line == 0, delta_start == 3, length == 6 };
|
code_title { delta_line == 0, delta_start == 3, length == 6 };
|
||||||
code_content { delta_line == 1, delta_start == 0, length == 10 };
|
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 ctx = KernelContext::new(location.clone(), state, document);
|
||||||
|
|
||||||
let mut reports = vec![];
|
let mut reports = vec![];
|
||||||
kernel.run_with_context(&mut ctx, |lua| {
|
kernel.run_with_context(&mut ctx, |_lua| {
|
||||||
if let Err(err) = self.start.call::<_, ()>(())
|
if let Err(err) = self.start.call::<_, ()>(()) {
|
||||||
{
|
|
||||||
report_err!(
|
report_err!(
|
||||||
&mut reports,
|
&mut reports,
|
||||||
location.source(),
|
location.source(),
|
||||||
|
@ -71,6 +70,7 @@ impl CustomStyle for LuaCustomStyle {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
reports.extend(ctx.reports);
|
||||||
reports
|
reports
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,9 +85,8 @@ impl CustomStyle for LuaCustomStyle {
|
||||||
let mut ctx = KernelContext::new(location.clone(), state, document);
|
let mut ctx = KernelContext::new(location.clone(), state, document);
|
||||||
|
|
||||||
let mut reports = vec![];
|
let mut reports = vec![];
|
||||||
kernel.run_with_context(&mut ctx, |lua| {
|
kernel.run_with_context(&mut ctx, |_lua| {
|
||||||
if let Err(err) = self.end.call::<_, ()>(())
|
if let Err(err) = self.end.call::<_, ()>(()) {
|
||||||
{
|
|
||||||
report_err!(
|
report_err!(
|
||||||
&mut reports,
|
&mut reports,
|
||||||
location.source(),
|
location.source(),
|
||||||
|
@ -101,6 +100,7 @@ impl CustomStyle for LuaCustomStyle {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
reports.extend(ctx.reports);
|
||||||
reports
|
reports
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -344,7 +344,13 @@ impl Rule for CustomStyleRule {
|
||||||
bindings.push((
|
bindings.push((
|
||||||
"define_toggled".into(),
|
"define_toggled".into(),
|
||||||
lua.create_function(
|
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 mut result = Ok(());
|
||||||
|
|
||||||
let style = LuaCustomStyle {
|
let style = LuaCustomStyle {
|
||||||
|
|
|
@ -5,9 +5,8 @@ use std::sync::Once;
|
||||||
use crate::lua::kernel::CTX;
|
use crate::lua::kernel::CTX;
|
||||||
use crate::parser::parser::ParseMode;
|
use crate::parser::parser::ParseMode;
|
||||||
use crate::parser::parser::ParserState;
|
use crate::parser::parser::ParserState;
|
||||||
use crate::parser::util::Property;
|
use crate::parser::property::Property;
|
||||||
use crate::parser::util::PropertyMapError;
|
use crate::parser::property::PropertyParser;
|
||||||
use crate::parser::util::PropertyParser;
|
|
||||||
use ariadne::Fmt;
|
use ariadne::Fmt;
|
||||||
use crypto::digest::Digest;
|
use crypto::digest::Digest;
|
||||||
use crypto::sha2::Sha512;
|
use crypto::sha2::Sha512;
|
||||||
|
@ -18,6 +17,7 @@ use lsp::semantic::Semantics;
|
||||||
use mlua::Error::BadArgument;
|
use mlua::Error::BadArgument;
|
||||||
use mlua::Function;
|
use mlua::Function;
|
||||||
use mlua::Lua;
|
use mlua::Lua;
|
||||||
|
use parser::util::escape_source;
|
||||||
use regex::Captures;
|
use regex::Captures;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
|
@ -166,14 +166,13 @@ impl GraphRule {
|
||||||
props.insert(
|
props.insert(
|
||||||
"layout".to_string(),
|
"layout".to_string(),
|
||||||
Property::new(
|
Property::new(
|
||||||
true,
|
|
||||||
"Graphviz layout engine see <https://graphviz.org/docs/layouts/>".to_string(),
|
"Graphviz layout engine see <https://graphviz.org/docs/layouts/>".to_string(),
|
||||||
Some("dot".to_string()),
|
Some("dot".to_string()),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
props.insert(
|
props.insert(
|
||||||
"width".to_string(),
|
"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 {
|
Self {
|
||||||
re: [Regex::new(
|
re: [Regex::new(
|
||||||
|
@ -239,98 +238,31 @@ impl RegexRule for GraphRule {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Properties
|
// Properties
|
||||||
let properties = match matches.get(1) {
|
let prop_source = escape_source(
|
||||||
None => match self.properties.default() {
|
|
||||||
Ok(properties) => properties,
|
|
||||||
Err(e) => {
|
|
||||||
report_err!(
|
|
||||||
&mut reports,
|
|
||||||
token.source(),
|
token.source(),
|
||||||
"Invalid Graph Properties".into(),
|
matches.get(1).map_or(0..0, |m| m.range()),
|
||||||
span(
|
"Graphviz Properties".into(),
|
||||||
token.range.clone(),
|
'\\',
|
||||||
format!("Graph is missing property: {e}")
|
"]",
|
||||||
)
|
|
||||||
);
|
);
|
||||||
return reports;
|
let properties =
|
||||||
}
|
match self
|
||||||
},
|
.properties
|
||||||
Some(props) => {
|
.parse("Graphviz", &mut reports, state, prop_source.into())
|
||||||
let processed =
|
{
|
||||||
util::escape_text('\\', "]", props.as_str().trim_start().trim_end());
|
Some(props) => props,
|
||||||
match self.properties.parse(processed.as_str()) {
|
None => return reports,
|
||||||
Err(e) => {
|
|
||||||
report_err!(
|
|
||||||
&mut reports,
|
|
||||||
token.source(),
|
|
||||||
"Invalid Graph Properties".into(),
|
|
||||||
span(props.range(), e)
|
|
||||||
);
|
|
||||||
return reports;
|
|
||||||
}
|
|
||||||
Ok(properties) => properties,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
let (graph_layout, graph_width) = match (
|
||||||
// Property "layout"
|
properties.get(&mut reports, "layout", |_, value| {
|
||||||
let graph_layout = match properties.get("layout", |prop, value| {
|
layout_from_str(value.value.as_str())
|
||||||
layout_from_str(value.as_str()).map_err(|e| (prop, e))
|
}),
|
||||||
}) {
|
properties.get(&mut reports, "width", |_, value| {
|
||||||
Ok((_prop, kind)) => kind,
|
Result::<_, String>::Ok(value.value.clone())
|
||||||
Err(e) => match e {
|
}),
|
||||||
PropertyMapError::ParseError((prop, err)) => {
|
) {
|
||||||
report_err!(
|
(Some(graph_layout), Some(graph_width)) => (graph_layout, graph_width),
|
||||||
&mut reports,
|
_ => return 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"),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
state.push(
|
state.push(
|
||||||
|
@ -348,7 +280,6 @@ impl RegexRule for GraphRule {
|
||||||
sems.add(range.start..range.start + 7, tokens.graph_sep);
|
sems.add(range.start..range.start + 7, tokens.graph_sep);
|
||||||
if let Some(props) = matches.get(1).map(|m| m.range()) {
|
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.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(props.end..props.end + 1, tokens.graph_props_sep);
|
||||||
}
|
}
|
||||||
sems.add(matches.get(2).unwrap().range(), tokens.graph_content);
|
sems.add(matches.get(2).unwrap().range(), tokens.graph_content);
|
||||||
|
@ -490,8 +421,10 @@ digraph {
|
||||||
validate_semantics!(state, source.clone(), 0,
|
validate_semantics!(state, source.clone(), 0,
|
||||||
graph_sep { delta_line == 1, delta_start == 0, length == 7 };
|
graph_sep { delta_line == 1, delta_start == 0, length == 7 };
|
||||||
graph_props_sep { delta_line == 0, delta_start == 7, length == 1 };
|
graph_props_sep { delta_line == 0, delta_start == 7, length == 1 };
|
||||||
graph_props { delta_line == 0, delta_start == 1, length == 9 };
|
prop_name { delta_line == 0, delta_start == 1, length == 5 };
|
||||||
graph_props_sep { delta_line == 0, delta_start == 9, length == 1 };
|
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 == 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 == 10 };
|
||||||
graph_content { delta_line == 1, delta_start == 0, length == 2 };
|
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::source::Token;
|
||||||
use crate::parser::state::RuleState;
|
use crate::parser::state::RuleState;
|
||||||
use crate::parser::state::Scope;
|
use crate::parser::state::Scope;
|
||||||
use crate::parser::util::escape_text;
|
|
||||||
use ariadne::Fmt;
|
use ariadne::Fmt;
|
||||||
use mlua::Error::BadArgument;
|
use mlua::Error::BadArgument;
|
||||||
use mlua::Function;
|
use mlua::Function;
|
||||||
use mlua::Lua;
|
use mlua::Lua;
|
||||||
|
use parser::source::Source;
|
||||||
|
use parser::source::VirtualSource;
|
||||||
|
use parser::util::escape_source;
|
||||||
use regex::Captures;
|
use regex::Captures;
|
||||||
use regex::Match;
|
use regex::Match;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
@ -54,8 +56,8 @@ impl FromStr for LayoutToken {
|
||||||
|
|
||||||
mod default_layouts {
|
mod default_layouts {
|
||||||
use crate::parser::layout::LayoutType;
|
use crate::parser::layout::LayoutType;
|
||||||
use crate::parser::util::Property;
|
use crate::parser::property::Property;
|
||||||
use crate::parser::util::PropertyParser;
|
use crate::parser::property::PropertyParser;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
@ -68,7 +70,6 @@ mod default_layouts {
|
||||||
properties.insert(
|
properties.insert(
|
||||||
"style".to_string(),
|
"style".to_string(),
|
||||||
Property::new(
|
Property::new(
|
||||||
true,
|
|
||||||
"Additional style for the split".to_string(),
|
"Additional style for the split".to_string(),
|
||||||
Some("".to_string()),
|
Some("".to_string()),
|
||||||
),
|
),
|
||||||
|
@ -83,46 +84,38 @@ mod default_layouts {
|
||||||
|
|
||||||
fn expects(&self) -> Range<usize> { 1..1 }
|
fn expects(&self) -> Range<usize> { 1..1 }
|
||||||
|
|
||||||
fn parse_properties(&self, properties: &str) -> Result<Option<Box<dyn Any>>, String> {
|
fn parse_properties(
|
||||||
let props = if properties.is_empty() {
|
&self,
|
||||||
self.0.default()
|
reports: &mut Vec<Report>,
|
||||||
} else {
|
state: &ParserState,
|
||||||
self.0.parse(properties)
|
token: Token,
|
||||||
}
|
) -> Option<Box<dyn Any>> {
|
||||||
.map_err(|err| {
|
let properties = match self.0.parse("Centered Layout", reports, state, token) {
|
||||||
format!(
|
Some(props) => props,
|
||||||
"Failed to parse properties for layout {}: {err}",
|
None => return None,
|
||||||
self.name()
|
};
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let style = props
|
let style = match properties.get(reports, "style", |_, value| {
|
||||||
.get("style", |_, value| -> Result<String, ()> {
|
Result::<_, String>::Ok(value.value.clone())
|
||||||
Ok(value.clone())
|
}) {
|
||||||
})
|
Some(style) => style,
|
||||||
.map_err(|err| format!("Failed to parse style: {err:#?}"))
|
_ => return None,
|
||||||
.map(|(_, value)| value)?;
|
};
|
||||||
|
|
||||||
Ok(Some(Box::new(style)))
|
Some(Box::new(style))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compile(
|
fn compile(
|
||||||
&self,
|
&self,
|
||||||
token: LayoutToken,
|
token: LayoutToken,
|
||||||
_id: usize,
|
_id: usize,
|
||||||
properties: &Option<Box<dyn Any>>,
|
properties: &Box<dyn Any>,
|
||||||
compiler: &Compiler,
|
compiler: &Compiler,
|
||||||
_document: &dyn Document,
|
_document: &dyn Document,
|
||||||
) -> Result<String, String> {
|
) -> Result<String, String> {
|
||||||
match compiler.target() {
|
match compiler.target() {
|
||||||
Target::HTML => {
|
Target::HTML => {
|
||||||
let style = match properties
|
let style = match properties.downcast_ref::<String>().unwrap().as_str() {
|
||||||
.as_ref()
|
|
||||||
.unwrap()
|
|
||||||
.downcast_ref::<String>()
|
|
||||||
.unwrap()
|
|
||||||
.as_str()
|
|
||||||
{
|
|
||||||
"" => "".to_string(),
|
"" => "".to_string(),
|
||||||
str => format!(r#" style={}"#, Compiler::sanitize(compiler.target(), str)),
|
str => format!(r#" style={}"#, Compiler::sanitize(compiler.target(), str)),
|
||||||
};
|
};
|
||||||
|
@ -146,7 +139,6 @@ mod default_layouts {
|
||||||
properties.insert(
|
properties.insert(
|
||||||
"style".to_string(),
|
"style".to_string(),
|
||||||
Property::new(
|
Property::new(
|
||||||
true,
|
|
||||||
"Additional style for the split".to_string(),
|
"Additional style for the split".to_string(),
|
||||||
Some("".to_string()),
|
Some("".to_string()),
|
||||||
),
|
),
|
||||||
|
@ -161,46 +153,38 @@ mod default_layouts {
|
||||||
|
|
||||||
fn expects(&self) -> Range<usize> { 2..usize::MAX }
|
fn expects(&self) -> Range<usize> { 2..usize::MAX }
|
||||||
|
|
||||||
fn parse_properties(&self, properties: &str) -> Result<Option<Box<dyn Any>>, String> {
|
fn parse_properties(
|
||||||
let props = if properties.is_empty() {
|
&self,
|
||||||
self.0.default()
|
reports: &mut Vec<Report>,
|
||||||
} else {
|
state: &ParserState,
|
||||||
self.0.parse(properties)
|
token: Token,
|
||||||
}
|
) -> Option<Box<dyn Any>> {
|
||||||
.map_err(|err| {
|
let properties = match self.0.parse("Split Layout", reports, state, token) {
|
||||||
format!(
|
Some(props) => props,
|
||||||
"Failed to parse properties for layout {}: {err}",
|
None => return None,
|
||||||
self.name()
|
};
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let style = props
|
let style = match properties.get(reports, "style", |_, value| {
|
||||||
.get("style", |_, value| -> Result<String, ()> {
|
Result::<_, String>::Ok(value.value.clone())
|
||||||
Ok(value.clone())
|
}) {
|
||||||
})
|
Some(style) => style,
|
||||||
.map_err(|err| format!("Failed to parse style: {err:#?}"))
|
_ => return None,
|
||||||
.map(|(_, value)| value)?;
|
};
|
||||||
|
|
||||||
Ok(Some(Box::new(style)))
|
Some(Box::new(style))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compile(
|
fn compile(
|
||||||
&self,
|
&self,
|
||||||
token: LayoutToken,
|
token: LayoutToken,
|
||||||
_id: usize,
|
_id: usize,
|
||||||
properties: &Option<Box<dyn Any>>,
|
properties: &Box<dyn Any>,
|
||||||
compiler: &Compiler,
|
compiler: &Compiler,
|
||||||
_document: &dyn Document,
|
_document: &dyn Document,
|
||||||
) -> Result<String, String> {
|
) -> Result<String, String> {
|
||||||
match compiler.target() {
|
match compiler.target() {
|
||||||
Target::HTML => {
|
Target::HTML => {
|
||||||
let style = match properties
|
let style = match properties.downcast_ref::<String>().unwrap().as_str() {
|
||||||
.as_ref()
|
|
||||||
.unwrap()
|
|
||||||
.downcast_ref::<String>()
|
|
||||||
.unwrap()
|
|
||||||
.as_str()
|
|
||||||
{
|
|
||||||
"" => "".to_string(),
|
"" => "".to_string(),
|
||||||
str => format!(r#" style={}"#, Compiler::sanitize(compiler.target(), str)),
|
str => format!(r#" style={}"#, Compiler::sanitize(compiler.target(), str)),
|
||||||
};
|
};
|
||||||
|
@ -225,11 +209,7 @@ mod default_layouts {
|
||||||
let mut properties = HashMap::new();
|
let mut properties = HashMap::new();
|
||||||
properties.insert(
|
properties.insert(
|
||||||
"title".to_string(),
|
"title".to_string(),
|
||||||
Property::new(
|
Property::new("Spoiler title".to_string(), Some("".to_string())),
|
||||||
true,
|
|
||||||
"Spoiler title".to_string(),
|
|
||||||
Some("".to_string())
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Self(PropertyParser { properties })
|
Self(PropertyParser { properties })
|
||||||
|
@ -241,50 +221,45 @@ mod default_layouts {
|
||||||
|
|
||||||
fn expects(&self) -> Range<usize> { 1..1 }
|
fn expects(&self) -> Range<usize> { 1..1 }
|
||||||
|
|
||||||
fn parse_properties(&self, properties: &str) -> Result<Option<Box<dyn Any>>, String> {
|
fn parse_properties(
|
||||||
let props = if properties.is_empty() {
|
&self,
|
||||||
self.0.default()
|
reports: &mut Vec<Report>,
|
||||||
} else {
|
state: &ParserState,
|
||||||
self.0.parse(properties)
|
token: Token,
|
||||||
}
|
) -> Option<Box<dyn Any>> {
|
||||||
.map_err(|err| {
|
let properties = match self.0.parse("Spoiler Layout", reports, state, token) {
|
||||||
format!(
|
Some(props) => props,
|
||||||
"Failed to parse properties for layout {}: {err}",
|
None => return None,
|
||||||
self.name()
|
};
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let title = props
|
let title = match properties.get(reports, "title", |_, value| {
|
||||||
.get("title", |_, value| -> Result<String, ()> {
|
Result::<_, String>::Ok(value.value.clone())
|
||||||
Ok(value.clone())
|
}) {
|
||||||
})
|
Some(title) => title,
|
||||||
.map_err(|err| format!("Failed to parse style: {err:#?}"))
|
_ => return None,
|
||||||
.map(|(_, value)| value)?;
|
};
|
||||||
|
|
||||||
Ok(Some(Box::new(title)))
|
Some(Box::new(title))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compile(
|
fn compile(
|
||||||
&self,
|
&self,
|
||||||
token: LayoutToken,
|
token: LayoutToken,
|
||||||
_id: usize,
|
_id: usize,
|
||||||
properties: &Option<Box<dyn Any>>,
|
properties: &Box<dyn Any>,
|
||||||
compiler: &Compiler,
|
compiler: &Compiler,
|
||||||
_document: &dyn Document,
|
_document: &dyn Document,
|
||||||
) -> Result<String, String> {
|
) -> Result<String, String> {
|
||||||
match compiler.target() {
|
match compiler.target() {
|
||||||
Target::HTML => {
|
Target::HTML => {
|
||||||
let title = properties
|
let title = properties.downcast_ref::<String>().unwrap();
|
||||||
.as_ref()
|
|
||||||
.unwrap()
|
|
||||||
.downcast_ref::<String>()
|
|
||||||
.unwrap();
|
|
||||||
match token {
|
match token {
|
||||||
LayoutToken::Begin => Ok(format!(
|
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()),
|
LayoutToken::End => Ok(r#"</details>"#.to_string()),
|
||||||
_ => panic!()
|
_ => panic!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => todo!(""),
|
_ => todo!(""),
|
||||||
|
@ -299,7 +274,7 @@ struct Layout {
|
||||||
pub(self) layout: Rc<dyn LayoutType>,
|
pub(self) layout: Rc<dyn LayoutType>,
|
||||||
pub(self) id: usize,
|
pub(self) id: usize,
|
||||||
pub(self) token: LayoutToken,
|
pub(self) token: LayoutToken,
|
||||||
pub(self) properties: Option<Box<dyn Any>>,
|
pub(self) properties: Box<dyn Any>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Element for Layout {
|
impl Element for Layout {
|
||||||
|
@ -330,8 +305,11 @@ impl RuleState for LayoutState {
|
||||||
|
|
||||||
let doc_borrow = document.content().borrow();
|
let doc_borrow = document.content().borrow();
|
||||||
let at = doc_borrow.last().map_or(
|
let at = doc_borrow.last().map_or(
|
||||||
Token::new(document.source().content().len()..document.source().content().len(), document.source()),
|
Token::new(
|
||||||
|last| last.location().to_owned()
|
document.source().content().len()..document.source().content().len(),
|
||||||
|
document.source(),
|
||||||
|
),
|
||||||
|
|last| last.location().to_owned(),
|
||||||
);
|
);
|
||||||
|
|
||||||
for (tokens, layout_type) in &self.stack {
|
for (tokens, layout_type) in &self.stack {
|
||||||
|
@ -406,42 +384,23 @@ impl LayoutRule {
|
||||||
|
|
||||||
pub fn parse_properties<'a>(
|
pub fn parse_properties<'a>(
|
||||||
mut reports: &mut Vec<Report>,
|
mut reports: &mut Vec<Report>,
|
||||||
|
state: &ParserState,
|
||||||
token: &Token,
|
token: &Token,
|
||||||
layout_type: Rc<dyn LayoutType>,
|
layout_type: Rc<dyn LayoutType>,
|
||||||
properties: Option<Match>,
|
m: Option<Match>,
|
||||||
) -> Result<Option<Box<dyn Any>>, ()> {
|
) -> Option<Box<dyn Any>> {
|
||||||
match properties {
|
let prop_source = escape_source(
|
||||||
None => match layout_type.parse_properties("") {
|
|
||||||
Ok(props) => Ok(props),
|
|
||||||
Err(err) => {
|
|
||||||
report_err!(
|
|
||||||
&mut reports,
|
|
||||||
token.source(),
|
token.source(),
|
||||||
"Invalid Layout Properties".into(),
|
m.map_or(0..0, |m| m.range()),
|
||||||
span(
|
format!("Layout {} Properties", layout_type.name()),
|
||||||
token.start() + 1..token.end(),
|
'\\',
|
||||||
format!("Layout is missing required property: {err}")
|
"]",
|
||||||
|
);
|
||||||
|
layout_type.parse_properties(
|
||||||
|
reports,
|
||||||
|
state,
|
||||||
|
Token::new(0..prop_source.content().len(), prop_source),
|
||||||
)
|
)
|
||||||
);
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -546,12 +505,13 @@ impl RegexRule for LayoutRule {
|
||||||
// Parse properties
|
// Parse properties
|
||||||
let properties = match LayoutRule::parse_properties(
|
let properties = match LayoutRule::parse_properties(
|
||||||
&mut reports,
|
&mut reports,
|
||||||
|
state,
|
||||||
&token,
|
&token,
|
||||||
layout_type.clone(),
|
layout_type.clone(),
|
||||||
matches.get(1),
|
matches.get(1),
|
||||||
) {
|
) {
|
||||||
Ok(props) => props,
|
Some(props) => props,
|
||||||
Err(()) => return reports,
|
None => return reports,
|
||||||
};
|
};
|
||||||
|
|
||||||
state.push(
|
state.push(
|
||||||
|
@ -590,7 +550,6 @@ impl RegexRule for LayoutRule {
|
||||||
);
|
);
|
||||||
if let Some(props) = matches.get(1).map(|m| m.range()) {
|
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.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(props.end..props.end + 1, tokens.layout_props_sep);
|
||||||
}
|
}
|
||||||
sems.add(matches.get(2).unwrap().range(), tokens.layout_type);
|
sems.add(matches.get(2).unwrap().range(), tokens.layout_type);
|
||||||
|
@ -650,12 +609,13 @@ impl RegexRule for LayoutRule {
|
||||||
// Parse properties
|
// Parse properties
|
||||||
let properties = match LayoutRule::parse_properties(
|
let properties = match LayoutRule::parse_properties(
|
||||||
&mut reports,
|
&mut reports,
|
||||||
|
state,
|
||||||
&token,
|
&token,
|
||||||
layout_type.clone(),
|
layout_type.clone(),
|
||||||
matches.get(1),
|
matches.get(1),
|
||||||
) {
|
) {
|
||||||
Ok(props) => props,
|
Some(props) => props,
|
||||||
Err(()) => return reports,
|
None => return reports,
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some((sems, tokens)) = Semantics::from_source(token.source(), &state.shared.lsp)
|
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()) {
|
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.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(props.end..props.end + 1, tokens.layout_props_sep);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -732,12 +691,13 @@ impl RegexRule for LayoutRule {
|
||||||
// Parse properties
|
// Parse properties
|
||||||
let properties = match LayoutRule::parse_properties(
|
let properties = match LayoutRule::parse_properties(
|
||||||
&mut reports,
|
&mut reports,
|
||||||
|
state,
|
||||||
&token,
|
&token,
|
||||||
layout_type.clone(),
|
layout_type.clone(),
|
||||||
matches.get(1),
|
matches.get(1),
|
||||||
) {
|
) {
|
||||||
Ok(props) => props,
|
Some(props) => props,
|
||||||
Err(()) => return reports,
|
None => return reports,
|
||||||
};
|
};
|
||||||
|
|
||||||
let layout_type = layout_type.clone();
|
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()) {
|
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.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(props.end..props.end + 1, tokens.layout_props_sep);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -803,8 +762,8 @@ impl RegexRule for LayoutRule {
|
||||||
Ok(token) => token,
|
Ok(token) => token,
|
||||||
};
|
};
|
||||||
|
|
||||||
CTX.with_borrow(|ctx| {
|
CTX.with_borrow_mut(|ctx| {
|
||||||
ctx.as_ref().map(|ctx| {
|
ctx.as_mut().map(|ctx| {
|
||||||
// Make sure the rule state has been initialized
|
// Make sure the rule state has been initialized
|
||||||
let rule_state = LayoutRule::initialize_state(ctx.state);
|
let rule_state = LayoutRule::initialize_state(ctx.state);
|
||||||
|
|
||||||
|
@ -827,17 +786,18 @@ impl RegexRule for LayoutRule {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Parse properties
|
// Parse properties
|
||||||
let layout_properties = match layout_type.parse_properties(properties.as_str()) {
|
let prop_source = Rc::new(VirtualSource::new(ctx.location.clone(), ":LUA:Layout Properties".into(), properties)) as Rc<dyn Source>;
|
||||||
Err(err) => {
|
let layout_properties = match layout_type.parse_properties(&mut ctx.reports, ctx.state, prop_source.into()) {
|
||||||
|
None => {
|
||||||
result = Err(BadArgument {
|
result = Err(BadArgument {
|
||||||
to: Some("push".to_string()),
|
to: Some("push".to_string()),
|
||||||
pos: 3,
|
pos: 3,
|
||||||
name: Some("properties".to_string()),
|
name: Some("properties".to_string()),
|
||||||
cause: Arc::new(mlua::Error::external(err)),
|
cause: Arc::new(mlua::Error::external("Failed to parse properties")),
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
Ok(properties) => properties,
|
Some(properties) => properties,
|
||||||
};
|
};
|
||||||
|
|
||||||
let id = match layout_token {
|
let id = match layout_token {
|
||||||
|
@ -1162,8 +1122,10 @@ mod tests {
|
||||||
layout_sep { delta_line == 1, delta_start == 1, length == 2 };
|
layout_sep { delta_line == 1, delta_start == 1, length == 2 };
|
||||||
layout_token { delta_line == 0, delta_start == 2, length == 11 };
|
layout_token { delta_line == 0, delta_start == 2, length == 11 };
|
||||||
layout_props_sep { delta_line == 0, delta_start == 11, length == 1 };
|
layout_props_sep { delta_line == 0, delta_start == 11, length == 1 };
|
||||||
layout_props { delta_line == 0, delta_start == 1, length == 8 };
|
prop_name { delta_line == 0, delta_start == 1, length == 5 };
|
||||||
layout_props_sep { delta_line == 0, delta_start == 8, length == 1 };
|
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_sep { delta_line == 1, delta_start == 0, length == 2 };
|
||||||
layout_token { delta_line == 0, delta_start == 2, length == 10 };
|
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::lsp::semantic::Semantics;
|
||||||
use crate::parser::parser::ParseMode;
|
use crate::parser::parser::ParseMode;
|
||||||
use crate::parser::parser::ParserState;
|
use crate::parser::parser::ParserState;
|
||||||
|
use crate::parser::property::Property;
|
||||||
|
use crate::parser::property::PropertyParser;
|
||||||
use crate::parser::reports::macros::*;
|
use crate::parser::reports::macros::*;
|
||||||
use crate::parser::reports::Report;
|
use crate::parser::reports::Report;
|
||||||
use crate::parser::reports::*;
|
use crate::parser::reports::*;
|
||||||
|
@ -21,11 +23,7 @@ use crate::parser::source::Cursor;
|
||||||
use crate::parser::source::Token;
|
use crate::parser::source::Token;
|
||||||
use crate::parser::source::VirtualSource;
|
use crate::parser::source::VirtualSource;
|
||||||
use crate::parser::util;
|
use crate::parser::util;
|
||||||
use crate::parser::util::escape_text;
|
use parser::util::escape_source;
|
||||||
use crate::parser::util::Property;
|
|
||||||
use crate::parser::util::PropertyMapError;
|
|
||||||
use crate::parser::util::PropertyParser;
|
|
||||||
use regex::Match;
|
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||||
|
@ -137,11 +135,11 @@ impl ListRule {
|
||||||
let mut props = HashMap::new();
|
let mut props = HashMap::new();
|
||||||
props.insert(
|
props.insert(
|
||||||
"offset".to_string(),
|
"offset".to_string(),
|
||||||
Property::new(false, "Entry numbering offset".to_string(), None),
|
Property::new("Entry numbering offset".to_string(), None),
|
||||||
);
|
);
|
||||||
props.insert(
|
props.insert(
|
||||||
"bullet".to_string(),
|
"bullet".to_string(),
|
||||||
Property::new(false, "Entry bullet".to_string(), None),
|
Property::new("Entry bullet".to_string(), None),
|
||||||
);
|
);
|
||||||
|
|
||||||
Self {
|
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)> {
|
fn parse_depth(depth: &str, document: &dyn Document, offset: usize) -> Vec<(bool, usize)> {
|
||||||
let mut parsed = vec![];
|
let mut parsed = vec![];
|
||||||
let prev_entry = document
|
let prev_entry = document
|
||||||
|
@ -305,28 +281,46 @@ impl Rule for ListRule {
|
||||||
end_cursor = end_cursor.at(captures.get(0).unwrap().end());
|
end_cursor = end_cursor.at(captures.get(0).unwrap().end());
|
||||||
|
|
||||||
// Properties
|
// Properties
|
||||||
let mut offset = None;
|
let prop_source = escape_source(
|
||||||
let mut bullet = None;
|
end_cursor.source.clone(),
|
||||||
if let Some(properties) = captures.get(2) {
|
captures.get(2).map_or(0..0, |m| m.range()),
|
||||||
match self.parse_properties(properties) {
|
"List Properties".into(),
|
||||||
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 properties = match self.properties.parse(
|
||||||
}
|
"List",
|
||||||
Ok(props) => (offset, bullet) = props,
|
&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
|
// Get bullet from previous entry if it exists
|
||||||
if bullet.is_none() {
|
if bullet.is_none() {
|
||||||
bullet = document
|
(
|
||||||
|
offset,
|
||||||
|
document
|
||||||
.last_element::<ListEntry>()
|
.last_element::<ListEntry>()
|
||||||
.and_then(|prev| prev.bullet.clone())
|
.and_then(|prev| prev.bullet.clone()),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(offset, bullet)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
_ => return (end_cursor, reports),
|
||||||
|
};
|
||||||
|
|
||||||
// Depth
|
// Depth
|
||||||
let depth = ListRule::parse_depth(
|
let depth = ListRule::parse_depth(
|
||||||
|
@ -341,7 +335,6 @@ impl Rule for ListRule {
|
||||||
sems.add(captures.get(1).unwrap().range(), tokens.list_bullet);
|
sems.add(captures.get(1).unwrap().range(), tokens.list_bullet);
|
||||||
if let Some(props) = captures.get(2).map(|m| m.range()) {
|
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.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);
|
sems.add(props.end..props.end + 1, tokens.list_props_sep);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -528,8 +521,10 @@ mod tests {
|
||||||
validate_semantics!(state, source.clone(), 0,
|
validate_semantics!(state, source.clone(), 0,
|
||||||
list_bullet { delta_line == 1, delta_start == 1, length == 1 };
|
list_bullet { delta_line == 1, delta_start == 1, length == 1 };
|
||||||
list_props_sep { delta_line == 0, 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 };
|
prop_name { delta_line == 0, delta_start == 1, length == 6 };
|
||||||
list_props_sep { delta_line == 0, delta_start == 8, length == 1 };
|
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 == 8, length == 2 };
|
||||||
style_marker { delta_line == 0, delta_start == 6, length == 2 };
|
style_marker { delta_line == 0, delta_start == 6, length == 2 };
|
||||||
list_bullet { delta_line == 2, delta_start == 1, length == 2 };
|
list_bullet { delta_line == 2, delta_start == 1, length == 2 };
|
||||||
|
|
|
@ -5,7 +5,6 @@ use ariadne::Fmt;
|
||||||
use lsp::semantic::Semantics;
|
use lsp::semantic::Semantics;
|
||||||
use parser::util::escape_source;
|
use parser::util::escape_source;
|
||||||
use regex::Captures;
|
use regex::Captures;
|
||||||
use regex::Match;
|
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use regex::RegexBuilder;
|
use regex::RegexBuilder;
|
||||||
|
|
||||||
|
@ -20,16 +19,14 @@ use crate::document::element::ReferenceableElement;
|
||||||
use crate::document::references::validate_refname;
|
use crate::document::references::validate_refname;
|
||||||
use crate::parser::parser::ParseMode;
|
use crate::parser::parser::ParseMode;
|
||||||
use crate::parser::parser::ParserState;
|
use crate::parser::parser::ParserState;
|
||||||
|
use crate::parser::property::Property;
|
||||||
|
use crate::parser::property::PropertyParser;
|
||||||
use crate::parser::reports::macros::*;
|
use crate::parser::reports::macros::*;
|
||||||
use crate::parser::reports::*;
|
use crate::parser::reports::*;
|
||||||
use crate::parser::rule::RegexRule;
|
use crate::parser::rule::RegexRule;
|
||||||
use crate::parser::source::Token;
|
use crate::parser::source::Token;
|
||||||
use crate::parser::util;
|
use crate::parser::util;
|
||||||
use crate::parser::util::parse_paragraph;
|
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::paragraph::Paragraph;
|
||||||
use super::reference::InternalReference;
|
use super::reference::InternalReference;
|
||||||
|
@ -245,19 +242,15 @@ impl MediaRule {
|
||||||
let mut props = HashMap::new();
|
let mut props = HashMap::new();
|
||||||
props.insert(
|
props.insert(
|
||||||
"type".to_string(),
|
"type".to_string(),
|
||||||
Property::new(
|
Property::new("Override for the media type detection".to_string(), None),
|
||||||
false,
|
|
||||||
"Override for the media type detection".to_string(),
|
|
||||||
None,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
props.insert(
|
props.insert(
|
||||||
"width".to_string(),
|
"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(
|
props.insert(
|
||||||
"caption".to_string(),
|
"caption".to_string(),
|
||||||
Property::new(false, "Medium caption".to_string(), None),
|
Property::new("Medium caption".to_string(), None),
|
||||||
);
|
);
|
||||||
Self {
|
Self {
|
||||||
re: [RegexBuilder::new(
|
re: [RegexBuilder::new(
|
||||||
|
@ -280,47 +273,6 @@ impl MediaRule {
|
||||||
Ok(trimmed)
|
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> {
|
fn detect_filetype(filename: &str) -> Option<MediaType> {
|
||||||
let sep = match filename.rfind('.') {
|
let sep = match filename.rfind('.') {
|
||||||
Some(pos) => pos,
|
Some(pos) => pos,
|
||||||
|
@ -390,19 +342,38 @@ impl RegexRule for MediaRule {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Properties
|
// Properties
|
||||||
let properties = match self.parse_properties(&mut reports, &token, &matches.get(3)) {
|
let prop_source = escape_source(
|
||||||
Some(pm) => pm,
|
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,
|
None => return reports,
|
||||||
};
|
};
|
||||||
|
|
||||||
let media_type = match Self::detect_filetype(uri.as_str()) {
|
let (media_type, caption, width) = match (
|
||||||
Some(media_type) => media_type,
|
properties.get_opt(&mut reports, "type", |_, value| {
|
||||||
None => match properties.get("type", |prop, value| {
|
MediaType::from_str(value.value.as_str())
|
||||||
MediaType::from_str(value.as_str()).map_err(|e| (prop, e))
|
}),
|
||||||
}) {
|
properties.get_opt(&mut reports, "caption", |_, value| {
|
||||||
Ok((_prop, kind)) => kind,
|
Result::<_, String>::Ok(value.value.clone())
|
||||||
Err(e) => match e {
|
}),
|
||||||
PropertyMapError::ParseError((prop, err)) => {
|
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!(
|
report_err!(
|
||||||
&mut reports,
|
&mut reports,
|
||||||
token.source(),
|
token.source(),
|
||||||
|
@ -410,44 +381,22 @@ impl RegexRule for MediaRule {
|
||||||
span(
|
span(
|
||||||
token.start() + 1..token.end(),
|
token.start() + 1..token.end(),
|
||||||
format!(
|
format!(
|
||||||
"Property `type: {}` cannot be converted: {}",
|
"Failed to detect media type for `{}`",
|
||||||
prop.fg(state.parser.colors().info),
|
uri.fg(state.parser.colors().info)
|
||||||
err.fg(state.parser.colors().error)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
return reports;
|
return reports;
|
||||||
}
|
}
|
||||||
PropertyMapError::NotFoundError(err) => {
|
Some(media_type) => (media_type, caption, width),
|
||||||
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) {
|
if let Some((sems, tokens)) = Semantics::from_source(token.source(), &state.shared.lsp) {
|
||||||
sems.add(
|
sems.add(
|
||||||
matches.get(0).unwrap().start()..matches.get(0).unwrap().start() + 1,
|
matches.get(0).unwrap().start()..matches.get(0).unwrap().start() + 1,
|
||||||
|
@ -476,7 +425,6 @@ impl RegexRule for MediaRule {
|
||||||
// Props
|
// Props
|
||||||
if let Some(props) = matches.get(3) {
|
if let Some(props) = matches.get(3) {
|
||||||
sems.add(props.start() - 1..props.start(), tokens.media_props_sep);
|
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);
|
sems.add(props.end()..props.end() + 1, tokens.media_props_sep);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,5 +17,5 @@ pub mod section;
|
||||||
pub mod style;
|
pub mod style;
|
||||||
pub mod tex;
|
pub mod tex;
|
||||||
pub mod text;
|
pub mod text;
|
||||||
pub mod variable;
|
|
||||||
pub mod toc;
|
pub mod toc;
|
||||||
|
pub mod variable;
|
||||||
|
|
|
@ -6,18 +6,18 @@ use crate::lsp::semantic::Semantics;
|
||||||
use crate::lua::kernel::CTX;
|
use crate::lua::kernel::CTX;
|
||||||
use crate::parser::parser::ParseMode;
|
use crate::parser::parser::ParseMode;
|
||||||
use crate::parser::parser::ParserState;
|
use crate::parser::parser::ParserState;
|
||||||
|
use crate::parser::property::Property;
|
||||||
|
use crate::parser::property::PropertyParser;
|
||||||
use crate::parser::reports::macros::*;
|
use crate::parser::reports::macros::*;
|
||||||
use crate::parser::reports::*;
|
use crate::parser::reports::*;
|
||||||
use crate::parser::rule::RegexRule;
|
use crate::parser::rule::RegexRule;
|
||||||
use crate::parser::source::Token;
|
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 crate::parser::util::{self};
|
||||||
use ariadne::Fmt;
|
use ariadne::Fmt;
|
||||||
use mlua::Error::BadArgument;
|
use mlua::Error::BadArgument;
|
||||||
use mlua::Function;
|
use mlua::Function;
|
||||||
use mlua::Lua;
|
use mlua::Lua;
|
||||||
|
use parser::util::escape_source;
|
||||||
use regex::Captures;
|
use regex::Captures;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
@ -59,7 +59,6 @@ impl RawRule {
|
||||||
props.insert(
|
props.insert(
|
||||||
"kind".to_string(),
|
"kind".to_string(),
|
||||||
Property::new(
|
Property::new(
|
||||||
true,
|
|
||||||
"Element display kind".to_string(),
|
"Element display kind".to_string(),
|
||||||
Some("inline".to_string()),
|
Some("inline".to_string()),
|
||||||
),
|
),
|
||||||
|
@ -127,77 +126,28 @@ impl RegexRule for RawRule {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let properties = match matches.get(1) {
|
let prop_source = escape_source(
|
||||||
None => match self.properties.default() {
|
|
||||||
Ok(properties) => properties,
|
|
||||||
Err(e) => {
|
|
||||||
report_err!(
|
|
||||||
&mut reports,
|
|
||||||
token.source(),
|
token.source(),
|
||||||
"Invalid Raw Code".into(),
|
matches.get(1).map_or(0..0, |m| m.range()),
|
||||||
span(
|
"Raw Properties".into(),
|
||||||
token.range.clone(),
|
'\\',
|
||||||
format!("Raw code is missing properties: {e}")
|
"]",
|
||||||
)
|
|
||||||
);
|
);
|
||||||
return reports;
|
let properties = match self.properties.parse(
|
||||||
}
|
"Raw Code",
|
||||||
},
|
|
||||||
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,
|
&mut reports,
|
||||||
token.source(),
|
state,
|
||||||
"Invalid Raw Code Properties".into(),
|
Token::new(0..prop_source.content().len(), prop_source),
|
||||||
span(props.range(), e)
|
) {
|
||||||
);
|
Some(props) => props,
|
||||||
return reports;
|
None => return reports,
|
||||||
}
|
|
||||||
Ok(properties) => properties,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let raw_kind: ElemKind = match properties.get("kind", |prop, value| {
|
let raw_kind = match properties.get(&mut reports, "kind", |_, value| {
|
||||||
ElemKind::from_str(value.as_str()).map_err(|e| (prop, e))
|
ElemKind::from_str(value.value.as_str())
|
||||||
}) {
|
}) {
|
||||||
Ok((_prop, kind)) => kind,
|
None => return reports,
|
||||||
Err(e) => match e {
|
Some(raw_kind) => raw_kind,
|
||||||
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;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
state.push(
|
state.push(
|
||||||
|
@ -214,7 +164,6 @@ impl RegexRule for RawRule {
|
||||||
sems.add(range.start..range.start + 2, tokens.raw_sep);
|
sems.add(range.start..range.start + 2, tokens.raw_sep);
|
||||||
if let Some(props) = matches.get(1).map(|m| m.range()) {
|
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.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(props.end..props.end + 1, tokens.raw_props_sep);
|
||||||
}
|
}
|
||||||
sems.add(matches.get(2).unwrap().range(), tokens.raw_content);
|
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,
|
validate_semantics!(state, source.clone(), 0,
|
||||||
raw_sep { delta_line == 1, delta_start == 0, length == 2 };
|
raw_sep { delta_line == 1, delta_start == 0, length == 2 };
|
||||||
raw_props_sep { delta_line == 0, delta_start == 2, length == 1 };
|
raw_props_sep { delta_line == 0, delta_start == 2, length == 1 };
|
||||||
raw_props { delta_line == 0, delta_start == 1, length == 10 };
|
prop_name { delta_line == 0, delta_start == 1, length == 4 };
|
||||||
raw_props_sep { delta_line == 0, delta_start == 10, length == 1 };
|
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_content { delta_line == 0, delta_start == 1, length == 4 };
|
||||||
raw_sep { delta_line == 0, delta_start == 4, length == 2 };
|
raw_sep { delta_line == 0, delta_start == 4, length == 2 };
|
||||||
raw_sep { delta_line == 1, delta_start == 0, length == 2 };
|
raw_sep { delta_line == 1, delta_start == 0, length == 2 };
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use parser::util::escape_source;
|
||||||
use reference_style::ExternalReferenceStyle;
|
use reference_style::ExternalReferenceStyle;
|
||||||
use regex::Captures;
|
use regex::Captures;
|
||||||
use regex::Match;
|
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use runtime_format::FormatArgs;
|
use runtime_format::FormatArgs;
|
||||||
use runtime_format::FormatKey;
|
use runtime_format::FormatKey;
|
||||||
|
@ -19,15 +19,13 @@ use crate::document::references::validate_refname;
|
||||||
use crate::lsp::semantic::Semantics;
|
use crate::lsp::semantic::Semantics;
|
||||||
use crate::parser::parser::ParseMode;
|
use crate::parser::parser::ParseMode;
|
||||||
use crate::parser::parser::ParserState;
|
use crate::parser::parser::ParserState;
|
||||||
|
use crate::parser::property::Property;
|
||||||
|
use crate::parser::property::PropertyParser;
|
||||||
use crate::parser::reports::macros::*;
|
use crate::parser::reports::macros::*;
|
||||||
use crate::parser::reports::*;
|
use crate::parser::reports::*;
|
||||||
use crate::parser::rule::RegexRule;
|
use crate::parser::rule::RegexRule;
|
||||||
use crate::parser::source::Token;
|
use crate::parser::source::Token;
|
||||||
use crate::parser::style::StyleHolder;
|
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)]
|
#[derive(Debug)]
|
||||||
pub struct InternalReference {
|
pub struct InternalReference {
|
||||||
|
@ -168,58 +166,13 @@ impl ReferenceRule {
|
||||||
let mut props = HashMap::new();
|
let mut props = HashMap::new();
|
||||||
props.insert(
|
props.insert(
|
||||||
"caption".to_string(),
|
"caption".to_string(),
|
||||||
Property::new(
|
Property::new("Override the display of the reference".to_string(), None),
|
||||||
false,
|
|
||||||
"Override the display of the reference".to_string(),
|
|
||||||
None,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
Self {
|
Self {
|
||||||
re: [Regex::new(r"&\{(.*?)\}(?:\[((?:\\.|[^\\\\])*?)\])?").unwrap()],
|
re: [Regex::new(r"&\{(.*?)\}(?:\[((?:\\.|[^\\\\])*?)\])?").unwrap()],
|
||||||
properties: PropertyParser { properties: props },
|
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 {
|
impl RegexRule for ReferenceRule {
|
||||||
|
@ -280,17 +233,29 @@ impl RegexRule for ReferenceRule {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Properties
|
// Properties
|
||||||
let properties = match self.parse_properties(&mut reports, &token, &matches.get(2)) {
|
let prop_source = escape_source(
|
||||||
Some(pm) => pm,
|
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,
|
None => return reports,
|
||||||
};
|
};
|
||||||
|
|
||||||
let caption = properties
|
let caption = match properties.get_opt(&mut reports, "caption", |_, value| {
|
||||||
.get("caption", |_, value| -> Result<String, ()> {
|
Result::<_, String>::Ok(value.value.clone())
|
||||||
Ok(value.clone())
|
}) {
|
||||||
})
|
Some(caption) => caption,
|
||||||
.ok()
|
None => return reports,
|
||||||
.map(|(_, s)| s);
|
};
|
||||||
|
|
||||||
if let Some(refdoc) = refdoc {
|
if let Some(refdoc) = refdoc {
|
||||||
// Get style
|
// Get style
|
||||||
|
@ -370,7 +335,6 @@ impl RegexRule for ReferenceRule {
|
||||||
matches.get(2).map(|m| m.range()),
|
matches.get(2).map(|m| m.range()),
|
||||||
) {
|
) {
|
||||||
sems.add(props.start - 1..props.start, tokens.reference_props_sep);
|
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);
|
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);
|
hints.add(matches.get(0).unwrap().end(), result);
|
||||||
}
|
}
|
||||||
} else if kind == 2
|
} else if kind == 2
|
||||||
// Eval and Parse
|
// 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());
|
hints.add(matches.get(0).unwrap().end(), result.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -229,7 +233,6 @@ impl RegexRule for ScriptRule {
|
||||||
result,
|
result,
|
||||||
)) as Rc<dyn Source>;
|
)) as Rc<dyn Source>;
|
||||||
|
|
||||||
|
|
||||||
state.with_state(|new_state| {
|
state.with_state(|new_state| {
|
||||||
new_state.parser.parse_into(
|
new_state.parser.parse_into(
|
||||||
new_state,
|
new_state,
|
||||||
|
@ -295,6 +298,7 @@ impl RegexRule for ScriptRule {
|
||||||
sems.add(range.end - 2..range.end, tokens.script_sep);
|
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) {
|
if let Some(hints) = Hints::from_source(token.source(), &state.shared.lsp) {
|
||||||
let mut label = String::new();
|
let mut label = String::new();
|
||||||
ctx.redirects.iter().for_each(|redir| {
|
ctx.redirects.iter().for_each(|redir| {
|
||||||
|
@ -305,6 +309,8 @@ impl RegexRule for ScriptRule {
|
||||||
hints.add(matches.get(0).unwrap().end(), label);
|
hints.add(matches.get(0).unwrap().end(), label);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Process reports
|
||||||
reports
|
reports
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,8 +12,8 @@ use crypto::digest::Digest;
|
||||||
use crypto::sha2::Sha512;
|
use crypto::sha2::Sha512;
|
||||||
use mlua::Function;
|
use mlua::Function;
|
||||||
use mlua::Lua;
|
use mlua::Lua;
|
||||||
|
use parser::util::escape_source;
|
||||||
use regex::Captures;
|
use regex::Captures;
|
||||||
use regex::Match;
|
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
use crate::cache::cache::Cached;
|
use crate::cache::cache::Cached;
|
||||||
|
@ -27,15 +27,13 @@ use crate::lsp::semantic::Semantics;
|
||||||
use crate::lua::kernel::CTX;
|
use crate::lua::kernel::CTX;
|
||||||
use crate::parser::parser::ParseMode;
|
use crate::parser::parser::ParseMode;
|
||||||
use crate::parser::parser::ParserState;
|
use crate::parser::parser::ParserState;
|
||||||
|
use crate::parser::property::Property;
|
||||||
|
use crate::parser::property::PropertyParser;
|
||||||
use crate::parser::reports::macros::*;
|
use crate::parser::reports::macros::*;
|
||||||
use crate::parser::reports::*;
|
use crate::parser::reports::*;
|
||||||
use crate::parser::rule::RegexRule;
|
use crate::parser::rule::RegexRule;
|
||||||
use crate::parser::source::Token;
|
use crate::parser::source::Token;
|
||||||
use crate::parser::util;
|
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)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
enum TexKind {
|
enum TexKind {
|
||||||
|
@ -235,19 +233,15 @@ impl TexRule {
|
||||||
let mut props = HashMap::new();
|
let mut props = HashMap::new();
|
||||||
props.insert(
|
props.insert(
|
||||||
"env".to_string(),
|
"env".to_string(),
|
||||||
Property::new(
|
Property::new("Tex environment".to_string(), Some("main".to_string())),
|
||||||
true,
|
|
||||||
"Tex environment".to_string(),
|
|
||||||
Some("main".to_string()),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
props.insert(
|
props.insert(
|
||||||
"kind".to_string(),
|
"kind".to_string(),
|
||||||
Property::new(false, "Element display kind".to_string(), None),
|
Property::new("Element display kind".to_string(), None),
|
||||||
);
|
);
|
||||||
props.insert(
|
props.insert(
|
||||||
"caption".to_string(),
|
"caption".to_string(),
|
||||||
Property::new(false, "Latex caption".to_string(), None),
|
Property::new("Latex caption".to_string(), None),
|
||||||
);
|
);
|
||||||
Self {
|
Self {
|
||||||
re: [
|
re: [
|
||||||
|
@ -258,47 +252,6 @@ impl TexRule {
|
||||||
properties: PropertyParser { properties: props },
|
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 {
|
impl RegexRule for TexRule {
|
||||||
|
@ -358,67 +311,52 @@ impl RegexRule for TexRule {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Properties
|
// Properties
|
||||||
let properties = match self.parse_properties(&mut reports, &token, &matches.get(1)) {
|
let prop_source = escape_source(
|
||||||
Some(pm) => pm,
|
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,
|
None => return reports,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Tex kind
|
let (tex_kind, caption, tex_env) = match (
|
||||||
let tex_kind = match properties.get("kind", |prop, value| {
|
properties.get_or(
|
||||||
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,
|
&mut reports,
|
||||||
token.source(),
|
"kind",
|
||||||
"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 {
|
if index == 1 {
|
||||||
TexKind::Inline
|
TexKind::Inline
|
||||||
} else {
|
} else {
|
||||||
TexKind::Block
|
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(
|
state.push(
|
||||||
document,
|
document,
|
||||||
Box::new(Tex {
|
Box::new(Tex {
|
||||||
mathmode: index == 1,
|
mathmode: index == 1,
|
||||||
location: token.clone(),
|
location: token.clone(),
|
||||||
kind: tex_kind,
|
kind: tex_kind,
|
||||||
env: tex_env.to_string(),
|
env: tex_env,
|
||||||
tex: tex_content,
|
tex: tex_content,
|
||||||
caption,
|
caption,
|
||||||
}),
|
}),
|
||||||
|
@ -432,7 +370,6 @@ impl RegexRule for TexRule {
|
||||||
);
|
);
|
||||||
if let Some(props) = matches.get(1).map(|m| m.range()) {
|
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.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(props.end..props.end + 1, tokens.tex_props_sep);
|
||||||
}
|
}
|
||||||
sems.add(matches.get(2).unwrap().range(), tokens.tex_content);
|
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,
|
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 == false, tex == "Non Math \\LaTeX", env == "another" };
|
||||||
Tex { mathmode == true, tex == "e^{i\\pi}=-1", 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()) };
|
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,
|
validate_document!(doc.content().borrow(), 0,
|
||||||
Paragraph {
|
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 == 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 == "e^{i\\pi}=-1", env == "another" };
|
||||||
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()) };
|
||||||
|
@ -634,8 +571,10 @@ $[kind=inline]\LaTeX$
|
||||||
validate_semantics!(state, source.clone(), 0,
|
validate_semantics!(state, source.clone(), 0,
|
||||||
tex_sep { delta_line == 1, delta_start == 0, length == 1 };
|
tex_sep { delta_line == 1, delta_start == 0, length == 1 };
|
||||||
tex_props_sep { delta_line == 0, delta_start == 1, length == 1 };
|
tex_props_sep { delta_line == 0, delta_start == 1, length == 1 };
|
||||||
tex_props { delta_line == 0, delta_start == 1, length == 11 };
|
prop_name { delta_line == 0, delta_start == 1, length == 4 };
|
||||||
tex_props_sep { delta_line == 0, delta_start == 11, length == 1 };
|
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_content { delta_line == 0, delta_start == 1, length == 6 };
|
||||||
tex_sep { delta_line == 0, delta_start == 6, length == 1 };
|
tex_sep { delta_line == 0, delta_start == 6, length == 1 };
|
||||||
);
|
);
|
||||||
|
|
|
@ -96,6 +96,11 @@ pub struct Tokens {
|
||||||
pub section_kind: (u32, u32),
|
pub section_kind: (u32, u32),
|
||||||
pub section_name: (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 comment: (u32, u32),
|
||||||
|
|
||||||
pub link_display_sep: (u32, u32),
|
pub link_display_sep: (u32, u32),
|
||||||
|
@ -117,7 +122,6 @@ pub struct Tokens {
|
||||||
pub reference_doc: (u32, u32),
|
pub reference_doc: (u32, u32),
|
||||||
pub reference_link: (u32, u32),
|
pub reference_link: (u32, u32),
|
||||||
pub reference_props_sep: (u32, u32),
|
pub reference_props_sep: (u32, u32),
|
||||||
pub reference_props: (u32, u32),
|
|
||||||
|
|
||||||
pub variable_operator: (u32, u32),
|
pub variable_operator: (u32, u32),
|
||||||
pub variable_kind: (u32, u32),
|
pub variable_kind: (u32, u32),
|
||||||
|
@ -135,7 +139,6 @@ pub struct Tokens {
|
||||||
|
|
||||||
pub code_sep: (u32, u32),
|
pub code_sep: (u32, u32),
|
||||||
pub code_props_sep: (u32, u32),
|
pub code_props_sep: (u32, u32),
|
||||||
pub code_props: (u32, u32),
|
|
||||||
pub code_lang: (u32, u32),
|
pub code_lang: (u32, u32),
|
||||||
pub code_title: (u32, u32),
|
pub code_title: (u32, u32),
|
||||||
pub code_content: (u32, u32),
|
pub code_content: (u32, u32),
|
||||||
|
@ -148,31 +151,25 @@ pub struct Tokens {
|
||||||
|
|
||||||
pub list_bullet: (u32, u32),
|
pub list_bullet: (u32, u32),
|
||||||
pub list_props_sep: (u32, u32),
|
pub list_props_sep: (u32, u32),
|
||||||
pub list_props: (u32, u32),
|
|
||||||
|
|
||||||
pub blockquote_marker: (u32, u32),
|
pub blockquote_marker: (u32, u32),
|
||||||
pub blockquote_props_sep: (u32, u32),
|
pub blockquote_props_sep: (u32, u32),
|
||||||
pub blockquote_props: (u32, u32),
|
|
||||||
|
|
||||||
pub raw_sep: (u32, u32),
|
pub raw_sep: (u32, u32),
|
||||||
pub raw_props_sep: (u32, u32),
|
pub raw_props_sep: (u32, u32),
|
||||||
pub raw_props: (u32, u32),
|
|
||||||
pub raw_content: (u32, u32),
|
pub raw_content: (u32, u32),
|
||||||
|
|
||||||
pub tex_sep: (u32, u32),
|
pub tex_sep: (u32, u32),
|
||||||
pub tex_props_sep: (u32, u32),
|
pub tex_props_sep: (u32, u32),
|
||||||
pub tex_props: (u32, u32),
|
|
||||||
pub tex_content: (u32, u32),
|
pub tex_content: (u32, u32),
|
||||||
|
|
||||||
pub graph_sep: (u32, u32),
|
pub graph_sep: (u32, u32),
|
||||||
pub graph_props_sep: (u32, u32),
|
pub graph_props_sep: (u32, u32),
|
||||||
pub graph_props: (u32, u32),
|
|
||||||
pub graph_content: (u32, u32),
|
pub graph_content: (u32, u32),
|
||||||
|
|
||||||
pub layout_sep: (u32, u32),
|
pub layout_sep: (u32, u32),
|
||||||
pub layout_token: (u32, u32),
|
pub layout_token: (u32, u32),
|
||||||
pub layout_props_sep: (u32, u32),
|
pub layout_props_sep: (u32, u32),
|
||||||
pub layout_props: (u32, u32),
|
|
||||||
pub layout_type: (u32, u32),
|
pub layout_type: (u32, u32),
|
||||||
|
|
||||||
pub toc_sep: (u32, u32),
|
pub toc_sep: (u32, u32),
|
||||||
|
@ -185,7 +182,6 @@ pub struct Tokens {
|
||||||
pub media_uri_sep: (u32, u32),
|
pub media_uri_sep: (u32, u32),
|
||||||
pub media_uri: (u32, u32),
|
pub media_uri: (u32, u32),
|
||||||
pub media_props_sep: (u32, u32),
|
pub media_props_sep: (u32, u32),
|
||||||
pub media_props: (u32, u32),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Tokens {
|
impl Tokens {
|
||||||
|
@ -196,6 +192,11 @@ impl Tokens {
|
||||||
section_kind: token!("enum"),
|
section_kind: token!("enum"),
|
||||||
section_name: token!("string"),
|
section_name: token!("string"),
|
||||||
|
|
||||||
|
prop_equal: token!("operator"),
|
||||||
|
prop_comma: token!("operator"),
|
||||||
|
prop_name: token!("class"),
|
||||||
|
prop_value: token!("enum"),
|
||||||
|
|
||||||
comment: token!("comment"),
|
comment: token!("comment"),
|
||||||
|
|
||||||
link_display_sep: token!("macro"),
|
link_display_sep: token!("macro"),
|
||||||
|
@ -217,7 +218,6 @@ impl Tokens {
|
||||||
reference_doc: token!("function"),
|
reference_doc: token!("function"),
|
||||||
reference_link: token!("macro"),
|
reference_link: token!("macro"),
|
||||||
reference_props_sep: token!("operator"),
|
reference_props_sep: token!("operator"),
|
||||||
reference_props: token!("enum"),
|
|
||||||
|
|
||||||
variable_operator: token!("operator"),
|
variable_operator: token!("operator"),
|
||||||
variable_kind: token!("operator"),
|
variable_kind: token!("operator"),
|
||||||
|
@ -235,7 +235,6 @@ impl Tokens {
|
||||||
|
|
||||||
code_sep: token!("operator"),
|
code_sep: token!("operator"),
|
||||||
code_props_sep: token!("operator"),
|
code_props_sep: token!("operator"),
|
||||||
code_props: token!("enum"),
|
|
||||||
code_lang: token!("function"),
|
code_lang: token!("function"),
|
||||||
code_title: token!("number"),
|
code_title: token!("number"),
|
||||||
code_content: token!("string"),
|
code_content: token!("string"),
|
||||||
|
@ -248,31 +247,25 @@ impl Tokens {
|
||||||
|
|
||||||
list_bullet: token!("macro"),
|
list_bullet: token!("macro"),
|
||||||
list_props_sep: token!("operator"),
|
list_props_sep: token!("operator"),
|
||||||
list_props: token!("enum"),
|
|
||||||
|
|
||||||
blockquote_marker: token!("macro"),
|
blockquote_marker: token!("macro"),
|
||||||
blockquote_props_sep: token!("operator"),
|
blockquote_props_sep: token!("operator"),
|
||||||
blockquote_props: token!("enum"),
|
|
||||||
|
|
||||||
raw_sep: token!("operator"),
|
raw_sep: token!("operator"),
|
||||||
raw_props_sep: token!("operator"),
|
raw_props_sep: token!("operator"),
|
||||||
raw_props: token!("enum"),
|
|
||||||
raw_content: token!("string"),
|
raw_content: token!("string"),
|
||||||
|
|
||||||
tex_sep: token!("modifier"),
|
tex_sep: token!("modifier"),
|
||||||
tex_props_sep: token!("operator"),
|
tex_props_sep: token!("operator"),
|
||||||
tex_props: token!("enum"),
|
|
||||||
tex_content: token!("string"),
|
tex_content: token!("string"),
|
||||||
|
|
||||||
graph_sep: token!("modifier"),
|
graph_sep: token!("modifier"),
|
||||||
graph_props_sep: token!("operator"),
|
graph_props_sep: token!("operator"),
|
||||||
graph_props: token!("enum"),
|
|
||||||
graph_content: token!("string"),
|
graph_content: token!("string"),
|
||||||
|
|
||||||
layout_sep: token!("number"),
|
layout_sep: token!("number"),
|
||||||
layout_token: token!("number"),
|
layout_token: token!("number"),
|
||||||
layout_props_sep: token!("operator"),
|
layout_props_sep: token!("operator"),
|
||||||
layout_props: token!("enum"),
|
|
||||||
layout_type: token!("function"),
|
layout_type: token!("function"),
|
||||||
|
|
||||||
toc_sep: token!("number"),
|
toc_sep: token!("number"),
|
||||||
|
@ -285,7 +278,6 @@ impl Tokens {
|
||||||
media_uri_sep: token!("macro"),
|
media_uri_sep: token!("macro"),
|
||||||
media_uri: token!("function"),
|
media_uri: token!("function"),
|
||||||
media_props_sep: token!("operator"),
|
media_props_sep: token!("operator"),
|
||||||
media_props: token!("enum"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ use mlua::Lua;
|
||||||
use crate::document::document::Document;
|
use crate::document::document::Document;
|
||||||
use crate::parser::parser::Parser;
|
use crate::parser::parser::Parser;
|
||||||
use crate::parser::parser::ParserState;
|
use crate::parser::parser::ParserState;
|
||||||
|
use crate::parser::reports::Report;
|
||||||
use crate::parser::source::Token;
|
use crate::parser::source::Token;
|
||||||
|
|
||||||
/// Redirected data from lua execution
|
/// Redirected data from lua execution
|
||||||
|
@ -21,6 +22,7 @@ pub struct KernelContext<'a, 'b, 'c> {
|
||||||
pub state: &'a ParserState<'a, 'b>,
|
pub state: &'a ParserState<'a, 'b>,
|
||||||
pub document: &'c dyn Document<'c>,
|
pub document: &'c dyn Document<'c>,
|
||||||
pub redirects: Vec<KernelRedirect>,
|
pub redirects: Vec<KernelRedirect>,
|
||||||
|
pub reports: Vec<Report>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b, 'c> KernelContext<'a, 'b, 'c> {
|
impl<'a, 'b, 'c> KernelContext<'a, 'b, 'c> {
|
||||||
|
@ -34,6 +36,7 @@ impl<'a, 'b, 'c> KernelContext<'a, 'b, 'c> {
|
||||||
state,
|
state,
|
||||||
document,
|
document,
|
||||||
redirects: vec![],
|
redirects: vec![],
|
||||||
|
reports: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,12 +105,11 @@ impl<'b> Parser for LangParser<'b> {
|
||||||
.downcast_rc::<SourceFile>()
|
.downcast_rc::<SourceFile>()
|
||||||
.ok()
|
.ok()
|
||||||
.map(|source| {
|
.map(|source| {
|
||||||
if source.path().is_empty() // Test mode
|
if source.path().is_empty()
|
||||||
|
// Test mode
|
||||||
{
|
{
|
||||||
None
|
None
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
let start = if source.path().starts_with("file:///") {
|
let start = if source.path().starts_with("file:///") {
|
||||||
7
|
7
|
||||||
} else {
|
} else {
|
||||||
|
@ -119,7 +118,9 @@ impl<'b> Parser for LangParser<'b> {
|
||||||
let mut path = PathBuf::from(&source.path()[start..]);
|
let mut path = PathBuf::from(&source.path()[start..]);
|
||||||
match path.canonicalize() {
|
match path.canonicalize() {
|
||||||
Ok(cano) => path = cano,
|
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();
|
path.pop();
|
||||||
Some(path)
|
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) {
|
if let Err(err) = std::env::set_current_dir(¤t_dir) {
|
||||||
println!(
|
println!(
|
||||||
"Failed to set working directory to `{}`: {err} {source:#?}",
|
"Failed to set working directory to `{}`: {err} {source:#?}",
|
||||||
|
|
|
@ -7,13 +7,22 @@ use crate::compiler::compiler::Compiler;
|
||||||
use crate::document::document::Document;
|
use crate::document::document::Document;
|
||||||
use crate::elements::layout::LayoutToken;
|
use crate::elements::layout::LayoutToken;
|
||||||
|
|
||||||
|
use super::parser::ParserState;
|
||||||
|
use super::reports::Report;
|
||||||
|
use super::source::Token;
|
||||||
|
|
||||||
/// Represents the type of a layout
|
/// Represents the type of a layout
|
||||||
pub trait LayoutType: core::fmt::Debug {
|
pub trait LayoutType: core::fmt::Debug {
|
||||||
/// Name of the layout
|
/// Name of the layout
|
||||||
fn name(&self) -> &'static str;
|
fn name(&self) -> &'static str;
|
||||||
|
|
||||||
/// Parses layout properties
|
/// 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
|
/// Expected number of blocks
|
||||||
fn expects(&self) -> Range<usize>;
|
fn expects(&self) -> Range<usize>;
|
||||||
|
@ -23,7 +32,7 @@ pub trait LayoutType: core::fmt::Debug {
|
||||||
&self,
|
&self,
|
||||||
token: LayoutToken,
|
token: LayoutToken,
|
||||||
id: usize,
|
id: usize,
|
||||||
properties: &Option<Box<dyn Any>>,
|
properties: &Box<dyn Any>,
|
||||||
compiler: &Compiler,
|
compiler: &Compiler,
|
||||||
document: &dyn Document,
|
document: &dyn Document,
|
||||||
) -> Result<String, String>;
|
) -> Result<String, String>;
|
||||||
|
|
|
@ -2,6 +2,7 @@ pub mod customstyle;
|
||||||
pub mod langparser;
|
pub mod langparser;
|
||||||
pub mod layout;
|
pub mod layout;
|
||||||
pub mod parser;
|
pub mod parser;
|
||||||
|
pub mod property;
|
||||||
pub mod reports;
|
pub mod reports;
|
||||||
pub mod rule;
|
pub mod rule;
|
||||||
pub mod source;
|
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 }
|
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::ops::Range;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
@ -233,226 +232,6 @@ pub fn parse_paragraph<'a>(
|
||||||
Ok(paragraph.downcast::<Paragraph>().unwrap())
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
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(),);
|
||||||
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