seam

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

commit 98c103924fd3dd5f666cafaf7193d639de6d444d
parent 97069af77a3d0be2e4390b0e4e8d90beeec99396
Author: Demonstrandum <moi@knutsen.co>
Date:   Sat, 27 Jun 2020 11:26:42 +0100

Added XML converter.

Diffstat:
Asamples/css-concept.sex | 26++++++++++++++++++++++++++
Asamples/js-concept.sex | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msamples/xml-example-1.sex | 4++--
Asamples/xml-schema.sex | 4++++
Msrc/assemble/html.rs | 11++++++-----
Msrc/assemble/mod.rs | 1+
Asrc/assemble/xml.rs | 146+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/bin.rs | 20+++++++++++++-------
Mtest.html | 4++--
Mtest.sex | 2+-
10 files changed, 258 insertions(+), 17 deletions(-)

diff --git a/samples/css-concept.sex b/samples/css-concept.sex @@ -0,0 +1,26 @@ +;; Very generally: +;a b c { +; x: y; +; z: w; +;} + +;; Becomes: +(a b c :x y + :z w) + +;; Take a proper example: + +;p #para-id { +; width: calc(3em + 6px); +; color: green; +; margin: 3px, auto, 4px, 2px; +; position: absolute; +;} + +;; Becomes: +(p #para-id + :width (calc (+ 3em 6px)) + :color "green" + :margin (3px auto 4px 2px) + :position absolute) + diff --git a/samples/js-concept.sex b/samples/js-concept.sex @@ -0,0 +1,57 @@ + +;; Example JavaScript: + +;import * as BC from '../lib/BasicCanvas.js'; +;import {grid, ellipse} from '../lib/BasicShapes.js'; +; +;use(BC); +; +;const sketch = canvas_id('sketch'); +; +;sketch.dimensions(400, 400); +;sketch.translate(200, 200); +;sketch.scale(30, -30); +; +;sketch.fill = Color`transparent`; +;sketch.stroke = Color`black`; +; +;const gaussian = (mean = 0, σ = 1) => { +; const ϑ = Math.random() * Math.TAU; +; const ρ = Math.sqrt(-2 * Math.log(1 - Math.random())); +; const radius = σ * ρ; +; +; return Polar(radius, ϑ, P(mean, mean)); +;}; +; +;const points = []; +;const r = 0.4; + + +(import * as BC from "../lib/BasicCanvas.js") +(import { grid, ellipse } from "../lib/BasicCanvas.js") + +(use BC) + +(const sketch (canvas_id "sketch")) + +;; We have toistinguish operators from function calls. +((. sketch dimensions) 400 400) +(sketch.translate 200 200) ;; Alternative (won't work with spaces). +((. sketch scale) 30 -30) + +(= (. sketch fill) (Color `transparent`)) +(= sketch.stroke (Color `black`)) + +(const gaussian (=> (= mean 0) (= σ 1) + (const ϑ (* (. Math TAU) ((. Math random))) + (const ρ (Math.sqrt (* -2 (Math.log (- 1 (Math.random)))))) + (const radius (* σ ρ)) + + (Polar radius ϑ (P mean mean))))) + +(const points []) +(const r 0.4) + + + + diff --git a/samples/xml-example-1.sex b/samples/xml-example-1.sex @@ -1,7 +1,7 @@ (message :status urgernt (to Tove) (from Jani) - (heading A \"reminder\" \(Again!\) for you) - (body Don't forget me this weekend!)) + (heading A reminder \(Again!\) for you) + (body Don't \"forget\" me this weekend!)) diff --git a/samples/xml-schema.sex b/samples/xml-schema.sex @@ -0,0 +1,4 @@ +(?xml :version 1.0 :encoding ISO-8859-1) +(xs:schema :xmlns:xs "http://www.w3.org/2001/XMLSchema") +;; Symbols can have colones (:) inside them, just not at the start, +;; because that's an attribute (they can be escaped with '\'). diff --git a/src/assemble/html.rs b/src/assemble/html.rs @@ -19,8 +19,10 @@ pub const DEFAULT : &str = "<!DOCTYPE html>\n\ <html>\n\ <head></head>\n\ - <body></body>\n\ - </html>"; + <body>\n\ + <!-- Generated by SEAM (empty file) -->\n\ + </body>\n\ + </html>\n"; impl Documentise for HTMLFormatter { fn document(&self) -> String { @@ -94,10 +96,9 @@ impl Documentise for HTMLFormatter { } // Populate. - doc += "<!-- Generated from symbolic-expressions \ - into HTML. -->\n"; doc += &self.to_string(); - + doc += "<!-- Generated by SEAM, from symbolic-expressions \ + into HTML. -->\n"; // Cloes all new tags. if !body_tag { doc += "</body>\n"; diff --git a/src/assemble/mod.rs b/src/assemble/mod.rs @@ -2,4 +2,5 @@ pub trait Documentise { fn document(&self) -> String; } +pub mod xml; pub mod html; diff --git a/src/assemble/xml.rs b/src/assemble/xml.rs @@ -0,0 +1,146 @@ +//! Assembles an expanded tree into valid XML. +use super::Documentise; +use crate::parse::parser::{self, ParseNode, ParseTree}; + +use std::fmt::{self, Display}; + +#[derive(Debug, Clone)] +pub struct XMLFormatter { + pub tree : ParseTree +} + +impl XMLFormatter { + pub fn new(tree : ParseTree) -> Self { + Self { tree } + } + + fn show_attribute(&self, attr : &parser::AttributeNode) + -> Result<String, fmt::Error> { + if let Some(symbol) = (*attr.node).atomic() { + Ok(format!("{}=\"{}\"", attr.keyword, symbol.value)) + } else { + Err(fmt::Error) + } + } + +} + +pub const DEFAULT : &str = + "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"; + +impl Documentise for XMLFormatter { + fn document(&self) -> String { + let mut doc = String::new(); + if self.tree.is_empty() { + return String::from(DEFAULT); + } + let stripped = parser::strip(&self.tree, true); + let current_node = stripped.get(0); + + // Check if declaration 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() == "?xml" { + has_declaration = true; + } + } + } + + if !has_declaration { + doc += DEFAULT; + } + + // Populate. + doc += &self.to_string(); + doc += "<!-- Generated by SEAM, from symbolic-expressions \ + into XML. -->\n"; + 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 XMLFormatter { + fn fmt(&self, f : &mut fmt::Formatter<'_>) -> fmt::Result { + let mut tree_iter = self.tree.iter().peekable(); + while let Some(node) = tree_iter.next() { + match node { + ParseNode::Symbol(node) + | ParseNode::Number(node) => { + // If the node 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 mut rest = &list[1..]; + + // Declarations behave differently. + let front = tag.as_bytes()[0] as char; + if front == '!' || front == '?' { + while !rest.is_empty() { + if let ParseNode::List(_list) = &rest[0] { + // TODO: Throw error. + } else { + 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)?)?; + } else { + // Error. + } + } + rest = &rest[1..]; + } + if front == '?' { + write!(f, " ?>")?; + } else { + write!(f, ">")?; + } + continue; + } + + while let Some(ParseNode::Attribute(attr)) = rest.first() { + write!(f, " {}", self.show_attribute(&attr)?)?; + rest = &rest[1..]; + } + write!(f, ">")?; + + let xml_fmt = XMLFormatter::new(rest.to_owned()); + write!(f, "{}", xml_fmt)?; + write!(f, "</{}>", tag)?; + }, + _ => panic!("Uh {:?}", node), + } + } + write!(f, "") + } +} diff --git a/src/bin.rs b/src/bin.rs @@ -14,7 +14,7 @@ fn argument_fatal(msg : &str) -> ! { std::process::exit(1) } -const SUPPORTED_TARGETS : [&str; 1] = ["html"]; +const SUPPORTED_TARGETS : [&str; 2] = ["html", "xml"]; fn main() -> Result<(), Box<dyn Error>> { let (major, minor, tiny) = seam::VERSION; @@ -54,14 +54,20 @@ fn main() -> Result<(), Box<dyn Error>> { eprintln!("{}", &tree .iter().fold(String::new(), |acc, s| acc + "\n" + &s.to_string())); - if target == "html" { - let fmt = seam::assemble::html::HTMLFormatter::new(tree); - let result = fmt.document(); - println!("{}", result); - } + let result = match target { + "html" => { + let fmt = seam::assemble::html::HTMLFormatter::new(tree); + fmt.document() + }, + "xml" => { + let fmt = seam::assemble::xml::XMLFormatter::new(tree); + fmt.document() + }, + _ => continue + }; + print!("{}", result); } - eprintln!("All files read and converted."); Ok(()) } diff --git a/test.html b/test.html @@ -1,4 +1,3 @@ -<!-- Generated from symbolic-expressions into HTML. --> <!DOCTYPE html> <html lang="en"><head><title>Example HTML Document</title></head> <body><p id="hello">Hello, World!</p> @@ -6,5 +5,6 @@ <h1>A (big) Header!</h1> <p>Yet some more <span style="color: red">text</span> <3</p> <p>Hello<span style="color: green">World</span>!</p> -<img alt="Cat" src="https://static.insider.com/image/5d24d6b921a861093e71fef3.jpg" width="300"></img></body></html> +<img alt="Cute cat" src="https://static.insider.com/image/5d24d6b921a861093e71fef3.jpg" width="300"></img></body></html> +<!-- Generated by SEAM, from symbolic-expressions into HTML. --> diff --git a/test.sex b/test.sex @@ -10,7 +10,7 @@ (span :style "color: red" text) <3) (p Hello(span :style "color: green" World)!) (img - :alt Cat + :alt "Cute cat" :src "https://static.insider.com/image/5d24d6b921a861093e71fef3.jpg" :width 300)))