seam

Symbolic-Expressions As Markup.
git clone git://git.knutsen.co/seam
Log | Files | Refs | README | LICENSE

commit 26c383a4f19380cad5ea86274b3ceee2ea2d4f3b
parent 14a067d52b94acb24c059e1885d08759a56520f3
Author: Demonstrandum <samuel@knutsen.co>
Date:   Fri, 12 Jul 2024 17:55:17 +0100

Remove all memory leaks; added plain-text formatter.

Removed all instances of `Box::leak(...)` cop-outs; replaced with
proper lifetime management.

Also added a plain-text formatter (`--text`) that is now the default
when rendering the text inside `<title>` tags in HTML.
This renderer does not preserve string escape sequences and double
quotes when rendering a string like the SExp (`--sexp`) source-code
formatter does.  This makes it much more akin to just plain text
generation with macro manipulation.

Diffstat:
MCargo.lock | 2+-
MCargo.toml | 2+-
Arust-toolchain.toml | 2++
Msrc/assemble/css.rs | 18+++++++++---------
Msrc/assemble/html.rs | 383+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Msrc/assemble/mod.rs | 46+++++++++++++++++++++++++++++++++-------------
Msrc/assemble/sexp.rs | 94++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
Asrc/assemble/text.rs | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/assemble/xml.rs | 224++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Msrc/bin.rs | 20++++++++++++--------
Msrc/lib.rs | 2+-
Msrc/parse/expander.rs | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Msrc/parse/lexer.rs | 2+-
Msrc/parse/parser.rs | 18++++++++++++++----
Msrc/parse/tokens.rs | 6+++---
15 files changed, 581 insertions(+), 391 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -162,7 +162,7 @@ dependencies = [ [[package]] name = "seam" -version = "0.2.3" +version = "0.2.4" dependencies = [ "chrono", "colored", diff --git a/Cargo.toml b/Cargo.toml @@ -4,7 +4,7 @@ description = "Symbolic Expressions As Markup." keywords = ["markup", "lisp", "macro", "symbolic-expression", "sexp"] license-file = "LICENSE" homepage = "https://git.knutsen.co/seam" -version = "0.2.3" +version = "0.2.4" authors = ["Demonstrandum <samuel@knutsen.co>"] edition = "2021" diff --git a/rust-toolchain.toml b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly" diff --git a/src/assemble/css.rs b/src/assemble/css.rs @@ -1,12 +1,12 @@ //! Assembles an expanded tree into valid CSS. -use super::{GenerationError, MarkupDisplay, Formatter}; +use super::{GenerationError, MarkupFormatter, Formatter}; use crate::parse::parser::{ParseNode, ParseTree}; use std::slice::Iter; #[derive(Debug, Clone)] pub struct CSSFormatter<'a> { - pub tree : ParseTree<'a>, + pub tree: ParseTree<'a>, } impl<'a> CSSFormatter<'a> { @@ -15,10 +15,10 @@ impl<'a> CSSFormatter<'a> { } } -pub const DEFAULT : &str = "\n"; +pub const DEFAULT: &str = "\n"; /// All CSS functions, I might have missed a few. -const CSS_FUNCTIONS : [&str; 58] = [ +const CSS_FUNCTIONS: [&str; 58] = [ "attr", "blur", "brightness", "calc", "circle", "color", "contrast", "counter", "counters", "cubic-bezier", "drop-shadow", "ellipse", "format", "grayscale", "hsl", "hsla", "hue-rotate", "hwb", "image", "inset", @@ -34,12 +34,12 @@ const CSS_FUNCTIONS : [&str; 58] = [ /// Some CSS functions use commas as an argument delimiter, /// some use spaces! Why not! -const CSS_COMMA_DELIM : [&str; 2] = ["rgba", "hsla"]; +const CSS_COMMA_DELIM: [&str; 2] = ["rgba", "hsla"]; /// Intentionally left out "@viewport". If they decided to make /// CSS a good and coherent language in the first place, we wouldn't /// have to deal with ridiculous stuff like this. -const CSS_SPECIAL_SELECTORS : [&str; 12] +const CSS_SPECIAL_SELECTORS: [&str; 12] = [ "@charset" , "@counter-style" , "@document" , "@font-face" , "@font-feature-values" , "@import" , "@keyframes" , "@media" , "@namespace" @@ -62,7 +62,7 @@ const CSS_NON_NESTED_SELECTORS: [&str; 3] /// The only four math operations supported by CSS calc(...), /// or at least I think. -const BINARY_OPERATORS : [&str; 4] = ["+", "-", "*", "/"]; +const BINARY_OPERATORS: [&str; 4] = ["+", "-", "*", "/"]; fn convert_value<'a>(node: &'a ParseNode<'a>) -> Result<String, GenerationError<'a>> { match node { @@ -116,7 +116,7 @@ fn convert_value<'a>(node: &'a ParseNode<'a>) -> Result<String, GenerationError< /// Function responsible for translating a CSS value (i.e. /// a value of a CSS property) from some s-expression into /// a valid CSS value. -pub fn css_value<'a>(_property : &str, node: &'a ParseNode<'a>) +pub fn css_value<'a>(_property: &str, node: &'a ParseNode<'a>) -> Result<String, GenerationError<'a>> { // Naïve way (in future consider the type of property, // and take care of special cases): @@ -237,7 +237,7 @@ fn generate_css_rule<'a>(f: Formatter, iter: Iter<'a, ParseNode<'a>>) -> Result< Ok(()) } -impl<'a> MarkupDisplay for CSSFormatter<'a> { +impl<'a> MarkupFormatter for CSSFormatter<'a> { fn document(&self) -> Result<String, GenerationError> { let mut doc = String::new(); if self.tree.is_empty() { diff --git a/src/assemble/html.rs b/src/assemble/html.rs @@ -1,6 +1,9 @@ //! Assembles an expanded tree into valid HTML. -use super::{escape_xml, GenerationError, MarkupDisplay, Formatter}; +use std::cell::RefCell; + +use super::{escape_xml, GenerationError, MarkupFormatter, Formatter}; use super::{ + text::PlainTextFormatter, sexp::SExpFormatter, xml::XMLFormatter, css::CSSFormatter, @@ -12,11 +15,203 @@ use crate::parse::tokens; #[derive(Debug, Clone)] pub struct HTMLFormatter<'a> { pub tree: ParseTree<'a>, + formatters: RefCell<Vec<Box<dyn MarkupFormatter + 'a>>>, } impl<'a> HTMLFormatter<'a> { pub fn new(tree: ParseTree<'a>) -> Self { - Self { tree } + Self { + tree, + formatters: Default::default(), + } + } + + fn register_formatter<Fmt: MarkupFormatter + 'a>(&self, formatter: Fmt) -> &'a Box<dyn MarkupFormatter + 'a> { + let fmts = self.formatters.as_ptr(); + unsafe { + (*fmts).push(Box::new(formatter)); + (*fmts).last().unwrap() + } + } + + fn generate_html_node(&self, f: Formatter, node: &ParseNode<'a>) -> Result<(), GenerationError<'a>> { + match node { + ParseNode::Symbol(node) + | ParseNode::Number(node) => { + write!(f, "{}", node.leading_whitespace)?; + write!(f, "{}", escape_xml(&node.value))?; + }, + ParseNode::String(node) => { + write!(f, "{}", node.leading_whitespace)?; + write!(f, "{}", escape_xml(&node.value))?; + }, + ParseNode::List { nodes: list, leading_whitespace, end_token, .. } => { + write!(f, "{}", leading_whitespace)?; + let head = list.first(); + let tag: &str; // html <tag> name. + if let Some(head_node) = head { + if let ParseNode::Symbol(head_symbol) = head_node { + tag = &head_symbol.value; + write!(f, "<{}", tag)?; + } else { + // Error, tags can only have symbol values. + return Err(GenerationError::new("HTML", + "HTML tags can only be given as symbols.", + head_node.site())); + } + } else { + // Error, empty tags not supported. + return Err(GenerationError::new("HTML", + "Empty lists cannot be converted into a valid HTML tag.", + node.site())); + } + let tag = tag.to_ascii_lowercase(); + + let mut rest = &list[1..]; + + // Declarations behave differently. + if tag.as_bytes()[0] == '!' as u8 { + while !rest.is_empty() { + if let Some(node) = rest[0].symbolic() { + write!(f, " {}", node.value)?; + } else { + return Err(GenerationError::new("HTML", + "Non-symbolic item in declaration", + &rest[0].site())); + } + rest = &rest[1..]; + } + write!(f, ">")?; + return Ok(()); + } + + while let Some(ParseNode::Attribute { node, keyword, .. }) = rest.first() { + if let Some(atom) = (*node).atomic() { + write!(f, " {}=\"{}\"", keyword, atom.value)?; + rest = &rest[1..]; + } else { + // Error! Cannot be non atomic. + return Err(GenerationError::new("HTML", + "Attribute cannot contain non-atomic data.", + &(*node).site())); + } + } + write!(f, ">")?; + + // Check early if this tag is a void element. + if VOID_ELEMENTS.binary_search(&tag.as_str()).is_ok() { + // Void elements cannot have children. + if let Some(child_node) = rest.first() { + return Err(GenerationError::new("HTML", + &format!("A void element such as `<{}>' cannot have children.", tag), + child_node.site())); + } + // Finished: void elements don't get a closing tag. + return Ok(()); + } + + // The first node to a tag should have its whitespace supressed! + // e.g. `(p hello world)` -> `<p>hello world</p>`. + // But if there's a new line, its likely it should be carreid through. + // e.g. + // ``` + // (div + // hello) + // ``` + // -> + // ``` + // <div> + // hello + // </div> + let rest_with_preserved_whitespace = rest; + let mut rest: Vec<ParseNode<'a>> = rest_with_preserved_whitespace.to_vec(); + let mut is_first_node_on_next_line = false; + if let Some(first_node) = rest.get_mut(0) { + is_first_node_on_next_line = first_node.leading_whitespace().contains('\n'); + if !is_first_node_on_next_line { + first_node.set_leading_whitespace("".to_owned()); + } + } + + // Handle tags which *do not* contain HTML as syntax: + // <pre>, <style>, <script>, <math>, <svg>, <textarea>, <title> + // Specifically: + // - <svg> and <math> contain XML, not HTML; + // - <pre>, <textarea> and <title> contain raw text, not parsed as HTML; + // - <pre> will display raw text found in source code; + // - <textarea> and <title> however, are escapable (evaluete macros); + // - <script> contains JavaScript, maybe we will parse this in the future!; + // - <style> contains CSS, which we have our own parser for already. + match tag.as_str() { + "pre" => { // <pre> should preserve the raw text in the *source* file. + // Find beginning and end byte offset of first and last token inside + // of `(pre ...)` and simply clone the text between those offsets. + let pre = raw_text(rest_with_preserved_whitespace.first(), end_token); + write!(f, "{}", pre)?; + }, + "textarea" | "title" => { // Not eaw source-code, but plain-text. + // We have to reconsititute what the source-code would look like if all + // macros were expanded by hand, and read as raw source code. + let text_fmt = PlainTextFormatter::new(rest.into_boxed_slice()); + let text_fmt = self.register_formatter(text_fmt); + text_fmt.generate(f)?; + }, + "style" => { // <style> tag needs to generate CSS. + // When just a string is passed, don't convert. Assume raw CSS. + if let Some(ParseNode::String(string_node)) = rest.first() { + if rest.len() != 1 { + return Err(GenerationError { + markup: "HTML+CSS", + message: String::from("A `style' tag can either have S-expression CSS rules, or\ + a single string containing raw CSS be passed in.\n\ + A string was passed in, but excess expressions were passed \ + in after that!"), + site: string_node.site.clone() + }); + } + // Otherwise, write that raw CSS. + write!(f, "{}", string_node.value)?; + } else { + writeln!(f, "")?; + let css_fmt = CSSFormatter::new(rest.into_boxed_slice()); + let css_fmt = self.register_formatter(css_fmt); + css_fmt.generate(f)?; + } + }, + "script" => { + // TODO: Generating JavaScript from S-expressions is not implemented. + // For now, just treat it as a completely source-code preserving. + let sexp_fmt = SExpFormatter::new(rest.into_boxed_slice()); + let sexp_fmt = self.register_formatter(sexp_fmt); + sexp_fmt.generate(f)?; + }, + "math" | "svg" => { // <math> and <svg> are subsets of XML. + let xml_fmt = XMLFormatter::new(rest.into_boxed_slice()); + let xml_fmt = self.register_formatter(xml_fmt); + xml_fmt.generate(f)?; + }, + _ => { // Tag contains regular old HTML. + let html_fmt = HTMLFormatter::new(rest.into_boxed_slice()); + let html_fmt = self.register_formatter(html_fmt); + html_fmt.generate(f)?; + }, + } + // Closing tag should be equally as spaced as opening tag (?) + if end_token.leading_whitespace.is_empty() { + if is_first_node_on_next_line || tag == "style" { + write!(f, "{}", leading_whitespace)?; + } + } else { + write!(f, "{}", end_token.leading_whitespace)?; + } + + write!(f, "</{}>", tag)?; + }, + ParseNode::Attribute { ref site, .. } => + return Err(GenerationError::new("HTML", + "Unexpected attribute encountered.", site)) + } + Ok(()) } } @@ -47,7 +242,7 @@ const VOID_ELEMENTS: [&str; 14] = [ "wbr", ]; -impl<'a> MarkupDisplay for HTMLFormatter<'a> { +impl<'a> MarkupFormatter for HTMLFormatter<'a> { fn document(&self) -> Result<String, GenerationError> { let mut doc = String::new(); if self.tree.is_empty() { @@ -101,192 +296,12 @@ impl<'a> MarkupDisplay for HTMLFormatter<'a> { fn generate(&self, f: Formatter) -> Result<(), GenerationError> { let mut tree_iter = self.tree.iter().peekable(); while let Some(node) = tree_iter.next() { - generate_html_node(f, node)?; + self.generate_html_node(f, node)?; } Ok(()) } } -fn generate_html_node<'a>(f: Formatter, node: &'a ParseNode<'a>) -> Result<(), GenerationError<'a>> { - match node { - ParseNode::Symbol(node) - | ParseNode::Number(node) => { - write!(f, "{}", node.leading_whitespace)?; - write!(f, "{}", escape_xml(&node.value))?; - }, - ParseNode::String(node) => { - write!(f, "{}", node.leading_whitespace)?; - write!(f, "{}", escape_xml(&node.value))?; - }, - ParseNode::List { nodes: list, leading_whitespace, end_token, .. } => { - write!(f, "{}", leading_whitespace)?; - let head = list.first(); - let tag: &str; // html <tag> name. - if let Some(head_node) = head { - if let ParseNode::Symbol(head_symbol) = head_node { - tag = &head_symbol.value; - write!(f, "<{}", tag)?; - } else { - // Error, tags can only have symbol values. - return Err(GenerationError::new("HTML", - "HTML tags can only be given as symbols.", - head_node.site())); - } - } else { - // Error, empty tags not supported. - return Err(GenerationError::new("HTML", - "Empty lists cannot be converted into a valid HTML tag.", - node.site())); - } - let tag = tag.to_ascii_lowercase(); - - let mut rest = &list[1..]; - - // Declarations behave differently. - if tag.as_bytes()[0] == '!' as u8 { - while !rest.is_empty() { - if let Some(node) = rest[0].symbolic() { - write!(f, " {}", node.value)?; - } else { - return Err(GenerationError::new("HTML", - "Non-symbolic item in declaration", - &rest[0].site())); - } - rest = &rest[1..]; - } - write!(f, ">")?; - return Ok(()); - } - - while let Some(ParseNode::Attribute { node, keyword, .. }) = rest.first() { - if let Some(atom) = (*node).atomic() { - write!(f, " {}=\"{}\"", keyword, atom.value)?; - rest = &rest[1..]; - } else { - // Error! Cannot be non atomic. - return Err(GenerationError::new("HTML", - "Attribute cannot contain non-atomic data.", - &(*node).site())); - } - } - write!(f, ">")?; - - // Check early if this tag is a void element. - if VOID_ELEMENTS.binary_search(&tag.as_str()).is_ok() { - // Void elements cannot have children. - if let Some(child_node) = rest.first() { - return Err(GenerationError::new("HTML", - &format!("A void element such as `<{}>' cannot have children.", tag), - child_node.site())); - } - // Finished: void elements don't get a closing tag. - return Ok(()); - } - - // The first node to a tag should have its whitespace supressed! - // e.g. `(p hello world)` -> `<p>hello world</p>`. - // But if there's a new line, its likely it should be carreid through. - // e.g. - // ``` - // (div - // hello) - // ``` - // -> - // ``` - // <div> - // hello - // </div> - let rest_with_preserved_whitespace = rest; - let mut rest: Vec<ParseNode<'a>> = rest_with_preserved_whitespace.to_vec(); - let mut is_first_node_on_next_line = false; - if let Some(first_node) = rest.get_mut(0) { - is_first_node_on_next_line = first_node.leading_whitespace().contains('\n'); - if !is_first_node_on_next_line { - first_node.set_leading_whitespace("".to_owned()); - } - } - - // Handle tags which *do not* contain HTML as syntax: - // <pre>, <style>, <script>, <math>, <svg>, <textarea>, <title> - // Specifically: - // - <svg> and <math> contain XML, not HTML; - // - <pre>, <textarea> and <title> contain raw text, not parsed as HTML; - // - <pre> will display raw text found in source code; - // - <textarea> and <title> however, are escapable (evaluete macros); - // - <script> contains JavaScript, maybe we will parse this in the future!; - // - <style> contains CSS, which we have our own parser for already. - match tag.as_str() { - "pre" => { // <pre> should preserve the raw text in the *source* file. - // Find beginning and end byte offset of first and last token inside - // of `(pre ...)` and simply clone the text between those offsets. - let pre = raw_text(rest_with_preserved_whitespace.first(), end_token); - write!(f, "{}", pre)?; - }, - "textarea" | "title" => { // Not eaw source-code, but plain-text. - // We have to reconsititute what the source-code would look like if all - // macros were expanded by hand, and read as raw source code. - let sexp_fmt = SExpFormatter::new(rest.into_boxed_slice()); - let sexp_fmt = Box::leak(Box::new(sexp_fmt)); // TODO: Store. - sexp_fmt.generate(f)?; - }, - "style" => { // <style> tag needs to generate CSS. - // When just a string is passed, don't convert. Assume raw CSS. - if let Some(ParseNode::String(string_node)) = rest.first() { - if rest.len() != 1 { - // FIXME: Leak doesn't really matter, but should really be a better way. - let second_node = Box::leak(Box::new(rest[1].to_owned())); - return Err(GenerationError::new("HTML+CSS", - "A `style' tag can either have S-expression CSS rules, or\ - a single string containing raw CSS be passed in.\n\ - A string was passed in, but excess expressions were passed \ - in after that!", - second_node.site())); - } - // Otherwise, write that raw CSS. - write!(f, "{}", string_node.value)?; - } else { - writeln!(f, "")?; - let css_fmt = CSSFormatter::new(rest.into_boxed_slice()); - let css_fmt = Box::leak(Box::new(css_fmt)); // FIXME: store formatter. - css_fmt.generate(f)?; - } - }, - "script" => { - // TODO: Generating JavaScript from S-expressions is not implemented. - // For now, just treat it as a raw-text tag (a la <pre>). - let sexp_fmt = SExpFormatter::new(rest.into_boxed_slice()); - let sexp_fmt = Box::leak(Box::new(sexp_fmt)); // TODO: Store. - sexp_fmt.generate(f)?; - }, - "math" | "svg" => { // <math> and <svg> are subsets of XML. - let xml_fmt = XMLFormatter::new(rest.into_boxed_slice()); - let xml_fmt = Box::leak(Box::new(xml_fmt)); // FIXME: store formatter. - xml_fmt.generate(f)?; - }, - _ => { // Tag contains regular old HTML. - let html_fmt = HTMLFormatter::new(rest.into_boxed_slice()); - let html_fmt = Box::leak(Box::new(html_fmt)); // FIXME: store formatter. - html_fmt.generate(f)?; - }, - } - // Closing tag should be equally as spaced as opening tag (?) - if end_token.leading_whitespace.is_empty() { - if is_first_node_on_next_line || tag == "style" { - write!(f, "{}", leading_whitespace)?; - } - } else { - write!(f, "{}", end_token.leading_whitespace)?; - } - - write!(f, "</{}>", tag)?; - }, - ParseNode::Attribute { ref site, .. } => - return Err(GenerationError::new("HTML", - "Unexpected attribute encountered.", site)) - } - Ok(()) -} - /// Get raw text in source-file between a `start_node` and some `end_token`. /// Does not work well if the `start_node` is a result of a macro expansion, /// it must be a plain node. diff --git a/src/assemble/mod.rs b/src/assemble/mod.rs @@ -1,5 +1,5 @@ use crate::parse::tokens::Site; -use std::{convert, fmt, error::Error}; +use std::{convert, error::Error, fmt::{self, Debug}}; use colored::*; use unicode_width::UnicodeWidthStr; @@ -8,16 +8,16 @@ use unicode_width::UnicodeWidthStr; /// each type of markup. #[derive(Debug, Clone)] pub struct GenerationError<'a> { - pub markup: String, + pub markup: &'static str, pub message: String, pub site: Site<'a>, } impl<'a> GenerationError<'a> { /// Create a new error given the ML, the message, and the site. - pub fn new(ml: &str, msg: &str, site: &Site<'a>) -> Self { + pub fn new(ml: &'static str, msg: &str, site: &Site<'a>) -> Self { Self { - markup: ml.to_owned(), + markup: ml, message: msg.to_owned(), site: site.to_owned(), } @@ -26,7 +26,7 @@ impl<'a> GenerationError<'a> { /// Implement fmt::Display for user-facing error output. impl<'a> fmt::Display for GenerationError<'a> { - fn fmt(&self, f : &mut fmt::Formatter<'_>) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let line_prefix = format!(" {} |", self.site.line); let line_view = self.site.line_slice(); writeln!(f, "{} {}", line_prefix, line_view)?; @@ -53,7 +53,7 @@ impl<'a> Error for GenerationError<'a> { } impl<'a> From<std::io::Error> for GenerationError<'a> { fn from(e: std::io::Error) -> Self { Self { - markup: String::from("<markup>"), // FIXME. + markup: "<markup>", message: format!("IO error: {}", e), site: Site::unknown(), } @@ -65,7 +65,7 @@ impl<'a> From<std::io::Error> for GenerationError<'a> { impl<'a> convert::From<fmt::Error> for GenerationError<'a> { fn from(e: fmt::Error) -> Self { Self { - markup: String::from("<markup>"), + markup: "<markup>", message: format!("Format buffer error: {}", e), site: Site::unknown(), } @@ -76,13 +76,12 @@ pub type Formatter<'a> = &'a mut dyn fmt::Write; /// Trait for all structs that can generate specific markup /// for the s-expression tree. -pub trait MarkupDisplay { +pub trait MarkupFormatter: Debug + CloneBox { // Required definitions: /// Similar to fmt in Display/Debug traits, takes in a /// mutable writable buffer, returns success or a specifc /// error while generating the markup. - fn generate(&self, buf : Formatter) - -> Result<(), GenerationError>; + fn generate(&self, buf: Formatter) -> Result<(), GenerationError>; /// Documentises the input, that's to say, it adds any /// extra meta-information to the generated markup, if /// the s-expressions your wrote ommited it. @@ -98,10 +97,29 @@ pub trait MarkupDisplay { } } +/// See: https://stackoverflow.com/a/30353928 +pub trait CloneBox { + fn clone_box(&self) -> *mut (); +} + +impl<'a, T> CloneBox for T where T: Clone + 'a { + fn clone_box(&self) -> *mut () { + Box::<T>::into_raw(Box::new(self.clone())) as *mut () + } +} + +impl<'a> Clone for Box<dyn MarkupFormatter + 'a> { + fn clone(&self) -> Box<dyn MarkupFormatter + 'a> { + unsafe { + *Box::from_raw(self.clone_box() as *mut Self) + } + } +} + /// Automatically implement fmt::Display as a wrapper around -/// MarkupDisplay::generate, but throws away the useful error message. -impl fmt::Display for dyn MarkupDisplay { - fn fmt(&self, f : &mut fmt::Formatter) -> fmt::Result { +/// MarkupFormatter::generate, but throws away the useful error message. +impl fmt::Display for dyn MarkupFormatter { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.generate(f).map_err(|_| fmt::Error) } } @@ -132,6 +150,8 @@ pub fn escape_xml(string: &str) -> String { /// Re-constitute original S-expressions. pub mod sexp; +/// Converts source into expanded plain-text. +pub mod text; /// XML generation. pub mod xml; /// HTML5 CSS generation. diff --git a/src/assemble/sexp.rs b/src/assemble/sexp.rs @@ -1,21 +1,69 @@ //! Output expanded source-code as identical looking to the original //! hand-written code as possible. +use std::cell::RefCell; -use super::{MarkupDisplay, GenerationError, Formatter}; +use super::{MarkupFormatter, GenerationError, Formatter}; use crate::parse::parser::{ParseNode, ParseTree}; #[derive(Debug, Clone)] pub struct SExpFormatter<'a> { - pub tree : ParseTree<'a>, + pub tree: ParseTree<'a>, + formatters: RefCell<Vec<Box<dyn MarkupFormatter + 'a>>>, } impl<'a> SExpFormatter<'a> { pub fn new(tree: ParseTree<'a>) -> Self { - Self { tree } + Self { + tree, + formatters: Default::default() + } + } + + fn register_formatter<Fmt: MarkupFormatter + 'a>(&self, formatter: Fmt) -> &'a Box<dyn MarkupFormatter + 'a> { + let fmts = self.formatters.as_ptr(); + unsafe { + (*fmts).push(Box::new(formatter)); + (*fmts).last().unwrap() + } + } + + fn generate_node(&self, f: Formatter, node: &ParseNode<'a>) -> Result<(), GenerationError<'a>> { + match node { + ParseNode::Symbol(node) + | ParseNode::Number(node) => { + write!(f, "{}", node.leading_whitespace)?; + write!(f, "{}", node.value)?; + }, + ParseNode::String(node) => { + // We actually don't want the rendered string, + // we want the escaped string, so we retrieve + // it from source. + write!(f, "{}", node.leading_whitespace)?; + write!(f, "{}", node.site.view())?; + }, + ParseNode::List { nodes, leading_whitespace, end_token, .. } => { + write!(f, "{}", leading_whitespace)?; + write!(f, "(")?; + let tree = nodes.to_vec(); + let sexp_fmt = SExpFormatter::new(tree.into_boxed_slice()); + let sexp_fmt = self.register_formatter(sexp_fmt); + sexp_fmt.generate(f)?; + write!(f, "{}", end_token.leading_whitespace)?; + write!(f, ")")?; + + }, + ParseNode::Attribute { keyword, node, leading_whitespace, .. } => { + write!(f, "{}", leading_whitespace)?; + write!(f, ":{}", keyword)?; + self.generate_node(f, node)?; + }, + } + Ok(()) } + } -impl<'a> MarkupDisplay for SExpFormatter<'a> { +impl<'a> MarkupFormatter for SExpFormatter<'a> { fn document(&self) -> Result<String, GenerationError> { self.display() } @@ -23,44 +71,8 @@ impl<'a> MarkupDisplay for SExpFormatter<'a> { fn generate(&self, f: Formatter) -> Result<(), GenerationError> { let mut tree_iter = self.tree.iter().peekable(); while let Some(node) = tree_iter.next() { - generate_node(f, node)?; + self.generate_node(f, node)?; } Ok(()) } } - -// TODO: Make this into a trait on `ParseNode`? -/// Write S-expression string for a single parse node into formatter. -fn generate_node<'a>(f: Formatter, node: &ParseNode<'a>) -> Result<(), GenerationError<'a>> { - match node { - ParseNode::Symbol(node) - | ParseNode::Number(node) => { - write!(f, "{}", node.leading_whitespace)?; - write!(f, "{}", node.value)?; - }, - ParseNode::String(node) => { - // We actually don't want the rendered string, - // we want the escaped string, so we retrieve - // it from source. - write!(f, "{}", node.leading_whitespace)?; - write!(f, "{}", node.site.view())?; - }, - ParseNode::List { nodes, leading_whitespace, end_token, .. } => { - write!(f, "{}", leading_whitespace)?; - write!(f, "(")?; - let tree = nodes.to_vec(); - let sexp_fmt = SExpFormatter::new(tree.into_boxed_slice()); - let sexp_fmt = Box::leak(Box::new(sexp_fmt)); // FIXME: Store. - sexp_fmt.generate(f)?; - write!(f, "{}", end_token.leading_whitespace)?; - write!(f, ")")?; - - }, - ParseNode::Attribute { keyword, node, leading_whitespace, .. } => { - write!(f, "{}", leading_whitespace)?; - write!(f, ":{}", keyword)?; - generate_node(f, node)?; - }, - } - Ok(()) -} diff --git a/src/assemble/text.rs b/src/assemble/text.rs @@ -0,0 +1,73 @@ +//! Output expanded plain-text. +//! Very similar to the source-code expanding SExp generator, +//! but this also converts strings and other constructs +//! into unescaped plain text as well. +use std::cell::RefCell; + +use super::{MarkupFormatter, GenerationError, Formatter}; +use crate::parse::parser::{ParseNode, ParseTree}; + +#[derive(Debug, Clone)] +pub struct PlainTextFormatter<'a> { + pub tree: ParseTree<'a>, + formatters: RefCell<Vec<Box<dyn MarkupFormatter + 'a>>>, +} + +impl<'a> PlainTextFormatter<'a> { + pub fn new(tree: ParseTree<'a>) -> Self { + Self { + tree, + formatters: Default::default(), + } + } + + fn register_formatter<Fmt: MarkupFormatter + 'a>(&self, formatter: Fmt) -> &'a Box<dyn MarkupFormatter + 'a> { + let fmts = self.formatters.as_ptr(); + unsafe { + (*fmts).push(Box::new(formatter)); + (*fmts).last().unwrap() + } + } + + fn generate_node(&self, f: Formatter, node: &ParseNode<'a>) -> Result<(), GenerationError<'a>> { + match node { + ParseNode::Symbol(node) + | ParseNode::Number(node) + | ParseNode::String(node) => { + write!(f, "{}", node.leading_whitespace)?; + write!(f, "{}", node.value)?; + }, + ParseNode::List { nodes, leading_whitespace, end_token, .. } => { + write!(f, "{}", leading_whitespace)?; + write!(f, "(")?; + let tree = nodes.to_vec(); + let text_fmt = PlainTextFormatter::new(tree.into_boxed_slice()); + let text_fmt = self.register_formatter(text_fmt); + text_fmt.generate(f)?; + write!(f, "{}", end_token.leading_whitespace)?; + write!(f, ")")?; + + }, + ParseNode::Attribute { keyword, node, leading_whitespace, .. } => { + write!(f, "{}", leading_whitespace)?; + write!(f, ":{}", keyword)?; + self.generate_node(f, node)?; + }, + } + Ok(()) + } +} + +impl<'a> MarkupFormatter for PlainTextFormatter<'a> { + fn document(&self) -> Result<String, GenerationError> { + self.display() + } + + fn generate(&self, f: Formatter) -> Result<(), GenerationError> { + let mut tree_iter = self.tree.iter().peekable(); + while let Some(node) = tree_iter.next() { + self.generate_node(f, node)?; + } + Ok(()) + } +} diff --git a/src/assemble/xml.rs b/src/assemble/xml.rs @@ -1,22 +1,133 @@ //! Assembles an expanded tree into valid XML. -use super::{escape_xml, MarkupDisplay, GenerationError, Formatter}; +use std::cell::RefCell; + +use super::{escape_xml, MarkupFormatter, GenerationError, Formatter}; use crate::parse::parser::{self, ParseNode, ParseTree}; #[derive(Debug, Clone)] pub struct XMLFormatter<'a> { - pub tree : ParseTree<'a>, + pub tree: ParseTree<'a>, + formatters: RefCell<Vec<Box<dyn MarkupFormatter + 'a>>>, } impl<'a> XMLFormatter<'a> { pub fn new(tree: ParseTree<'a>) -> Self { - Self { tree } + Self { + tree, + formatters: Default::default(), + } + } + + fn register_formatter<Fmt: MarkupFormatter + 'a>(&self, formatter: Fmt) -> &'a Box<dyn MarkupFormatter + 'a> { + let fmts = self.formatters.as_ptr(); + unsafe { + (*fmts).push(Box::new(formatter)); + (*fmts).last().unwrap() + } + } + + fn generate_xml_node(&self, f: Formatter, node: &ParseNode<'a>) -> Result<(), GenerationError<'a>> { + match node { + ParseNode::Symbol(node) + | ParseNode::Number(node) => { + write!(f, "{}", node.leading_whitespace)?; + write!(f, "{}", escape_xml(&node.value))?; + }, + ParseNode::String(node) => { + write!(f, "{}", node.leading_whitespace)?; + write!(f, "{}", escape_xml(&node.value))? + }, + ParseNode::List { nodes: list, leading_whitespace, end_token, .. } => { + write!(f, "{}", leading_whitespace)?; + let head = list.first(); + let tag: &str; // xml <tag> name. + if let Some(head_node) = head { + if let ParseNode::Symbol(head_symbol) = head_node { + tag = &head_symbol.value; + write!(f, "<{}", tag)?; + } else { + // Error, tags can only have symbol values. + return Err(GenerationError::new("XML", + "XML tags can only be given as symbols.", + head_node.site())); + } + } else { + // Error, empty tags not supported. + return Err(GenerationError::new("XML", + "Empty lists cannot be converted into a valid XML tag.", + node.site())); + } + + let mut rest = &list[1..]; + + // Declarations behave differently. + let front = tag.as_bytes()[0] as char; + if front == '!' || front == '?' { + while !rest.is_empty() { + if let Some(node) = rest[0].symbolic() { + write!(f, "{}", node.value)?; + } else if let attr@ParseNode::Attribute { .. } = &rest[0] { + write!(f, " {}", display_attribute(attr)?)?; + } else { + return Err(GenerationError::new("XML", + "Only identifiers and attributes are allowed in declarations.", + &rest[0].site())); + } + rest = &rest[1..]; + } + if front == '?' { + write!(f, " ?>")?; + } else { + write!(f, ">")?; + } + return Ok(()); + } + + while let Some(attr@ParseNode::Attribute { .. }) = rest.first() { + write!(f, " {}", display_attribute(&attr)?)?; + rest = &rest[1..]; + } + write!(f, ">")?; + + // See similar comment for HTML generation: + // We strip leading whitespace from the first child element in a tag. + // This is more natural w.r.t. the S-exp syntax. + let mut rest = rest.to_vec(); + let mut is_first_node_on_next_line = false; + if let Some(first_node) = rest.get_mut(0) { + is_first_node_on_next_line = first_node.leading_whitespace().contains('\n'); + if !is_first_node_on_next_line { + first_node.set_leading_whitespace("".to_owned()); + } + } + + let xml_fmt = XMLFormatter::new(rest.to_owned().into_boxed_slice()); + let xml_fmt = self.register_formatter(xml_fmt); + xml_fmt.generate(f)?; + + // Closing tag should be equally as spaced as opening tag (?) + if end_token.leading_whitespace.is_empty() { + if is_first_node_on_next_line || tag == "style" { + write!(f, "{}", leading_whitespace)?; + } + } else { + write!(f, "{}", end_token.leading_whitespace)?; + } + + write!(f, "</{}>", tag)?; + }, + _ => return Err(GenerationError::new("XML", + &format!("Unexpected {} node when generating.", node.node_type()), + &node.site())) + } + Ok(()) } } -pub const DEFAULT : &str = +pub const DEFAULT: &str = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"; -impl<'a> MarkupDisplay for XMLFormatter<'a> { +impl<'a> MarkupFormatter for XMLFormatter<'a> { fn document(&self) -> Result<String, GenerationError> { let mut doc = String::new(); if self.tree.is_empty() { @@ -43,113 +154,16 @@ impl<'a> MarkupDisplay for XMLFormatter<'a> { Ok(doc) } - fn generate(&self, f : Formatter) -> Result<(), GenerationError> { + fn generate(&self, f: Formatter) -> Result<(), GenerationError> { let mut tree_iter = self.tree.iter().peekable(); while let Some(node) = tree_iter.next() { - generate_xml_node(f, node)?; + self.generate_xml_node(f, node)?; } Ok(()) } } -fn generate_xml_node<'a>(f: Formatter, node: &'a ParseNode<'a>) -> Result<(), GenerationError<'a>> { - match node { - ParseNode::Symbol(node) - | ParseNode::Number(node) => { - write!(f, "{}", node.leading_whitespace)?; - write!(f, "{}", escape_xml(&node.value))?; - }, - ParseNode::String(node) => { - write!(f, "{}", node.leading_whitespace)?; - write!(f, "{}", escape_xml(&node.value))? - }, - ParseNode::List { nodes: list, leading_whitespace, end_token, .. } => { - write!(f, "{}", leading_whitespace)?; - let head = list.first(); - let tag: &str; // xml <tag> name. - if let Some(head_node) = head { - if let ParseNode::Symbol(head_symbol) = head_node { - tag = &head_symbol.value; - write!(f, "<{}", tag)?; - } else { - // Error, tags can only have symbol values. - return Err(GenerationError::new("XML", - "XML tags can only be given as symbols.", - head_node.site())); - } - } else { - // Error, empty tags not supported. - return Err(GenerationError::new("XML", - "Empty lists cannot be converted into a valid XML tag.", - node.site())); - } - - let mut rest = &list[1..]; - - // Declarations behave differently. - let front = tag.as_bytes()[0] as char; - if front == '!' || front == '?' { - while !rest.is_empty() { - if let Some(node) = rest[0].symbolic() { - write!(f, "{}", node.value)?; - } else if let attr@ParseNode::Attribute { .. } = &rest[0] { - write!(f, " {}", display_attribute(attr)?)?; - } else { - return Err(GenerationError::new("XML", - "Only identifiers and attributes are allowed in declarations.", - &rest[0].site())); - } - rest = &rest[1..]; - } - if front == '?' { - write!(f, " ?>")?; - } else { - write!(f, ">")?; - } - return Ok(()); - } - - while let Some(attr@ParseNode::Attribute { .. }) = rest.first() { - write!(f, " {}", display_attribute(&attr)?)?; - rest = &rest[1..]; - } - write!(f, ">")?; - - // See similar comment for HTML generation: - // We strip leading whitespace from the first child element in a tag. - // This is more natural w.r.t. the S-exp syntax. - let mut rest = rest.to_vec(); - let mut is_first_node_on_next_line = false; - if let Some(first_node) = rest.get_mut(0) { - is_first_node_on_next_line = first_node.leading_whitespace().contains('\n'); - if !is_first_node_on_next_line { - first_node.set_leading_whitespace("".to_owned()); - } - } - - let xml_fmt = XMLFormatter::new(rest.to_owned().into_boxed_slice()); - let xml_fmt = Box::leak(Box::new(xml_fmt)); // FIXME: store formatter. - xml_fmt.generate(f)?; - - // Closing tag should be equally as spaced as opening tag (?) - if end_token.leading_whitespace.is_empty() { - if is_first_node_on_next_line || tag == "style" { - write!(f, "{}", leading_whitespace)?; - } - } else { - write!(f, "{}", end_token.leading_whitespace)?; - } - - write!(f, "</{}>", tag)?; - }, - _ => return Err(GenerationError::new("XML", - &format!("Unexpected {} node when generating.", node.node_type()), - &node.site())) - } - Ok(()) -} - -fn display_attribute<'a>(attr: &'a parser::ParseNode<'a>) -> Result<String, GenerationError> { +fn display_attribute<'a>(attr: &parser::ParseNode<'a>) -> Result<String, GenerationError<'a>> { let parser::ParseNode::Attribute { keyword, node, .. } = attr else { panic!("Passed non-attribute to display_attribute.") }; diff --git a/src/bin.rs b/src/bin.rs @@ -1,5 +1,5 @@ use seam; -use seam::assemble::MarkupDisplay; +use seam::assemble::MarkupFormatter; use std::{io, env}; use std::path::PathBuf; @@ -7,21 +7,21 @@ use std::error::Error; use colored::*; -fn argument_fatal(msg : impl std::fmt::Display) -> ! { +fn argument_fatal(msg: impl std::fmt::Display) -> ! { eprintln!("{} {}", format!("[{}]", "**".red()).white().bold(), msg.to_string().bold()); std::process::exit(1) } -const SUPPORTED_TARGETS : [&str; 4] = ["sexp", "html", "xml", "css"]; +const SUPPORTED_TARGETS: [&str; 5] = ["text", "sexp", "html", "xml", "css"]; fn main() -> Result<(), Box<dyn Error>> { let mut args = env::args(); let _ = args.next(); // Discard. let mut files = Vec::new(); - let mut target = ""; + let mut target = String::from(""); let mut from_stdin = false; let mut is_doc = true; @@ -29,7 +29,7 @@ fn main() -> Result<(), Box<dyn Error>> { if arg.chars().nth(0) == Some('-') { if let Some(opt) = arg.split("--").nth(1) { if SUPPORTED_TARGETS.contains(&opt) { - target = Box::leak(opt.to_owned().into_boxed_str()); + target = opt.to_owned(); continue; } match opt { @@ -74,12 +74,12 @@ fn main() -> Result<(), Box<dyn Error>> { if from_stdin { let mut stdin = io::stdin(); let builder = seam::tree_builder_stream(&mut stdin)?; - generate_and_print(&builder, target, is_doc); + generate_and_print(&builder, &target, is_doc); } for file in files { let builder = seam::tree_builder_file(&file)?; - generate_and_print(&builder, target, is_doc); + generate_and_print(&builder, &target, is_doc); } @@ -94,12 +94,16 @@ fn generate_and_print<'a>(expander: &'a seam::parse::expander::Expander<'a>, tar std::process::exit(1); } }; - let fmt: Box<dyn MarkupDisplay> = match target { + let fmt: Box<dyn MarkupFormatter> = match target { + "text" => Box::new(seam::assemble::text::PlainTextFormatter::new(tree)), "sexp" => Box::new(seam::assemble::sexp::SExpFormatter::new(tree)), "html" => Box::new(seam::assemble::html::HTMLFormatter::new(tree)), "xml" => Box::new(seam::assemble::xml::XMLFormatter::new(tree)), "css" => Box::new(seam::assemble::css::CSSFormatter::new(tree)), _ => { + if SUPPORTED_TARGETS.contains(&target) { + unreachable!("bug: target `{}' is not covered", target); + } argument_fatal( format!("Target `{}', does not exist.", target)) } diff --git a/src/lib.rs b/src/lib.rs @@ -9,7 +9,7 @@ use parse::{expander, parser, lexer}; use std::{fs, io, path::Path}; -pub const VERSION: (u8, u8, u8) = (0, 2, 3); +pub const VERSION: (u8, u8, u8) = (0, 2, 4); pub fn tree_builder<'a, P: AsRef<Path>>(source_path: Option<P>, string: String) -> expander::Expander<'a> { diff --git a/src/parse/expander.rs b/src/parse/expander.rs @@ -1,4 +1,4 @@ -use super::parser::{Parser, ParseNode, ParseTree, Node}; +use super::parser::{Node, ParseNode, ParseTree, Parser}; use super::tokens::Site; use std::{ @@ -62,7 +62,7 @@ pub struct Macro<'a> { // in order to implement lexical scoping. impl<'a> Macro<'a> { - pub fn new(name : &str) -> Macro { + pub fn new(name: &str) -> Macro { Macro { name: name.to_string(), params: Box::new([]), @@ -77,6 +77,9 @@ pub type Scope<'a> = RefCell<HashMap<String, Rc<Macro<'a>>>>; // Can you believe #[derive(Debug, Clone)] pub struct Expander<'a> { parser: Parser, + subparsers: RefCell<Vec<Parser>>, + subcontexts: RefCell<Vec<Self>>, + invocations: RefCell<Vec<ParseNode<'a>>>, definitions: Scope<'a>, } @@ -84,7 +87,10 @@ impl<'a> Expander<'a> { pub fn new(parser: Parser) -> Self { Self { parser, - definitions: RefCell::new(HashMap::new()) + subparsers: RefCell::new(Vec::new()), + subcontexts: RefCell::new(Vec::new()), + invocations: RefCell::new(Vec::new()), + definitions: RefCell::new(HashMap::new()), } } @@ -93,6 +99,45 @@ impl<'a> Expander<'a> { self.parser.get_source() } + /// Add a subparser owned by the expander context. + fn register_parser(&'a self, parser: Parser) -> &'a Parser { + { + let mut parsers = self.subparsers.borrow_mut(); + parsers.push(parser); + } + self.latest_parser() + } + + /// Get the latest subparser added. + fn latest_parser(&'a self) -> &'a Parser { + let p = self.subparsers.as_ptr(); + unsafe { (*p).last().unwrap_or(&self.parser) } + } + + /// Create and register a subcontext built from the current context. + fn create_subcontext(&'a self) -> &'a Self { + { + let copy = self.clone(); + let mut contexts = self.subcontexts.borrow_mut(); + contexts.push(copy); + } + self.latest_context().unwrap() + } + + /// Get the latest subparser added. + fn latest_context(&'a self) -> Option<&'a Self> { + let contexts = self.subcontexts.as_ptr(); + unsafe { (*contexts).last() } + } + + fn register_invocation(&'a self, node: ParseNode<'a>) -> &'a ParseNode<'a> { + let invocations = self.invocations.as_ptr(); + unsafe { + (*invocations).push(node); + (*invocations).last().unwrap() + } + } + /// Update variable (macro) for this scope. fn insert_variable(&'a self, name: String, var: Rc<Macro<'a>>) { let mut defs = self.definitions.borrow_mut(); @@ -118,7 +163,7 @@ impl<'a> Expander<'a> { return Err(ExpansionError( format!("`%define` macro takes at least \ two (2) arguments ({} were given.", params.len()), - node.site().to_owned())); + node.owned_site())); }; // If head is atomic, we assign to a 'variable'. @@ -249,11 +294,8 @@ impl<'a> Expander<'a> { } } }; - // FIXME: Whatever! I tried to indicate with life-times that these - // live for the entier duration that the lex->parse->expand phases. - // Might as well just leak it, since it's going to live that long anyway. - let leaked_parser = Box::leak(Box::new(parser)); // Keep a Vec<Box<Parser>>? - let tree = match leaked_parser.parse() { + let parser = self.register_parser(parser); + let tree = match parser.parse() { Ok(tree) => tree, Err(error) => return Err(ExpansionError( format!("{}", error), node.site().to_owned())) @@ -325,12 +367,11 @@ impl<'a> Expander<'a> { let Some(mac) = self.get_variable(name) else { return Err(ExpansionError::new( - &format!("Macro not found (`{}').", name), node.site())) + &format!("Macro not found (`{}').", name), &node.owned_site())) }; // Instance of expansion subcontext. - // FIXME: Leaking again, maybe track subcontexts in superior context? - let subcontext = Box::leak(Box::new(self.clone())); // TODO: Create a stack Vec<Rc<Context>> and clone it here. + let subcontext = self.create_subcontext(); // Check enough arguments were given. if params.len() != mac.params.len() { return Err(ExpansionError( @@ -407,20 +448,19 @@ impl<'a> Expander<'a> { // Pathway: (%_ _ _) macro invocation. if let Some(ref symbol@ParseNode::Symbol(..)) = head { - // FIXME: This is just really bad... - let list_node = Box::leak(Box::new(node.clone())); + let node = self.register_invocation(node.clone()); let name = symbol.atomic().unwrap().value; if name.starts_with("%") { // Rebuild node... let name = &name[1..]; // Clean macro arguments from whitespace tokens. let params: Vec<ParseNode> = call.collect(); - return self.expand_invocation(name, list_node, params.into_boxed_slice()); + return self.expand_invocation(name, node, params.into_boxed_slice()); } } // Otherwise, if not a macro, just expand child nodes incase they are macros. let mut expanded_list = Vec::with_capacity(len); - expanded_list.extend(self.expand_node(head.clone().unwrap())?); + expanded_list.extend(self.expand_node(head.unwrap().clone())?); for elem in call { expanded_list.extend(self.expand_node(elem)?); } @@ -436,10 +476,10 @@ impl<'a> Expander<'a> { let mut expanded_nodes = self.expand_node(*node)?; let new_node = Box::new(expanded_nodes[0].clone()); expanded_nodes[0] = ParseNode::Attribute { - keyword, + keyword: keyword.clone(), node: new_node, - site, - leading_whitespace, + site: site.clone(), + leading_whitespace: leading_whitespace.clone(), }; Ok(expanded_nodes) }, @@ -450,7 +490,7 @@ impl<'a> Expander<'a> { pub fn expand_nodes(&'a self, tree: Box<[ParseNode<'a>]>) -> Result<ParseTree<'a>, ExpansionError<'a>> { let mut expanded = Vec::with_capacity(tree.len()); - for branch in tree.into_vec() { + for branch in tree { expanded.extend(self.expand_node(branch)?); } Ok(expanded.into_boxed_slice()) diff --git a/src/parse/lexer.rs b/src/parse/lexer.rs @@ -10,7 +10,7 @@ use unicode_width::UnicodeWidthStr; pub struct LexError<'a>(pub String, pub Site<'a>); impl<'a> fmt::Display for LexError<'a> { - fn fmt(&self, f : &mut fmt::Formatter<'_>) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let LexError(msg, site) = self; let line_prefix = format!(" {} |", site.line); let line_view = site.line_slice(); diff --git a/src/parse/parser.rs b/src/parse/parser.rs @@ -12,7 +12,7 @@ pub struct Node<'a> { } impl<'a> Node<'a> { - pub fn new(value: &str, site : &Site<'a>, leading_whitespace: &str) -> Self { + pub fn new(value: &str, site: &Site<'a>, leading_whitespace: &str) -> Self { Self { site: site.to_owned(), value: value.to_owned(), @@ -58,7 +58,7 @@ impl<'a> ParseNode<'a> { } } - pub fn site(&self) -> &Site { + pub fn site(&self) -> &Site<'a> { match self { Self::Symbol(ref node) | Self::Number(ref node) @@ -68,6 +68,16 @@ impl<'a> ParseNode<'a> { } } + pub fn owned_site(&self) -> Site { + match self { + Self::Symbol(node) + | Self::Number(node) + | Self::String(node) => node.site.clone(), + Self::List { site, .. } => site.clone(), + Self::Attribute { site, .. } => site.clone(), + } + } + pub fn leading_whitespace(&'a self) -> &'a str { match self { Self::Symbol(ref node) @@ -105,7 +115,7 @@ pub type ParseTree<'a> = Box<[ParseNode<'a>]>; pub struct ParseError<'a>(pub String, pub Site<'a>); impl<'a> fmt::Display for ParseError<'a> { - fn fmt(&self, f : &mut fmt::Formatter<'_>) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let ParseError(msg, site) = self; let line_prefix = format!(" {} |", site.line); let line_view = site.line_slice(); @@ -361,7 +371,7 @@ impl<'a> SearchTree<'a> for ParseTree<'a> { /// Pretty printing for parse nodes. #[cfg(feature="debug")] impl<'a> fmt::Display for ParseNode<'a> { - fn fmt(&self, f : &mut fmt::Formatter<'_>) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { ParseNode::Symbol(node) | ParseNode::Number(node) => write!(f, "{}", &node.value), diff --git a/src/parse/tokens.rs b/src/parse/tokens.rs @@ -24,9 +24,9 @@ impl<'a> Site<'a> { pub fn new(source: &'a str, source_code: &'a str, line: usize, - bytes_from_start : usize, + bytes_from_start: usize, bytes_from_start_of_line: usize, - bytes_span : usize) -> Self { + bytes_span: usize) -> Self { Self { source, source_code, @@ -83,7 +83,7 @@ impl<'a> Site<'a> { } impl<'a> Display for Site<'a> { - fn fmt(&self, f : &mut fmt::Formatter<'_>) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "(")?; write!(f, "{}:", self.source)?; write!(f, "{}:{}", self.line, self.line_column())?;