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:
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())?;