commit 49f18b078a290fdb5f90664db2a523f51c603021
parent 98c103924fd3dd5f666cafaf7193d639de6d444d
Author: Demonstrandum <moi@knutsen.co>
Date: Sun, 28 Jun 2020 12:18:20 +0100
Better error messages.
Diffstat:
9 files changed, 255 insertions(+), 87 deletions(-)
diff --git a/src/assemble/html.rs b/src/assemble/css.rs
diff --git a/src/assemble/html.rs b/src/assemble/html.rs
@@ -1,17 +1,19 @@
//! Assembles an expanded tree into valid HTML.
-use super::Documentise;
+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 tree : ParseTree,
}
impl HTMLFormatter {
pub fn new(tree : ParseTree) -> Self {
- Self { tree }
+ Self {
+ tree,
+ }
}
}
@@ -24,11 +26,11 @@ pub const DEFAULT : &str =
</body>\n\
</html>\n";
-impl Documentise for HTMLFormatter {
- fn document(&self) -> String {
+impl MarkupDisplay for HTMLFormatter {
+ fn document(&self) -> Result<String, GenerationError> {
let mut doc = String::new();
if self.tree.is_empty() {
- return String::from(DEFAULT);
+ return Ok(String::from(DEFAULT));
}
let stripped = parser::strip(&self.tree, true);
let mut current_node = stripped.get(0);
@@ -96,7 +98,7 @@ impl Documentise for HTMLFormatter {
}
// Populate.
- doc += &self.to_string();
+ self.generate(&mut doc)?;
doc += "<!-- Generated by SEAM, from symbolic-expressions \
into HTML. -->\n";
// Cloes all new tags.
@@ -104,26 +106,14 @@ impl Documentise for HTMLFormatter {
doc += "</body>\n";
}
if !html_tag {
- doc += "</html>";
+ doc += "</html>\n";
}
- 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 {
@@ -161,7 +151,9 @@ impl Display for HTMLFormatter {
if let Some(node) = rest[0].symbolic() {
write!(f, " {}", node.value)?;
} else {
- // TODO: Make and send error (can only be symbolic).
+ return Err(GenerationError::new("HTML",
+ "Non-symbolic item in declaration",
+ &rest[0].site()));
}
rest = &rest[1..];
}
@@ -175,17 +167,32 @@ impl Display for HTMLFormatter {
rest = &rest[1..];
} else {
// Error! Cannot be non atomic.
+ return Err(GenerationError::new("HTML",
+ "Attribute cannot contain non-atomic data.",
+ &(*attr.node).site()));
}
}
write!(f, ">")?;
let html_fmt = HTMLFormatter::new(rest.to_owned());
- write!(f, "{}", html_fmt)?;
+ html_fmt.generate(f)?;
write!(f, "</{}>", tag)?;
},
- _ => panic!("Uh {:?}", node),
+ _ => return Err(GenerationError::new("HTML",
+ "Unknown node encountered.", &node.site()))
}
}
- write!(f, "")
+ Ok(())
}
}
+
+
+// TODO: Convert special characters to HTML compatible ones.
+// e.g.
+// < => <
+// > => >
+// & => &
+// " => "
+// ! => !
+// etc.
+
diff --git a/src/assemble/mod.rs b/src/assemble/mod.rs
@@ -1,6 +1,77 @@
-pub trait Documentise {
- fn document(&self) -> String;
+use crate::parse::tokens::Site;
+use std::{convert, fmt, error::Error};
+
+use colored::*;
+
+#[derive(Debug, Clone)]
+pub struct GenerationError {
+ pub markup : String,
+ pub message : String,
+ pub site : Site
+}
+
+impl GenerationError {
+ pub fn new(ml : &str, msg : &str, site : &Site) -> Self {
+ Self {
+ markup: ml.to_owned(),
+ message: msg.to_owned(),
+ site: site.to_owned()
+ }
+ }
+ pub fn unknown(ml : &str) -> Self {
+ Self {
+ markup: ml.to_owned(),
+ message: String::from("Unknown generation error (bug)."),
+ site: Site::fake()
+ }
+ }
+}
+
+impl fmt::Display for GenerationError {
+ fn fmt(&self, f : &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{}: {}",
+ format!("[{}] Error Generating {} {}",
+ "**".red().bold(),
+ self.markup.bold(), self.site).white(),
+ self.message)
+ }
+}
+
+impl Error for GenerationError { }
+
+impl convert::From<fmt::Error> for GenerationError {
+ fn from(_ : fmt::Error) -> Self {
+ Self {
+ markup: String::from("Unknown"),
+ message: String::from(
+ "Unknown error while writing to format buffer"),
+ site: Site::fake()
+ }
+ }
+}
+
+pub trait MarkupDisplay {
+ // Required definitions:
+ fn generate(&self, buf : &mut dyn fmt::Write)
+ -> Result<(), GenerationError>;
+ fn document(&self) -> Result<String, GenerationError>;
+ // Default definitions:
+ fn display(&self) -> Result<String, GenerationError> {
+ let mut s_buf = String::new();
+ self.generate(&mut s_buf).map(|_| s_buf)
+ }
}
+impl fmt::Display for dyn MarkupDisplay {
+ fn fmt(&self, f : &mut fmt::Formatter<'_>) -> fmt::Result {
+ self.generate(f).map_err(|_| fmt::Error)
+ }
+}
+/// XML generation.
pub mod xml;
+
+/// HTML5 CSS generation.
+//pub mod css;
+/// HTML5 HTML generation.
pub mod html;
+
diff --git a/src/assemble/xml.rs b/src/assemble/xml.rs
@@ -1,8 +1,8 @@
//! Assembles an expanded tree into valid XML.
-use super::Documentise;
+use super::{MarkupDisplay, GenerationError};
use crate::parse::parser::{self, ParseNode, ParseTree};
-use std::fmt::{self, Display};
+use std::fmt;
#[derive(Debug, Clone)]
pub struct XMLFormatter {
@@ -14,12 +14,14 @@ impl XMLFormatter {
Self { tree }
}
- fn show_attribute(&self, attr : &parser::AttributeNode)
- -> Result<String, fmt::Error> {
+ fn display_attribute(&self, attr : &parser::AttributeNode)
+ -> Result<String, GenerationError> {
if let Some(symbol) = (*attr.node).atomic() {
Ok(format!("{}=\"{}\"", attr.keyword, symbol.value))
} else {
- Err(fmt::Error)
+ Err(GenerationError::new("XML",
+ "Attribute can only contain symbols, numbers or strings",
+ &(*attr.node).site()))
}
}
@@ -28,11 +30,11 @@ impl XMLFormatter {
pub const DEFAULT : &str =
"<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
-impl Documentise for XMLFormatter {
- fn document(&self) -> String {
+impl MarkupDisplay for XMLFormatter {
+ fn document(&self) -> Result<String, GenerationError> {
let mut doc = String::new();
if self.tree.is_empty() {
- return String::from(DEFAULT);
+ return Ok(String::from(DEFAULT));
}
let stripped = parser::strip(&self.tree, true);
let current_node = stripped.get(0);
@@ -52,26 +54,14 @@ impl Documentise for XMLFormatter {
}
// Populate.
- doc += &self.to_string();
+ self.generate(&mut doc)?;
doc += "<!-- Generated by SEAM, from symbolic-expressions \
into XML. -->\n";
- doc
+ Ok(doc)
}
-}
-
-
-// TODO: Convert special characters to HTML compatible ones.
-// e.g.
-// < => <
-// > => >
-// & => &
-// " => "
-// ! => !
-// etc.
-/// Converting the tree to an HTML string.
-impl Display for XMLFormatter {
- 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 {
@@ -113,7 +103,7 @@ impl Display for XMLFormatter {
if let Some(node) = rest[0].symbolic() {
write!(f, "{}", node.value)?;
} else if let ParseNode::Attribute(a) = &rest[0] {
- write!(f, " {}", self.show_attribute(a)?)?;
+ write!(f, " {}", self.display_attribute(a)?)?;
} else {
// Error.
}
@@ -129,18 +119,31 @@ impl Display for XMLFormatter {
}
while let Some(ParseNode::Attribute(attr)) = rest.first() {
- write!(f, " {}", self.show_attribute(&attr)?)?;
+ write!(f, " {}", self.display_attribute(&attr)?)?;
rest = &rest[1..];
}
write!(f, ">")?;
let xml_fmt = XMLFormatter::new(rest.to_owned());
- write!(f, "{}", xml_fmt)?;
+ xml_fmt.generate(f)?;
write!(f, "</{}>", tag)?;
},
- _ => panic!("Uh {:?}", node),
+ _ => return Err(GenerationError::new("XML",
+ "Unknonw node encountered.", &node.site()))
}
}
- write!(f, "")
+ Ok(())
}
}
+
+
+// TODO: Convert special characters to HTML compatible ones.
+// e.g.
+// < => <
+// > => >
+// & => &
+// " => "
+// ! => !
+// etc.
+
+
diff --git a/src/bin.rs b/src/bin.rs
@@ -1,5 +1,5 @@
use seam;
-use seam::assemble::Documentise;
+use seam::assemble::MarkupDisplay;
use std::env;
use std::path::PathBuf;
@@ -7,20 +7,16 @@ use std::error::Error;
use colored::*;
-fn argument_fatal(msg : &str) -> ! {
+fn argument_fatal(msg : impl std::fmt::Display) -> ! {
eprintln!("{} {}",
format!("[{}]", "**".red()).white().bold(),
- msg.bold());
+ msg.to_string().bold());
std::process::exit(1)
}
const SUPPORTED_TARGETS : [&str; 2] = ["html", "xml"];
fn main() -> Result<(), Box<dyn Error>> {
- let (major, minor, tiny) = seam::VERSION;
- eprintln!("{}", format!("SEAM v{}.{}.{}",
- major, minor, tiny).bold());
-
let mut args = env::args();
args.next(); // Discard.
@@ -33,10 +29,20 @@ fn main() -> Result<(), Box<dyn Error>> {
target = Box::leak(opt.to_owned().into_boxed_str());
}
continue;
+ } else if let Some(opt) = arg.split("-").nth(1) {
+ match opt {
+ "v" => {
+ let (major, minor, tiny) = seam::VERSION;
+ eprintln!("{}", format!("SEAM v{}.{}.{}",
+ major, minor, tiny).bold());
+ std::process::exit(0);
+ },
+ _ => argument_fatal(
+ format!("Unknown argument (`-{}').", opt))
+ }
}
let path = PathBuf::from(&arg);
if path.exists() {
- eprintln!("Reading file `{}'.", &path.display());
files.push(path);
}
}
@@ -49,7 +55,13 @@ fn main() -> Result<(), Box<dyn Error>> {
}
for file in files {
- let tree = seam::parse_file(&file)?;
+ let tree = match seam::parse_file(&file) {
+ Ok(tree) => tree,
+ Err(e) => {
+ eprintln!("{}", e);
+ std::process::exit(1)
+ }
+ };
#[cfg(feature="debug")]
eprintln!("{}", &tree
.iter().fold(String::new(),
@@ -63,9 +75,18 @@ fn main() -> Result<(), Box<dyn Error>> {
let fmt = seam::assemble::xml::XMLFormatter::new(tree);
fmt.document()
},
- _ => continue
+ _ => {
+ argument_fatal(
+ format!("Target `{}', does not exist.", target))
+ }
};
- print!("{}", result);
+ match result {
+ Ok(generated) => print!("{}", generated),
+ Err(e) => {
+ eprintln!("{}", e);
+ std::process::exit(1)
+ }
+ }
}
diff --git a/src/parse/lexer.rs b/src/parse/lexer.rs
@@ -8,8 +8,8 @@ pub struct LexError(tokens::Site, String);
impl fmt::Display for LexError {
fn fmt(&self, f : &mut fmt::Formatter<'_>) -> fmt::Result {
- write!(f, "[**] Lexical Error: `{}'.\nAt: {:#?}",
- self.1, self.0)
+ write!(f, "[**] Lexical Error {}: {}",
+ self.0, self.1)
}
}
@@ -42,6 +42,9 @@ fn character_kind(character : char, prev : Option<tokens::Kind>)
}
}
+// TODO: Post-tokeniser parenthesis balancer, give
+// nice and exact error messages.
+
pub fn lex<P: AsRef<Path>>(string : String, source : Option<P>)
-> Result<TokenStream, LexError> {
@@ -56,6 +59,7 @@ pub fn lex<P: AsRef<Path>>(string : String, source : Option<P>)
let mut token_start : usize = 0;
let mut current_kind = None;
let mut old_kind = None;
+ let mut string_open = false;
let mut escaped = false;
while bytes < eof {
@@ -71,20 +75,21 @@ pub fn lex<P: AsRef<Path>>(string : String, source : Option<P>)
let character = current_byte as char;
// Tripple quoted string:
- if character == '"' && &string[bytes..bytes + 3] == "\"\"\"" {
+ if character == '"'
+ && string.get(bytes..bytes + 3) == Some("\"\"\"") {
token_start = line_bytes;
let start_line = lines;
bytes += 3;
line_bytes += 3;
- while &string[bytes..bytes + 3] != "\"\"\"" {
- if string[bytes..].is_empty() {
- let mut site = tokens::Site::from_line(
- lines, line_bytes, 1);
- site.source = source
- .map(|e| e.as_ref().display().to_string());
- return Err(LexError(site,
- String::from("Unclosed tripple-quoted string.")));
+
+ let mut found_end_quote = false;
+
+ while let Some(quote) = string.get(bytes..bytes + 3) {
+ if quote == "\"\"\"" {
+ found_end_quote = true;
+ break;
}
+
let c = string.as_bytes()[bytes];
if c == '\n' as u8 {
lines += 1;
@@ -94,6 +99,17 @@ pub fn lex<P: AsRef<Path>>(string : String, source : Option<P>)
bytes += 1;
line_bytes += 1;
}
+
+ if !found_end_quote {
+ let mut site = tokens::Site::from_line(
+ lines, line_bytes, 1);
+ site.source = source
+ .map(|e| e.as_ref().display().to_string());
+ return Err(LexError(site,
+ String::from("Unclosed tripple-quoted string.")));
+ }
+
+
bytes += 3;
line_bytes += 3;
current_kind = None;
@@ -151,6 +167,7 @@ pub fn lex<P: AsRef<Path>>(string : String, source : Option<P>)
&& prev_kind != Some(tokens::Kind::String)
&& !escaped;
if string_start {
+ string_open = true;
current_kind = None;
}
@@ -181,6 +198,7 @@ pub fn lex<P: AsRef<Path>>(string : String, source : Option<P>)
peek_kind = None;
} else if peek_string {
peek_kind = None;
+ string_open = false;
}
// If we're on a whitespace, and there's a bracket (or quote) ahead,
@@ -254,6 +272,11 @@ pub fn lex<P: AsRef<Path>>(string : String, source : Option<P>)
}
escaped = false;
}
-
+ if string_open {
+ let mut site = tokens::Site::from_line(lines, line_bytes, 1);
+ site.source = source.map(|p| p.as_ref().display().to_string());
+ return Err(LexError(site,
+ "Unclosed double-quoted string.".to_string()))
+ }
Ok(tokens)
}
diff --git a/src/parse/parser.rs b/src/parse/parser.rs
@@ -19,7 +19,8 @@ impl Node {
#[derive(Debug, Clone)]
pub struct AttributeNode {
pub keyword : String,
- pub node : Box<ParseNode>
+ pub node : Box<ParseNode>,
+ pub site : Site
}
#[derive(Debug, Clone)]
@@ -47,6 +48,21 @@ impl ParseNode {
_ => None
}
}
+ pub fn site(&self) -> Site {
+ match self {
+ Self::Symbol(node)
+ | Self::Number(node)
+ | Self::String(node) => node.site.to_owned(),
+ Self::List(list) => {
+ if let Some(head) = list.first() {
+ head.site()
+ } else {
+ panic!("No empty lists should be allowed.")
+ }
+ },
+ Self::Attribute(attr) => attr.site.to_owned(),
+ }
+ }
}
pub type ParseTree = Vec<ParseNode>;
@@ -56,8 +72,8 @@ pub struct ParseError(pub String, pub Site);
impl fmt::Display for ParseError {
fn fmt(&self, f : &mut fmt::Formatter<'_>) -> fmt::Result {
- write!(f, "[**] Parse Error: `{}',\nAt: {:#?}",
- self.0, self.1)
+ write!(f, "[**] Parse Error {}: {}",
+ self.1, self.0)
}
}
@@ -82,6 +98,7 @@ pub fn parse(tokens : &[Token])
match token.kind {
Kind::LParen => {
// Parse list.
+ let open_paren = token.site.clone();
let mut slice = &tokens[1..];
if slice.is_empty() {
return Err(ParseError(
@@ -117,6 +134,11 @@ pub fn parse(tokens : &[Token])
slice = left;
}
slice = &slice[1..]; // Ignore last r-paren.
+ if elements.is_empty() {
+ // Empty lists have an invisible empty symbol in them.
+ let node = Node::new("", &open_paren);
+ elements.push(ParseNode::Symbol(node));
+ }
Ok((ParseNode::List(elements), slice))
},
Kind::Keyword => {
@@ -124,7 +146,8 @@ pub fn parse(tokens : &[Token])
let (node, mut slice) = parse(&tokens[1..])?;
let attribute = AttributeNode {
keyword: token.value[1..].to_owned(),
- node: Box::new(node)
+ node: Box::new(node),
+ site: token.site.to_owned()
};
// White space after attributes don't count.
if let Some(next) = slice.first() {
diff --git a/src/parse/tokens.rs b/src/parse/tokens.rs
@@ -1,3 +1,5 @@
+use std::fmt::{self, Display};
+
#[derive(Debug, Clone)]
pub struct Site {
pub source : Option<String>,
@@ -17,6 +19,15 @@ impl Site {
}
}
+ pub fn fake() -> Self {
+ Self {
+ source: None,
+ line: 0,
+ bytes_from_start: 0,
+ bytes_span: 0
+ }
+ }
+
pub fn from_line(line : usize,
bytes_from_start : usize,
bytes_span : usize) -> Self {
@@ -28,6 +39,16 @@ impl Site {
}
}
+impl Display for Site {
+ fn fmt(&self, f : &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "(")?;
+ if let Some(source) = &self.source {
+ write!(f, "`{}':", source)?;
+ }
+ write!(f, "{}:{})", self.line, self.bytes_from_start + 1)
+ }
+}
+
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Kind {
LParen,
diff --git a/test.sex b/test.sex
@@ -13,4 +13,3 @@
:alt "Cute cat"
:src "https://static.insider.com/image/5d24d6b921a861093e71fef3.jpg"
:width 300)))
-