Layout refactor & bindings

This commit is contained in:
ef3d0c3e 2024-08-04 10:25:51 +02:00
parent cc7bc7169c
commit 67ea2f9411
11 changed files with 533 additions and 105 deletions

View file

@ -216,7 +216,7 @@ mod tests {
#[test]
fn sort() {
let mut entries: Vec<(String, String, Option<String>)> = vec![
let entries: Vec<(String, String, Option<String>)> = vec![
("Index".into(), "".into(), None),
("AB".into(), "".into(), Some("Index".into())),
("Getting Started".into(), "".into(), Some("Index".into())),

49
src/document/layout.rs Normal file
View 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);
}
}

View file

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

View file

@ -3,6 +3,8 @@ use crate::compiler::compiler::Target;
use crate::document::document::Document;
use crate::document::element::ElemKind;
use crate::document::element::Element;
use crate::document::layout::LayoutType;
use crate::lua::kernel::CTX;
use crate::parser::parser::Parser;
use crate::parser::parser::ReportColors;
use crate::parser::rule::RegexRule;
@ -16,6 +18,7 @@ use ariadne::Label;
use ariadne::Report;
use ariadne::ReportKind;
use lazy_static::lazy_static;
use mlua::Error::BadArgument;
use mlua::Function;
use mlua::Lua;
use regex::Captures;
@ -27,6 +30,8 @@ use std::cell::RefCell;
use std::collections::HashMap;
use std::ops::Range;
use std::rc::Rc;
use std::str::FromStr;
use std::sync::Arc;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum LayoutToken {
@ -35,31 +40,20 @@ pub(crate) enum LayoutToken {
End,
}
/// Represents the type of a layout
pub trait LayoutType: core::fmt::Debug {
/// Name of the layout
fn name(&self) -> &'static str;
impl FromStr for LayoutToken {
type Err = String;
/// 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>;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"Begin" | "begin" => Ok(LayoutToken::Begin),
"Next" | "next" => Ok(LayoutToken::Next),
"End" | "end" => Ok(LayoutToken::End),
_ => Err(format!("Unable to find LayoutToken with name: {s}")),
}
}
}
mod default_layouts {
use std::any::Any;
use crate::parser::util::Property;
use crate::parser::util::PropertyParser;
@ -291,19 +285,10 @@ impl State for LayoutState {
pub struct LayoutRule {
re: [Regex; 3],
layouts: HashMap<String, Rc<dyn LayoutType>>,
}
impl LayoutRule {
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 {
re: [
RegexBuilder::new(
@ -325,7 +310,23 @@ impl LayoutRule {
.build()
.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>)>> {
let mut reports = vec![];
let query = parser.state().query(&STATE_NAME);
let state = match query {
Some(state) => state,
None => {
// Insert as a new state
match parser.state_mut().insert(
STATE_NAME.clone(),
Rc::new(RefCell::new(LayoutState { stack: vec![] })),
) {
Err(_) => panic!("Unknown error"),
Ok(state) => state,
}
}
};
let state = LayoutRule::initialize_state(parser);
if index == 0
// BEGIN_LAYOUT
@ -465,7 +453,7 @@ impl RegexRule for LayoutRule {
}
// Get layout
let layout_type = match self.layouts.get(trimmed) {
let layout_type = match parser.get_layout(trimmed) {
None => {
reports.push(
Report::build(ReportKind::Error, token.source(), name.start())
@ -663,7 +651,212 @@ impl RegexRule for LayoutRule {
}
// 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)]
@ -727,4 +920,56 @@ mod tests {
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 };
);
}
}

View file

@ -4,6 +4,7 @@ use crate::document::document::Document;
use crate::document::element::ContainerElement;
use crate::document::element::ElemKind;
use crate::document::element::Element;
use crate::lua::kernel::CTX;
use crate::parser::parser::Parser;
use crate::parser::rule::RegexRule;
use crate::parser::source::Source;
@ -14,12 +15,14 @@ use ariadne::Fmt;
use ariadne::Label;
use ariadne::Report;
use ariadne::ReportKind;
use mlua::Error::BadArgument;
use mlua::Function;
use mlua::Lua;
use regex::Captures;
use regex::Regex;
use std::ops::Range;
use std::rc::Rc;
use std::sync::Arc;
#[derive(Debug)]
pub struct Link {
@ -205,8 +208,56 @@ impl RegexRule for LinkRule {
return reports;
}
// 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(|_, (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)]
@ -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;
};
};
);
}
}

View file

@ -283,7 +283,29 @@ Break{?[kind=block] Raw?}NewParagraph{?<b>?}
None,
));
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);
validate_document!(doc.content().borrow(), 0,

View file

@ -50,8 +50,7 @@ impl Element for Section {
let numbering = compiler.section_counter(self.depth);
let mut result = String::new();
for num in numbering.iter()
{
for num in numbering.iter() {
result = result + num.to_string().as_str() + ".";
}
result += " ";
@ -409,15 +408,15 @@ mod section_style {
}
#[cfg(test)]
mod tests
{
use crate::{parser::{langparser::LangParser, source::SourceFile}, validate_document};
mod tests {
use crate::parser::langparser::LangParser;
use crate::parser::source::SourceFile;
use crate::validate_document;
use super::*;
use super::*;
#[test]
fn parser()
{
fn parser() {
let source = Rc::new(SourceFile::with_content(
"".to_string(),
r#"
@ -443,4 +442,34 @@ use super::*;
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()) };
);
}
}

View file

@ -1,6 +1,6 @@
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)]
pub struct LineCursor
@ -156,3 +156,13 @@ impl StyleHolder for LsParser {
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!()
}
}

View file

@ -16,6 +16,8 @@ use crate::document::element::DocumentEnd;
use crate::document::element::ElemKind;
use crate::document::element::Element;
use crate::document::langdocument::LangDocument;
use crate::document::layout::LayoutHolder;
use crate::document::layout::LayoutType;
use crate::document::style::ElementStyle;
use crate::document::style::StyleHolder;
use crate::elements::paragraph::Paragraph;
@ -46,6 +48,7 @@ pub struct LangParser {
pub state: RefCell<StateHolder>,
pub kernels: RefCell<HashMap<String, Kernel>>,
pub styles: RefCell<HashMap<String, Rc<dyn ElementStyle>>>,
pub layouts: RefCell<HashMap<String, Rc<dyn LayoutType>>>,
}
impl LangParser {
@ -57,11 +60,11 @@ impl LangParser {
state: RefCell::new(StateHolder::new()),
kernels: RefCell::new(HashMap::new()),
styles: RefCell::new(HashMap::new()),
layouts: RefCell::new(HashMap::new()),
};
// Register rules
register(&mut s);
// Register default kernel
s.kernels
.borrow_mut()
@ -71,6 +74,12 @@ impl LangParser {
for rule in &s.rules {
rule.register_styles(&s);
}
// Register default layouts
for rule in &s.rules {
rule.register_layouts(&s);
}
s
}
@ -313,3 +322,15 @@ impl StyleHolder for LangParser {
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()
}
}

View file

@ -10,6 +10,7 @@ use super::source::Source;
use super::state::StateHolder;
use crate::document::document::Document;
use crate::document::element::Element;
use crate::document::layout::LayoutHolder;
use crate::document::style::StyleHolder;
use crate::lua::kernel::KernelHolder;
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
///
/// When colors are disabled, all colors should resolve to empty string

View file

@ -29,6 +29,9 @@ pub trait Rule {
/// Registers default styles
fn register_styles(&self, _parser: &dyn Parser) {}
/// Registers default layouts
fn register_layouts(&self, _parser: &dyn Parser) {}
}
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) -> &regex::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 {
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 register_styles(&self, _parser: &dyn Parser) {}
fn register_layouts(&self, _parser: &dyn Parser) {}
}
impl<T: RegexRule> Rule for T {
@ -155,4 +120,8 @@ impl<T: RegexRule> Rule for T {
fn register_styles(&self, parser: &dyn Parser) {
self.register_styles(parser);
}
fn register_layouts(&self, parser: &dyn Parser) {
self.register_layouts(parser);
}
}