diff --git a/src/compiler/navigation.rs b/src/compiler/navigation.rs index becffaa..2ce01c5 100644 --- a/src/compiler/navigation.rs +++ b/src/compiler/navigation.rs @@ -216,7 +216,7 @@ mod tests { #[test] fn sort() { - let mut entries: Vec<(String, String, Option)> = vec![ + let entries: Vec<(String, String, Option)> = vec![ ("Index".into(), "".into(), None), ("AB".into(), "".into(), Some("Index".into())), ("Getting Started".into(), "".into(), Some("Index".into())), diff --git a/src/document/layout.rs b/src/document/layout.rs new file mode 100644 index 0000000..bb9ded9 --- /dev/null +++ b/src/document/layout.rs @@ -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>, String>; + + /// Expected number of blocks + fn expects(&self) -> Range; + + /// Compile layout + fn compile( + &self, + token: LayoutToken, + id: usize, + properties: &Option>, + compiler: &Compiler, + document: &dyn Document, + ) -> Result; +} + +pub trait LayoutHolder { + /// gets a reference to all defined layouts + fn layouts(&self) -> Ref<'_, HashMap>>; + + /// gets a (mutable) reference to all defined layours + fn layouts_mut(&self) -> RefMut<'_, HashMap>>; + + fn get_layout(&self, layout_name: &str) -> Option> { + self.layouts().get(layout_name).map(|layout| layout.clone()) + } + + fn insert_layout(&self, layout: Rc) { + self.layouts_mut().insert(layout.name().into(), layout); + } +} diff --git a/src/document/mod.rs b/src/document/mod.rs index 8fb316f..58fc559 100644 --- a/src/document/mod.rs +++ b/src/document/mod.rs @@ -4,3 +4,4 @@ pub mod langdocument; pub mod element; pub mod variable; pub mod style; +pub mod layout; diff --git a/src/elements/layout.rs b/src/elements/layout.rs index cf5017a..d4f742b 100644 --- a/src/elements/layout.rs +++ b/src/elements/layout.rs @@ -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>, String>; - - /// Expected number of blocks - fn expects(&self) -> Range; - - /// Compile layout - fn compile( - &self, - token: LayoutToken, - id: usize, - properties: &Option>, - compiler: &Compiler, - document: &dyn Document, - ) -> Result; + fn from_str(s: &str) -> Result { + 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>, } impl LayoutRule { pub fn new() -> Self { - let mut layouts: HashMap> = 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> { + 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, Range)>> { 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)>> { None } + fn lua_bindings<'lua>(&self, lua: &'lua Lua) -> Option)>> { + 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::() + .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::().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::().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#" +%% + A +%% + B +%% +%% + C +%% + D +%% + E +%% +%% +"# + .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 }; + ); + } } diff --git a/src/elements/link.rs b/src/elements/link.rs index 109f044..0ae8c64 100644 --- a/src/elements/link.rs +++ b/src/elements/link.rs @@ -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)>> { None } + fn lua_bindings<'lua>(&self, lua: &'lua Lua) -> Option)>> { + 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("**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; + }; + }; + ); + } } diff --git a/src/elements/raw.rs b/src/elements/raw.rs index b5cd6f3..7370219 100644 --- a/src/elements/raw.rs +++ b/src/elements/raw.rs @@ -283,7 +283,6 @@ Break{?[kind=block] Raw?}NewParagraph{??} None, )); let parser = LangParser::default(); - let compiler = Compiler::new(Target::HTML, None); let doc = parser.parse(source, None); validate_document!(doc.content().borrow(), 0, @@ -295,4 +294,27 @@ Break{?[kind=block] Raw?}NewParagraph{??} }; ); } + + #[test] + fn lua() { + let source = Rc::new(SourceFile::with_content( + "".to_string(), + r#" +Break%%NewParagraph%")>% + "# + .to_string(), + None, + )); + let parser = LangParser::default(); + 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 == "" }; + }; + ); + } } diff --git a/src/elements/section.rs b/src/elements/section.rs index d7f9eab..e303674 100644 --- a/src/elements/section.rs +++ b/src/elements/section.rs @@ -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,18 +408,18 @@ 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#" + "".to_string(), + r#" # 1 ##+ 2 ###* 3 @@ -428,8 +427,38 @@ use super::*; #####*+ 5 ######{refname} 6 "# - .to_string(), - None, + .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()) }; + ); + } + + #[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); diff --git a/src/lsp/parser.rs b/src/lsp/parser.rs index 489a6b5..82165c7 100644 --- a/src/lsp/parser.rs +++ b/src/lsp/parser.rs @@ -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>> { + todo!() + } + + fn layouts_mut(&self) -> RefMut<'_, HashMap>> { + todo!() + } +} diff --git a/src/parser/langparser.rs b/src/parser/langparser.rs index 3b70bcf..968a34b 100644 --- a/src/parser/langparser.rs +++ b/src/parser/langparser.rs @@ -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, pub kernels: RefCell>, pub styles: RefCell>>, + pub layouts: RefCell>>, } 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>> { + self.layouts.borrow() + } + + fn layouts_mut( + &self, + ) -> RefMut<'_, HashMap>> { + self.layouts.borrow_mut() + } +} diff --git a/src/parser/parser.rs b/src/parser/parser.rs index 4d28018..58969f2 100644 --- a/src/parser/parser.rs +++ b/src/parser/parser.rs @@ -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 diff --git a/src/parser/rule.rs b/src/parser/rule.rs index 55be741..6513518 100644 --- a/src/parser/rule.rs +++ b/src/parser/rule.rs @@ -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) -> ®ex::Regex; - /// Callback on regex rule match - fn on_regex_match<'a>(&self, parser: &Parser, document: &Document, token: Token<'a>, matches: regex::Captures) -> Vec)>>; -} - -impl 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 - { - 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)>>) - { - 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)>> { None } fn register_styles(&self, _parser: &dyn Parser) {} + fn register_layouts(&self, _parser: &dyn Parser) {} } impl Rule for T { @@ -155,4 +120,8 @@ impl 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); + } }