Layout refactor & bindings
This commit is contained in:
parent
cc7bc7169c
commit
67ea2f9411
11 changed files with 533 additions and 105 deletions
|
@ -216,7 +216,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn sort() {
|
fn sort() {
|
||||||
let mut entries: Vec<(String, String, Option<String>)> = vec![
|
let entries: Vec<(String, String, Option<String>)> = vec![
|
||||||
("Index".into(), "".into(), None),
|
("Index".into(), "".into(), None),
|
||||||
("AB".into(), "".into(), Some("Index".into())),
|
("AB".into(), "".into(), Some("Index".into())),
|
||||||
("Getting Started".into(), "".into(), Some("Index".into())),
|
("Getting Started".into(), "".into(), Some("Index".into())),
|
||||||
|
|
49
src/document/layout.rs
Normal file
49
src/document/layout.rs
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
use std::any::Any;
|
||||||
|
use std::cell::Ref;
|
||||||
|
use std::cell::RefMut;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::ops::Range;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use crate::compiler::compiler::Compiler;
|
||||||
|
use crate::elements::layout::LayoutToken;
|
||||||
|
|
||||||
|
use super::document::Document;
|
||||||
|
|
||||||
|
/// Represents the type of a layout
|
||||||
|
pub trait LayoutType: core::fmt::Debug {
|
||||||
|
/// Name of the layout
|
||||||
|
fn name(&self) -> &'static str;
|
||||||
|
|
||||||
|
/// Parses layout properties
|
||||||
|
fn parse_properties(&self, properties: &str) -> Result<Option<Box<dyn Any>>, String>;
|
||||||
|
|
||||||
|
/// Expected number of blocks
|
||||||
|
fn expects(&self) -> Range<usize>;
|
||||||
|
|
||||||
|
/// Compile layout
|
||||||
|
fn compile(
|
||||||
|
&self,
|
||||||
|
token: LayoutToken,
|
||||||
|
id: usize,
|
||||||
|
properties: &Option<Box<dyn Any>>,
|
||||||
|
compiler: &Compiler,
|
||||||
|
document: &dyn Document,
|
||||||
|
) -> Result<String, String>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait LayoutHolder {
|
||||||
|
/// gets a reference to all defined layouts
|
||||||
|
fn layouts(&self) -> Ref<'_, HashMap<String, Rc<dyn LayoutType>>>;
|
||||||
|
|
||||||
|
/// gets a (mutable) reference to all defined layours
|
||||||
|
fn layouts_mut(&self) -> RefMut<'_, HashMap<String, Rc<dyn LayoutType>>>;
|
||||||
|
|
||||||
|
fn get_layout(&self, layout_name: &str) -> Option<Rc<dyn LayoutType>> {
|
||||||
|
self.layouts().get(layout_name).map(|layout| layout.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert_layout(&self, layout: Rc<dyn LayoutType>) {
|
||||||
|
self.layouts_mut().insert(layout.name().into(), layout);
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,3 +4,4 @@ pub mod langdocument;
|
||||||
pub mod element;
|
pub mod element;
|
||||||
pub mod variable;
|
pub mod variable;
|
||||||
pub mod style;
|
pub mod style;
|
||||||
|
pub mod layout;
|
||||||
|
|
|
@ -3,6 +3,8 @@ use crate::compiler::compiler::Target;
|
||||||
use crate::document::document::Document;
|
use crate::document::document::Document;
|
||||||
use crate::document::element::ElemKind;
|
use crate::document::element::ElemKind;
|
||||||
use crate::document::element::Element;
|
use crate::document::element::Element;
|
||||||
|
use crate::document::layout::LayoutType;
|
||||||
|
use crate::lua::kernel::CTX;
|
||||||
use crate::parser::parser::Parser;
|
use crate::parser::parser::Parser;
|
||||||
use crate::parser::parser::ReportColors;
|
use crate::parser::parser::ReportColors;
|
||||||
use crate::parser::rule::RegexRule;
|
use crate::parser::rule::RegexRule;
|
||||||
|
@ -16,6 +18,7 @@ use ariadne::Label;
|
||||||
use ariadne::Report;
|
use ariadne::Report;
|
||||||
use ariadne::ReportKind;
|
use ariadne::ReportKind;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
|
use mlua::Error::BadArgument;
|
||||||
use mlua::Function;
|
use mlua::Function;
|
||||||
use mlua::Lua;
|
use mlua::Lua;
|
||||||
use regex::Captures;
|
use regex::Captures;
|
||||||
|
@ -27,6 +30,8 @@ use std::cell::RefCell;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub(crate) enum LayoutToken {
|
pub(crate) enum LayoutToken {
|
||||||
|
@ -35,31 +40,20 @@ pub(crate) enum LayoutToken {
|
||||||
End,
|
End,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents the type of a layout
|
impl FromStr for LayoutToken {
|
||||||
pub trait LayoutType: core::fmt::Debug {
|
type Err = String;
|
||||||
/// Name of the layout
|
|
||||||
fn name(&self) -> &'static str;
|
|
||||||
|
|
||||||
/// Parses layout properties
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
fn parse_properties(&self, properties: &str) -> Result<Option<Box<dyn Any>>, String>;
|
match s {
|
||||||
|
"Begin" | "begin" => Ok(LayoutToken::Begin),
|
||||||
/// Expected number of blocks
|
"Next" | "next" => Ok(LayoutToken::Next),
|
||||||
fn expects(&self) -> Range<usize>;
|
"End" | "end" => Ok(LayoutToken::End),
|
||||||
|
_ => Err(format!("Unable to find LayoutToken with name: {s}")),
|
||||||
/// Compile layout
|
}
|
||||||
fn compile(
|
}
|
||||||
&self,
|
|
||||||
token: LayoutToken,
|
|
||||||
id: usize,
|
|
||||||
properties: &Option<Box<dyn Any>>,
|
|
||||||
compiler: &Compiler,
|
|
||||||
document: &dyn Document,
|
|
||||||
) -> Result<String, String>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mod default_layouts {
|
mod default_layouts {
|
||||||
use std::any::Any;
|
|
||||||
|
|
||||||
use crate::parser::util::Property;
|
use crate::parser::util::Property;
|
||||||
use crate::parser::util::PropertyParser;
|
use crate::parser::util::PropertyParser;
|
||||||
|
|
||||||
|
@ -291,19 +285,10 @@ impl State for LayoutState {
|
||||||
|
|
||||||
pub struct LayoutRule {
|
pub struct LayoutRule {
|
||||||
re: [Regex; 3],
|
re: [Regex; 3],
|
||||||
layouts: HashMap<String, Rc<dyn LayoutType>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutRule {
|
impl LayoutRule {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let mut layouts: HashMap<String, Rc<dyn LayoutType>> = HashMap::new();
|
|
||||||
|
|
||||||
let layout_centered = default_layouts::Centered::default();
|
|
||||||
layouts.insert(layout_centered.name().to_string(), Rc::new(layout_centered));
|
|
||||||
|
|
||||||
let layout_split = default_layouts::Split::default();
|
|
||||||
layouts.insert(layout_split.name().to_string(), Rc::new(layout_split));
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
re: [
|
re: [
|
||||||
RegexBuilder::new(
|
RegexBuilder::new(
|
||||||
|
@ -325,7 +310,23 @@ impl LayoutRule {
|
||||||
.build()
|
.build()
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
],
|
],
|
||||||
layouts,
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn initialize_state(parser: &dyn Parser) -> Rc<RefCell<dyn State>> {
|
||||||
|
let query = parser.state().query(&STATE_NAME);
|
||||||
|
match query {
|
||||||
|
Some(state) => state,
|
||||||
|
None => {
|
||||||
|
// Insert as a new state
|
||||||
|
match parser.state_mut().insert(
|
||||||
|
STATE_NAME.clone(),
|
||||||
|
Rc::new(RefCell::new(LayoutState { stack: vec![] })),
|
||||||
|
) {
|
||||||
|
Err(_) => panic!("Unknown error"),
|
||||||
|
Ok(state) => state,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -391,20 +392,7 @@ impl RegexRule for LayoutRule {
|
||||||
) -> Vec<Report<(Rc<dyn Source>, Range<usize>)>> {
|
) -> Vec<Report<(Rc<dyn Source>, Range<usize>)>> {
|
||||||
let mut reports = vec![];
|
let mut reports = vec![];
|
||||||
|
|
||||||
let query = parser.state().query(&STATE_NAME);
|
let state = LayoutRule::initialize_state(parser);
|
||||||
let state = match query {
|
|
||||||
Some(state) => state,
|
|
||||||
None => {
|
|
||||||
// Insert as a new state
|
|
||||||
match parser.state_mut().insert(
|
|
||||||
STATE_NAME.clone(),
|
|
||||||
Rc::new(RefCell::new(LayoutState { stack: vec![] })),
|
|
||||||
) {
|
|
||||||
Err(_) => panic!("Unknown error"),
|
|
||||||
Ok(state) => state,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if index == 0
|
if index == 0
|
||||||
// BEGIN_LAYOUT
|
// BEGIN_LAYOUT
|
||||||
|
@ -465,7 +453,7 @@ impl RegexRule for LayoutRule {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get layout
|
// Get layout
|
||||||
let layout_type = match self.layouts.get(trimmed) {
|
let layout_type = match parser.get_layout(trimmed) {
|
||||||
None => {
|
None => {
|
||||||
reports.push(
|
reports.push(
|
||||||
Report::build(ReportKind::Error, token.source(), name.start())
|
Report::build(ReportKind::Error, token.source(), name.start())
|
||||||
|
@ -663,7 +651,212 @@ impl RegexRule for LayoutRule {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Option<Vec<(String, Function<'lua>)>> { None }
|
fn lua_bindings<'lua>(&self, lua: &'lua Lua) -> Option<Vec<(String, Function<'lua>)>> {
|
||||||
|
let mut bindings = vec![];
|
||||||
|
|
||||||
|
bindings.push((
|
||||||
|
"push".to_string(),
|
||||||
|
lua.create_function(
|
||||||
|
|_, (token, layout, properties): (String, String, String)| {
|
||||||
|
let mut result = Ok(());
|
||||||
|
|
||||||
|
// Parse token
|
||||||
|
let layout_token = match LayoutToken::from_str(token.as_str())
|
||||||
|
{
|
||||||
|
Err(err) => {
|
||||||
|
return Err(BadArgument {
|
||||||
|
to: Some("push".to_string()),
|
||||||
|
pos: 1,
|
||||||
|
name: Some("token".to_string()),
|
||||||
|
cause: Arc::new(mlua::Error::external(err))
|
||||||
|
});
|
||||||
|
},
|
||||||
|
Ok(token) => token,
|
||||||
|
};
|
||||||
|
|
||||||
|
CTX.with_borrow(|ctx| {
|
||||||
|
ctx.as_ref().map(|ctx| {
|
||||||
|
// Make sure the state has been initialized
|
||||||
|
let state = LayoutRule::initialize_state(ctx.parser);
|
||||||
|
|
||||||
|
// Get layout
|
||||||
|
let layout_type = match ctx.parser.get_layout(layout.as_str())
|
||||||
|
{
|
||||||
|
None => {
|
||||||
|
result = Err(BadArgument {
|
||||||
|
to: Some("push".to_string()),
|
||||||
|
pos: 2,
|
||||||
|
name: Some("layout".to_string()),
|
||||||
|
cause: Arc::new(mlua::Error::external(format!(
|
||||||
|
"Cannot find layout with name `{layout}`"
|
||||||
|
))),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
Some(layout) => layout,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Parse properties
|
||||||
|
let layout_properties = match layout_type.parse_properties(properties.as_str()) {
|
||||||
|
Err(err) => {
|
||||||
|
result = Err(BadArgument {
|
||||||
|
to: Some("push".to_string()),
|
||||||
|
pos: 3,
|
||||||
|
name: Some("properties".to_string()),
|
||||||
|
cause: Arc::new(mlua::Error::external(err)),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
Ok(properties) => properties,
|
||||||
|
};
|
||||||
|
|
||||||
|
let id = match layout_token {
|
||||||
|
LayoutToken::Begin => {
|
||||||
|
ctx.parser.push(
|
||||||
|
ctx.document,
|
||||||
|
Box::new(Layout {
|
||||||
|
location: ctx.location.clone(),
|
||||||
|
layout: layout_type.clone(),
|
||||||
|
id: 0,
|
||||||
|
token: LayoutToken::Begin,
|
||||||
|
properties: layout_properties,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
state
|
||||||
|
.borrow_mut()
|
||||||
|
.downcast_mut::<LayoutState>()
|
||||||
|
.map_or_else(
|
||||||
|
|| panic!("Invalid state at: `{}`", STATE_NAME.as_str()),
|
||||||
|
|s| s.stack.push((vec![ctx.location.clone()], layout_type.clone())),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
LayoutToken::Next => {
|
||||||
|
let mut state_borrow = state.borrow_mut();
|
||||||
|
let state = state_borrow.downcast_mut::<LayoutState>().unwrap();
|
||||||
|
|
||||||
|
let (tokens, current_layout_type) = match state.stack.last_mut() {
|
||||||
|
None => {
|
||||||
|
result = Err(BadArgument {
|
||||||
|
to: Some("push".to_string()),
|
||||||
|
pos: 1,
|
||||||
|
name: Some("token".to_string()),
|
||||||
|
cause: Arc::new(mlua::Error::external(format!("Unable set next layout: No active layout found"))),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Some(last) => last,
|
||||||
|
};
|
||||||
|
|
||||||
|
if !Rc::ptr_eq(&layout_type, current_layout_type) {
|
||||||
|
result = Err(BadArgument {
|
||||||
|
to: Some("push".to_string()),
|
||||||
|
pos: 2,
|
||||||
|
name: Some("layout".to_string()),
|
||||||
|
cause: Arc::new(mlua::Error::external(format!("Invalid layout next, current layout is {} vs {}",
|
||||||
|
current_layout_type.name(),
|
||||||
|
layout_type.name())))
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if layout_type.expects().end < tokens.len()
|
||||||
|
// Too many blocks
|
||||||
|
{
|
||||||
|
result = Err(BadArgument {
|
||||||
|
to: Some("push".to_string()),
|
||||||
|
pos: 1,
|
||||||
|
name: Some("token".to_string()),
|
||||||
|
cause: Arc::new(mlua::Error::external(format!("Unable set layout next: layout {} expect at most {} blocks, currently at {} blocks",
|
||||||
|
layout_type.name(),
|
||||||
|
layout_type.expects().end,
|
||||||
|
tokens.len()
|
||||||
|
))),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
tokens.push(ctx.location.clone());
|
||||||
|
tokens.len() - 1
|
||||||
|
},
|
||||||
|
LayoutToken::End => {
|
||||||
|
let mut state_borrow = state.borrow_mut();
|
||||||
|
let state = state_borrow.downcast_mut::<LayoutState>().unwrap();
|
||||||
|
|
||||||
|
let (tokens, current_layout_type) = match state.stack.last_mut() {
|
||||||
|
None => {
|
||||||
|
result = Err(BadArgument {
|
||||||
|
to: Some("push".to_string()),
|
||||||
|
pos: 1,
|
||||||
|
name: Some("token".to_string()),
|
||||||
|
cause: Arc::new(mlua::Error::external(format!("Unable set layout end: No active layout found"))),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Some(last) => last,
|
||||||
|
};
|
||||||
|
|
||||||
|
if !Rc::ptr_eq(&layout_type, current_layout_type) {
|
||||||
|
result = Err(BadArgument {
|
||||||
|
to: Some("push".to_string()),
|
||||||
|
pos: 2,
|
||||||
|
name: Some("layout".to_string()),
|
||||||
|
cause: Arc::new(mlua::Error::external(format!("Invalid layout end, current layout is {} vs {}",
|
||||||
|
current_layout_type.name(),
|
||||||
|
layout_type.name())))
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if layout_type.expects().start > tokens.len()
|
||||||
|
// Not enough blocks
|
||||||
|
{
|
||||||
|
result = Err(BadArgument {
|
||||||
|
to: Some("push".to_string()),
|
||||||
|
pos: 1,
|
||||||
|
name: Some("token".to_string()),
|
||||||
|
cause: Arc::new(mlua::Error::external(format!("Unable set next layout: layout {} expect at least {} blocks, currently at {} blocks",
|
||||||
|
layout_type.name(),
|
||||||
|
layout_type.expects().start,
|
||||||
|
tokens.len()
|
||||||
|
))),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let id = tokens.len();
|
||||||
|
state.stack.pop();
|
||||||
|
id
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ctx.parser.push(
|
||||||
|
ctx.document,
|
||||||
|
Box::new(Layout {
|
||||||
|
location: ctx.location.clone(),
|
||||||
|
layout: layout_type.clone(),
|
||||||
|
id,
|
||||||
|
token: layout_token,
|
||||||
|
properties: layout_properties,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
result
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
));
|
||||||
|
|
||||||
|
Some(bindings)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register_layouts(&self, parser: &dyn Parser) {
|
||||||
|
parser.insert_layout(Rc::new(default_layouts::Centered::default()));
|
||||||
|
parser.insert_layout(Rc::new(default_layouts::Split::default()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -727,4 +920,56 @@ mod tests {
|
||||||
Layout { token == LayoutToken::End, id == 2 };
|
Layout { token == LayoutToken::End, id == 2 };
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn lua() {
|
||||||
|
let source = Rc::new(SourceFile::with_content(
|
||||||
|
"".to_string(),
|
||||||
|
r#"
|
||||||
|
%<nml.layout.push("begin", "Split", "style=A")>%
|
||||||
|
A
|
||||||
|
%<nml.layout.push("Begin", "Centered", "style=B")>%
|
||||||
|
B
|
||||||
|
%<nml.layout.push("end", "Centered", "")>%
|
||||||
|
%<nml.layout.push("next", "Split", "style=C")>%
|
||||||
|
C
|
||||||
|
%<nml.layout.push("Begin", "Split", "style=D")>%
|
||||||
|
D
|
||||||
|
%<nml.layout.push("Next", "Split", "style=E")>%
|
||||||
|
E
|
||||||
|
%<nml.layout.push("End", "Split", "")>%
|
||||||
|
%<nml.layout.push("End", "Split", "")>%
|
||||||
|
"#
|
||||||
|
.to_string(),
|
||||||
|
None,
|
||||||
|
));
|
||||||
|
let parser = LangParser::default();
|
||||||
|
let doc = parser.parse(source, None);
|
||||||
|
|
||||||
|
validate_document!(doc.content().borrow(), 0,
|
||||||
|
Layout { token == LayoutToken::Begin, id == 0 };
|
||||||
|
Paragraph {
|
||||||
|
Text { content == "A" };
|
||||||
|
};
|
||||||
|
Layout { token == LayoutToken::Begin, id == 0 };
|
||||||
|
Paragraph {
|
||||||
|
Text { content == "B" };
|
||||||
|
};
|
||||||
|
Layout { token == LayoutToken::End, id == 1 };
|
||||||
|
Layout { token == LayoutToken::Next, id == 1 };
|
||||||
|
Paragraph {
|
||||||
|
Text { content == "C" };
|
||||||
|
};
|
||||||
|
Layout { token == LayoutToken::Begin, id == 0 };
|
||||||
|
Paragraph {
|
||||||
|
Text { content == "D" };
|
||||||
|
};
|
||||||
|
Layout { token == LayoutToken::Next, id == 1 };
|
||||||
|
Paragraph {
|
||||||
|
Text { content == "E" };
|
||||||
|
};
|
||||||
|
Layout { token == LayoutToken::End, id == 2 };
|
||||||
|
Layout { token == LayoutToken::End, id == 2 };
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ use crate::document::document::Document;
|
||||||
use crate::document::element::ContainerElement;
|
use crate::document::element::ContainerElement;
|
||||||
use crate::document::element::ElemKind;
|
use crate::document::element::ElemKind;
|
||||||
use crate::document::element::Element;
|
use crate::document::element::Element;
|
||||||
|
use crate::lua::kernel::CTX;
|
||||||
use crate::parser::parser::Parser;
|
use crate::parser::parser::Parser;
|
||||||
use crate::parser::rule::RegexRule;
|
use crate::parser::rule::RegexRule;
|
||||||
use crate::parser::source::Source;
|
use crate::parser::source::Source;
|
||||||
|
@ -14,12 +15,14 @@ use ariadne::Fmt;
|
||||||
use ariadne::Label;
|
use ariadne::Label;
|
||||||
use ariadne::Report;
|
use ariadne::Report;
|
||||||
use ariadne::ReportKind;
|
use ariadne::ReportKind;
|
||||||
|
use mlua::Error::BadArgument;
|
||||||
use mlua::Function;
|
use mlua::Function;
|
||||||
use mlua::Lua;
|
use mlua::Lua;
|
||||||
use regex::Captures;
|
use regex::Captures;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Link {
|
pub struct Link {
|
||||||
|
@ -205,8 +208,56 @@ impl RegexRule for LinkRule {
|
||||||
return reports;
|
return reports;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
fn lua_bindings<'lua>(&self, lua: &'lua Lua) -> Option<Vec<(String, Function<'lua>)>> {
|
||||||
fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Option<Vec<(String, Function<'lua>)>> { None }
|
let mut bindings = vec![];
|
||||||
|
|
||||||
|
bindings.push((
|
||||||
|
"push".to_string(),
|
||||||
|
lua.create_function(|_, (display, url): (String, String)| {
|
||||||
|
let mut result = Ok(());
|
||||||
|
CTX.with_borrow(|ctx| {
|
||||||
|
ctx.as_ref().map(|ctx| {
|
||||||
|
let source = Rc::new(VirtualSource::new(
|
||||||
|
ctx.location.clone(),
|
||||||
|
"Link Display".to_string(),
|
||||||
|
display,
|
||||||
|
));
|
||||||
|
let display_content =
|
||||||
|
match util::parse_paragraph(ctx.parser, source, ctx.document) {
|
||||||
|
Err(err) => {
|
||||||
|
result = Err(BadArgument {
|
||||||
|
to: Some("push".to_string()),
|
||||||
|
pos: 1,
|
||||||
|
name: Some("display".to_string()),
|
||||||
|
cause: Arc::new(mlua::Error::external(format!(
|
||||||
|
"Failed to parse link display: {err}"
|
||||||
|
))),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Ok(mut paragraph) => {
|
||||||
|
std::mem::replace(&mut paragraph.content, vec![])
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ctx.parser.push(
|
||||||
|
ctx.document,
|
||||||
|
Box::new(Link {
|
||||||
|
location: ctx.location.clone(),
|
||||||
|
display: display_content,
|
||||||
|
url,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
result
|
||||||
|
})
|
||||||
|
.unwrap(),
|
||||||
|
));
|
||||||
|
|
||||||
|
Some(bindings)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -247,4 +298,34 @@ Some [link](url).
|
||||||
};
|
};
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn lua() {
|
||||||
|
let source = Rc::new(SourceFile::with_content(
|
||||||
|
"".to_string(),
|
||||||
|
r#"
|
||||||
|
Some %<nml.link.push("link", "url")>%.
|
||||||
|
%<
|
||||||
|
nml.link.push("**BOLD link**", "another url")
|
||||||
|
>%
|
||||||
|
"#
|
||||||
|
.to_string(),
|
||||||
|
None,
|
||||||
|
));
|
||||||
|
let parser = LangParser::default();
|
||||||
|
let doc = parser.parse(source, None);
|
||||||
|
|
||||||
|
validate_document!(doc.content().borrow(), 0,
|
||||||
|
Paragraph {
|
||||||
|
Text { content == "Some " };
|
||||||
|
Link { url == "url" } { Text { content == "link" }; };
|
||||||
|
Text { content == "." };
|
||||||
|
Link { url == "another url" } {
|
||||||
|
Style;
|
||||||
|
Text { content == "BOLD link" };
|
||||||
|
Style;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -283,7 +283,29 @@ Break{?[kind=block] Raw?}NewParagraph{?<b>?}
|
||||||
None,
|
None,
|
||||||
));
|
));
|
||||||
let parser = LangParser::default();
|
let parser = LangParser::default();
|
||||||
let compiler = Compiler::new(Target::HTML, None);
|
let doc = parser.parse(source, None);
|
||||||
|
|
||||||
|
validate_document!(doc.content().borrow(), 0,
|
||||||
|
Paragraph;
|
||||||
|
Raw { kind == ElemKind::Block, content == "Raw" };
|
||||||
|
Paragraph {
|
||||||
|
Text;
|
||||||
|
Raw { kind == ElemKind::Inline, content == "<b>" };
|
||||||
|
};
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn lua() {
|
||||||
|
let source = Rc::new(SourceFile::with_content(
|
||||||
|
"".to_string(),
|
||||||
|
r#"
|
||||||
|
Break%<nml.raw.push("block", "Raw")>%NewParagraph%<nml.raw.push("inline", "<b>")>%
|
||||||
|
"#
|
||||||
|
.to_string(),
|
||||||
|
None,
|
||||||
|
));
|
||||||
|
let parser = LangParser::default();
|
||||||
let doc = parser.parse(source, None);
|
let doc = parser.parse(source, None);
|
||||||
|
|
||||||
validate_document!(doc.content().borrow(), 0,
|
validate_document!(doc.content().borrow(), 0,
|
||||||
|
|
|
@ -50,8 +50,7 @@ impl Element for Section {
|
||||||
let numbering = compiler.section_counter(self.depth);
|
let numbering = compiler.section_counter(self.depth);
|
||||||
|
|
||||||
let mut result = String::new();
|
let mut result = String::new();
|
||||||
for num in numbering.iter()
|
for num in numbering.iter() {
|
||||||
{
|
|
||||||
result = result + num.to_string().as_str() + ".";
|
result = result + num.to_string().as_str() + ".";
|
||||||
}
|
}
|
||||||
result += " ";
|
result += " ";
|
||||||
|
@ -409,15 +408,15 @@ mod section_style {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests
|
mod tests {
|
||||||
{
|
use crate::parser::langparser::LangParser;
|
||||||
use crate::{parser::{langparser::LangParser, source::SourceFile}, validate_document};
|
use crate::parser::source::SourceFile;
|
||||||
|
use crate::validate_document;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parser()
|
fn parser() {
|
||||||
{
|
|
||||||
let source = Rc::new(SourceFile::with_content(
|
let source = Rc::new(SourceFile::with_content(
|
||||||
"".to_string(),
|
"".to_string(),
|
||||||
r#"
|
r#"
|
||||||
|
@ -443,4 +442,34 @@ use super::*;
|
||||||
Section { depth == 6, title == "6", reference == Some("refname".to_string()) };
|
Section { depth == 6, title == "6", reference == Some("refname".to_string()) };
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn lua() {
|
||||||
|
let source = Rc::new(SourceFile::with_content(
|
||||||
|
"".to_string(),
|
||||||
|
r#"
|
||||||
|
%<
|
||||||
|
nml.section.push("1", 1, "", nil)
|
||||||
|
nml.section.push("2", 2, "+", nil)
|
||||||
|
nml.section.push("3", 3, "*", nil)
|
||||||
|
nml.section.push("4", 4, "+*", nil)
|
||||||
|
nml.section.push("5", 5, "*+", nil)
|
||||||
|
nml.section.push("6", 6, "", "refname")
|
||||||
|
>%
|
||||||
|
"#
|
||||||
|
.to_string(),
|
||||||
|
None,
|
||||||
|
));
|
||||||
|
let parser = LangParser::default();
|
||||||
|
let doc = parser.parse(source, None);
|
||||||
|
|
||||||
|
validate_document!(doc.content().borrow(), 0,
|
||||||
|
Section { depth == 1, title == "1" };
|
||||||
|
Section { depth == 2, title == "2", kind == section_kind::NO_TOC };
|
||||||
|
Section { depth == 3, title == "3", kind == section_kind::NO_NUMBER };
|
||||||
|
Section { depth == 4, title == "4", kind == section_kind::NO_NUMBER | section_kind::NO_TOC };
|
||||||
|
Section { depth == 5, title == "5", kind == section_kind::NO_NUMBER | section_kind::NO_TOC };
|
||||||
|
Section { depth == 6, title == "6", reference == Some("refname".to_string()) };
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::{cell::{RefCell, RefMut}, collections::HashMap, rc::Rc};
|
use std::{cell::{RefCell, RefMut}, collections::HashMap, rc::Rc};
|
||||||
|
|
||||||
use crate::{document::{document::Document, element::Element, style::{ElementStyle, StyleHolder}}, lua::kernel::{Kernel, KernelHolder}, parser::{parser::{Parser, ReportColors}, rule::Rule, source::{Cursor, Source}, state::StateHolder}};
|
use crate::{document::{document::Document, element::Element, layout::{LayoutHolder, LayoutType}, style::{ElementStyle, StyleHolder}}, lua::kernel::{Kernel, KernelHolder}, parser::{parser::{Parser, ReportColors}, rule::Rule, source::{Cursor, Source}, state::StateHolder}};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct LineCursor
|
pub struct LineCursor
|
||||||
|
@ -156,3 +156,13 @@ impl StyleHolder for LsParser {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl LayoutHolder for LsParser {
|
||||||
|
fn layouts(&self) -> std::cell::Ref<'_, HashMap<String, Rc<dyn LayoutType>>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn layouts_mut(&self) -> RefMut<'_, HashMap<String, Rc<dyn LayoutType>>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -16,6 +16,8 @@ use crate::document::element::DocumentEnd;
|
||||||
use crate::document::element::ElemKind;
|
use crate::document::element::ElemKind;
|
||||||
use crate::document::element::Element;
|
use crate::document::element::Element;
|
||||||
use crate::document::langdocument::LangDocument;
|
use crate::document::langdocument::LangDocument;
|
||||||
|
use crate::document::layout::LayoutHolder;
|
||||||
|
use crate::document::layout::LayoutType;
|
||||||
use crate::document::style::ElementStyle;
|
use crate::document::style::ElementStyle;
|
||||||
use crate::document::style::StyleHolder;
|
use crate::document::style::StyleHolder;
|
||||||
use crate::elements::paragraph::Paragraph;
|
use crate::elements::paragraph::Paragraph;
|
||||||
|
@ -46,6 +48,7 @@ pub struct LangParser {
|
||||||
pub state: RefCell<StateHolder>,
|
pub state: RefCell<StateHolder>,
|
||||||
pub kernels: RefCell<HashMap<String, Kernel>>,
|
pub kernels: RefCell<HashMap<String, Kernel>>,
|
||||||
pub styles: RefCell<HashMap<String, Rc<dyn ElementStyle>>>,
|
pub styles: RefCell<HashMap<String, Rc<dyn ElementStyle>>>,
|
||||||
|
pub layouts: RefCell<HashMap<String, Rc<dyn LayoutType>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LangParser {
|
impl LangParser {
|
||||||
|
@ -57,11 +60,11 @@ impl LangParser {
|
||||||
state: RefCell::new(StateHolder::new()),
|
state: RefCell::new(StateHolder::new()),
|
||||||
kernels: RefCell::new(HashMap::new()),
|
kernels: RefCell::new(HashMap::new()),
|
||||||
styles: RefCell::new(HashMap::new()),
|
styles: RefCell::new(HashMap::new()),
|
||||||
|
layouts: RefCell::new(HashMap::new()),
|
||||||
};
|
};
|
||||||
// Register rules
|
// Register rules
|
||||||
register(&mut s);
|
register(&mut s);
|
||||||
|
|
||||||
|
|
||||||
// Register default kernel
|
// Register default kernel
|
||||||
s.kernels
|
s.kernels
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
|
@ -71,6 +74,12 @@ impl LangParser {
|
||||||
for rule in &s.rules {
|
for rule in &s.rules {
|
||||||
rule.register_styles(&s);
|
rule.register_styles(&s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Register default layouts
|
||||||
|
for rule in &s.rules {
|
||||||
|
rule.register_layouts(&s);
|
||||||
|
}
|
||||||
|
|
||||||
s
|
s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -313,3 +322,15 @@ impl StyleHolder for LangParser {
|
||||||
self.styles.borrow_mut()
|
self.styles.borrow_mut()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl LayoutHolder for LangParser {
|
||||||
|
fn layouts(&self) -> Ref<'_, HashMap<String, Rc<dyn crate::document::layout::LayoutType>>> {
|
||||||
|
self.layouts.borrow()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn layouts_mut(
|
||||||
|
&self,
|
||||||
|
) -> RefMut<'_, HashMap<String, Rc<dyn crate::document::layout::LayoutType>>> {
|
||||||
|
self.layouts.borrow_mut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ use super::source::Source;
|
||||||
use super::state::StateHolder;
|
use super::state::StateHolder;
|
||||||
use crate::document::document::Document;
|
use crate::document::document::Document;
|
||||||
use crate::document::element::Element;
|
use crate::document::element::Element;
|
||||||
|
use crate::document::layout::LayoutHolder;
|
||||||
use crate::document::style::StyleHolder;
|
use crate::document::style::StyleHolder;
|
||||||
use crate::lua::kernel::KernelHolder;
|
use crate::lua::kernel::KernelHolder;
|
||||||
use ariadne::Color;
|
use ariadne::Color;
|
||||||
|
@ -42,7 +43,7 @@ impl ReportColors {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Parser: KernelHolder + StyleHolder {
|
pub trait Parser: KernelHolder + StyleHolder + LayoutHolder {
|
||||||
/// Gets the colors for formatting errors
|
/// Gets the colors for formatting errors
|
||||||
///
|
///
|
||||||
/// When colors are disabled, all colors should resolve to empty string
|
/// When colors are disabled, all colors should resolve to empty string
|
||||||
|
|
|
@ -29,6 +29,9 @@ pub trait Rule {
|
||||||
|
|
||||||
/// Registers default styles
|
/// Registers default styles
|
||||||
fn register_styles(&self, _parser: &dyn Parser) {}
|
fn register_styles(&self, _parser: &dyn Parser) {}
|
||||||
|
|
||||||
|
/// Registers default layouts
|
||||||
|
fn register_layouts(&self, _parser: &dyn Parser) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl core::fmt::Debug for dyn Rule {
|
impl core::fmt::Debug for dyn Rule {
|
||||||
|
@ -37,45 +40,6 @@ impl core::fmt::Debug for dyn Rule {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
pub trait RegexRule: Rule
|
|
||||||
{
|
|
||||||
fn name(&self) -> &'static str;
|
|
||||||
|
|
||||||
/// Returns the rule's regex
|
|
||||||
fn regex(&self) -> ®ex::Regex;
|
|
||||||
/// Callback on regex rule match
|
|
||||||
fn on_regex_match<'a>(&self, parser: &Parser, document: &Document, token: Token<'a>, matches: regex::Captures) -> Vec<Report<'a, (String, Range<usize>)>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: RegexRule> Rule for T {
|
|
||||||
fn name(&self) -> &'static str { RegexRule::name(self) }
|
|
||||||
|
|
||||||
/// Finds the next match starting from [`cursor`]
|
|
||||||
fn next_match<'a>(&self, cursor: &'a Cursor) -> Option<usize>
|
|
||||||
{
|
|
||||||
let re = self.regex();
|
|
||||||
|
|
||||||
let content = cursor.file.content.as_ref().unwrap();
|
|
||||||
match re.find_at(content.as_str(), cursor.pos)
|
|
||||||
{
|
|
||||||
Some(m) => Some(m.start()),
|
|
||||||
None => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_match<'a>(&self, parser: &Parser, document: &Document, cursor: Cursor<'a>) -> (Cursor<'a>, Vec<Report<'a, (String, Range<usize>)>>)
|
|
||||||
{
|
|
||||||
let content = cursor.file.content.as_ref().unwrap();
|
|
||||||
let matches = self.regex().captures_at(content.as_str(), cursor.pos).unwrap();
|
|
||||||
let token = Token::new(cursor.pos, matches.get(0).unwrap().len(), cursor.file);
|
|
||||||
|
|
||||||
let token_end = token.end();
|
|
||||||
(cursor.at(token_end), self.on_regex_match(parser, document, token, matches))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
pub trait RegexRule {
|
pub trait RegexRule {
|
||||||
fn name(&self) -> &'static str;
|
fn name(&self) -> &'static str;
|
||||||
|
|
||||||
|
@ -94,6 +58,7 @@ pub trait RegexRule {
|
||||||
|
|
||||||
fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Option<Vec<(String, Function<'lua>)>> { None }
|
fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Option<Vec<(String, Function<'lua>)>> { None }
|
||||||
fn register_styles(&self, _parser: &dyn Parser) {}
|
fn register_styles(&self, _parser: &dyn Parser) {}
|
||||||
|
fn register_layouts(&self, _parser: &dyn Parser) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: RegexRule> Rule for T {
|
impl<T: RegexRule> Rule for T {
|
||||||
|
@ -155,4 +120,8 @@ impl<T: RegexRule> Rule for T {
|
||||||
fn register_styles(&self, parser: &dyn Parser) {
|
fn register_styles(&self, parser: &dyn Parser) {
|
||||||
self.register_styles(parser);
|
self.register_styles(parser);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn register_layouts(&self, parser: &dyn Parser) {
|
||||||
|
self.register_layouts(parser);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue