seam

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

commit e1facb08b722bfef09ccf8ccd2acfb09adf11742
parent 49f18b078a290fdb5f90664db2a523f51c603021
Author: Demonstrandum <moi@knutsen.co>
Date:   Wed,  1 Jul 2020 01:00:37 +0100

Start CSS work.

Diffstat:
Msrc/assemble/css.rs | 234++++++++++++++++++++++++++-----------------------------------------------------
Msrc/assemble/html.rs | 4+---
Msrc/assemble/mod.rs | 24+++++++++++++++++++++++-
Msrc/bin.rs | 8+++++++-
Atest-css.sex | 6++++++
5 files changed, 113 insertions(+), 163 deletions(-)

diff --git a/src/assemble/css.rs b/src/assemble/css.rs @@ -1,191 +1,109 @@ -//! Assembles an expanded tree into valid HTML. -use super::Documentise; +//! Assembles an expanded tree into valid CSS. +use super::{GenerationError, MarkupDisplay}; use crate::parse::parser::{self, ParseNode, ParseTree}; -use std::fmt::{self, Display}; +use std::fmt; #[derive(Debug, Clone)] -pub struct HTMLFormatter { - pub tree : ParseTree +pub struct CSSFormatter { + pub tree : ParseTree, } -impl HTMLFormatter { +impl CSSFormatter { pub fn new(tree : ParseTree) -> Self { Self { tree } } } -pub const DEFAULT : &str = - "<!DOCTYPE html>\n\ - <html>\n\ - <head></head>\n\ - <body>\n\ - <!-- Generated by SEAM (empty file) -->\n\ - </body>\n\ - </html>\n"; +pub const DEFAULT : &str = "\n"; -impl Documentise for HTMLFormatter { - fn document(&self) -> String { +/// 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(property : &str, node : &ParseNode) -> String { + String::from("x") +} + +impl MarkupDisplay for CSSFormatter { + fn document(&self) -> Result<String, GenerationError> { let mut doc = String::new(); if self.tree.is_empty() { - return String::from(DEFAULT); - } - let stripped = parser::strip(&self.tree, true); - let mut current_node = stripped.get(0); - - // Check if <!DOCTYPE html> exists. - let mut has_declaration = false; - if let Some(ParseNode::List(list)) = current_node.as_ref() { - if let Some(ParseNode::Symbol(declaration)) = list.get(0) { - if declaration.value.to_lowercase() == "!doctype" { - has_declaration = true; - } - } - } - - if has_declaration { - current_node = stripped.get(1); - } else { - doc += "<!DOCTYPE html>\n" - } - - // Check if <html></html> root object exists. - let mut html_tag = false; - if let Some(ParseNode::List(list)) = current_node.as_ref() { - if let Some(ParseNode::Symbol(root_tag)) = &list.get(0) { - if root_tag.value.to_lowercase() == "html" { - html_tag = true; - } - } - } - - if !html_tag { - doc += "<html>\n"; - } - - // Check if <head></head> exists. - let mut head_tag = false; - if let Some(ParseNode::List(list)) = current_node.as_ref() { - if let Some(ParseNode::List(head_list)) = &list.get(1) { - if let Some(ParseNode::Symbol(head)) = &head_list.get(0) { - if head.value.to_lowercase() == "head" { - head_tag = true; - } - } - } - } - - if !head_tag { - doc += "<head></head>\n"; - } - - // Check if body exists, if not, make it, and populate it. - let mut body_tag = false; - if let Some(ParseNode::List(list)) = current_node.as_ref() { - if let Some(ParseNode::List(body_list)) = &list.get(2) { - if let Some(ParseNode::Symbol(body)) = &body_list.get(0) { - if body.value.to_lowercase() == "body" { - body_tag = true; - } - } - } + return Ok(String::from(DEFAULT)); } - - if !body_tag { - doc += "<body>\n"; - } - - // Populate. - doc += &self.to_string(); - doc += "<!-- Generated by SEAM, from symbolic-expressions \ - into HTML. -->\n"; - // Cloes all new tags. - if !body_tag { - doc += "</body>\n"; - } - if !html_tag { - doc += "</html>"; - } - - doc + self.generate(&mut doc)?; + Ok(doc) } -} - -// TODO: Convert special characters to HTML compatible ones. -// e.g. -// < => &lt; -// > => &gt; -// & => &amp; -// " => &quot; -// ! => &excl; -// etc. - -/// Converting the tree to an HTML string. -impl Display for HTMLFormatter { - fn fmt(&self, f : &mut fmt::Formatter<'_>) -> fmt::Result { + fn generate(&self, f : &mut dyn fmt::Write) + -> Result<(), GenerationError> { let mut tree_iter = self.tree.iter().peekable(); while let Some(node) = tree_iter.next() { match node { - ParseNode::Symbol(node) - | ParseNode::Number(node) => { - // If symbol ahead is so-called "symbolic", we can - // infere there was a space between them. - write!(f, "{}", node.value)?; - if let Some(peek) = tree_iter.peek() { - if peek.symbolic().is_some() { - write!(f, " ")? - } - } - }, - ParseNode::String(node) => write!(f, "{}", node.value)?, ParseNode::List(list) => { - let head = list.first(); - let mut tag = ""; - 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. - } - } else { - // Error, empty tags not supported. + let stripped = parser::strip(list, false); + let iter = stripped.iter(); + let mut prop_i = 0; // Index of first property. + let mut selectors = iter.clone() + .take_while(|n| { prop_i += 1; n.atomic().is_some() }) + .map(|n| n.atomic().unwrap()) // We've checked. + .peekable(); + + // Check we were actually provided with + // some selectors. + if selectors.peek().is_none() { + return Err(GenerationError::new("CSS", + "CSS selector(s) missing. \ + Expected a symbol node, none was found!", + &node.site())); } - - 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 { - // TODO: Make and send error (can only be symbolic). - } - rest = &rest[1..]; - } - write!(f, ">")?; - continue; + // Join the selectors togeher. + for selector in selectors { + write!(f, "{} ", selector.value)?; } + writeln!(f, "{{")?; + + let properties = iter.skip(prop_i - 1); - while let Some(ParseNode::Attribute(attr)) = rest.first() { - if let Some(atom) = (*attr.node).atomic() { - write!(f, " {}=\"{}\"", attr.keyword, atom.value)?; - rest = &rest[1..]; + for property in properties { + if let ParseNode::Attribute(property) = property { + let value = &property.node; + writeln!(f, " {}: {};", + &property.keyword, + css_value(&property.keyword, value))?; } else { - // Error! Cannot be non atomic. + return Err(GenerationError::new("CSS", + "CSS property-value pairs must be in the \ + form of attributes, i.e. `:property value`.", + &property.site())); } } - write!(f, ">")?; + writeln!(f, "}}\n")?; - let html_fmt = HTMLFormatter::new(rest.to_owned()); - write!(f, "{}", html_fmt)?; - write!(f, "</{}>", tag)?; }, - _ => panic!("Uh {:?}", node), + ParseNode::Attribute(attr) => { + let site = attr.site.to_owned(); + return Err(GenerationError::new("CSS", + "Attribute not expected here, CSS documents \ + are supposed to be a series of selectors \ + and property-value pairs, wrapped in parentheses.", + &site)); + }, + ParseNode::Symbol(node) + | ParseNode::Number(node) + | ParseNode::String(node) => { + let site = node.site.to_owned(); + if node.value.trim().is_empty() { + continue; + } + return Err(GenerationError::new("CSS", + "Symbolic node not expected here, CSS documents \ + are supposed to be a series of selectors \ + and property-value pairs, wrapped in parentheses.", + &site)); + } } } - write!(f, "") + Ok(()) } } + diff --git a/src/assemble/html.rs b/src/assemble/html.rs @@ -11,9 +11,7 @@ pub struct HTMLFormatter { impl HTMLFormatter { pub fn new(tree : ParseTree) -> Self { - Self { - tree, - } + Self { tree } } } diff --git a/src/assemble/mod.rs b/src/assemble/mod.rs @@ -3,6 +3,8 @@ use std::{convert, fmt, error::Error}; use colored::*; +/// Error type for specific errors with generating +/// each type of markup. #[derive(Debug, Clone)] pub struct GenerationError { pub markup : String, @@ -11,6 +13,7 @@ pub struct GenerationError { } impl GenerationError { + /// Create a new error given the ML, the message, and the site. pub fn new(ml : &str, msg : &str, site : &Site) -> Self { Self { markup: ml.to_owned(), @@ -18,6 +21,8 @@ impl GenerationError { site: site.to_owned() } } + /// When an error cannot be given a location, + /// or exact point of failure. pub fn unknown(ml : &str) -> Self { Self { markup: ml.to_owned(), @@ -27,6 +32,7 @@ impl GenerationError { } } +/// Implement fmt::Display for user-facing error output. impl fmt::Display for GenerationError { fn fmt(&self, f : &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}: {}", @@ -37,8 +43,11 @@ impl fmt::Display for GenerationError { } } +/// Implements std::error::Error. impl Error for GenerationError { } +/// An fmt::Error can be cast to an equally horribly +/// ambiguous GenerationError. impl convert::From<fmt::Error> for GenerationError { fn from(_ : fmt::Error) -> Self { Self { @@ -50,18 +59,31 @@ impl convert::From<fmt::Error> for GenerationError { } } +/// Trait for all structs that can generate specific markup +/// for the s-expression tree. pub trait MarkupDisplay { // 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 : &mut dyn fmt::Write) -> 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. + /// e.g. All XML gets a `<?xml ... ?>` tag added to it. fn document(&self) -> Result<String, GenerationError>; // Default definitions: + /// Directly converts the s-expressions into a string + /// containing the markup, unless there was an error. fn display(&self) -> Result<String, GenerationError> { let mut s_buf = String::new(); self.generate(&mut s_buf).map(|_| s_buf) } } +/// 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 { self.generate(f).map_err(|_| fmt::Error) @@ -71,7 +93,7 @@ impl fmt::Display for dyn MarkupDisplay { pub mod xml; /// HTML5 CSS generation. -//pub mod css; +pub mod css; /// HTML5 HTML generation. pub mod html; diff --git a/src/bin.rs b/src/bin.rs @@ -14,7 +14,7 @@ fn argument_fatal(msg : impl std::fmt::Display) -> ! { std::process::exit(1) } -const SUPPORTED_TARGETS : [&str; 2] = ["html", "xml"]; +const SUPPORTED_TARGETS : [&str; 3] = ["html", "xml", "css"]; fn main() -> Result<(), Box<dyn Error>> { let mut args = env::args(); @@ -24,6 +24,7 @@ fn main() -> Result<(), Box<dyn Error>> { let mut target = ""; for arg in args { + 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()); @@ -41,6 +42,7 @@ fn main() -> Result<(), Box<dyn Error>> { format!("Unknown argument (`-{}').", opt)) } } + } let path = PathBuf::from(&arg); if path.exists() { files.push(path); @@ -75,6 +77,10 @@ fn main() -> Result<(), Box<dyn Error>> { let fmt = seam::assemble::xml::XMLFormatter::new(tree); fmt.document() }, + "css" => { + let fmt = seam::assemble::css::CSSFormatter::new(tree); + fmt.document() + }, _ => { argument_fatal( format!("Target `{}', does not exist.", target)) diff --git a/test-css.sex b/test-css.sex @@ -0,0 +1,6 @@ +(a b c + :width 2 + :heigh 3) + +(x #y .z + :color (rgb 2 (calc (+ 3 1)) 4))