commit 98c103924fd3dd5f666cafaf7193d639de6d444d
parent 97069af77a3d0be2e4390b0e4e8d90beeec99396
Author: Demonstrandum <moi@knutsen.co>
Date:   Sat, 27 Jun 2020 11:26:42 +0100
Added XML converter.
Diffstat:
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.
+//      <  =>  <
+//      >  =>  >
+//      &  =>  &
+//      "  =>  "
+//      !  =>  !
+//      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)))