commit e1facb08b722bfef09ccf8ccd2acfb09adf11742
parent 49f18b078a290fdb5f90664db2a523f51c603021
Author: Demonstrandum <moi@knutsen.co>
Date: Wed, 1 Jul 2020 01:00:37 +0100
Start CSS work.
Diffstat:
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.
-// < => <
-// > => >
-// & => &
-// " => "
-// ! => !
-// 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))