Mike Gerwitz

Activist for User Freedom

aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--tamer/src/ir/asg/ident.rs2
-rw-r--r--tamer/src/ir/asg/mod.rs2
-rw-r--r--tamer/src/ld/poc.rs333
-rw-r--r--tamer/src/obj/mod.rs1
-rw-r--r--tamer/src/obj/xmle/mod.rs69
-rw-r--r--tamer/src/obj/xmle/writer/mod.rs47
-rw-r--r--tamer/src/obj/xmle/writer/writer.rs320
-rw-r--r--tamer/src/obj/xmle/writer/xmle.rs749
8 files changed, 1252 insertions, 271 deletions
diff --git a/tamer/src/ir/asg/ident.rs b/tamer/src/ir/asg/ident.rs
index 40386cd..a8291ae 100644
--- a/tamer/src/ir/asg/ident.rs
+++ b/tamer/src/ir/asg/ident.rs
@@ -214,7 +214,7 @@ impl<'i> TryFrom<&SymAttrs<'i>> for IdentKind {
/// a value of `2` indicates a matrix;
/// and a value of `n` indicates a multi-dimensional array of
/// depth `n`.
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
pub struct Dim(u8);
/// Underlying datatype of identifier.
diff --git a/tamer/src/ir/asg/mod.rs b/tamer/src/ir/asg/mod.rs
index ed364b1..0e49eb2 100644
--- a/tamer/src/ir/asg/mod.rs
+++ b/tamer/src/ir/asg/mod.rs
@@ -185,7 +185,7 @@ mod ident;
mod object;
pub use graph::{Asg, AsgResult, ObjectRef};
-pub use ident::IdentKind;
+pub use ident::{Dim, IdentKind};
pub use object::{FragmentText, Object, Source};
/// Default concrete ASG implementation.
diff --git a/tamer/src/ld/poc.rs b/tamer/src/ld/poc.rs
index 7dd764b..53f9f7b 100644
--- a/tamer/src/ld/poc.rs
+++ b/tamer/src/ld/poc.rs
@@ -21,16 +21,16 @@
use crate::global;
use crate::ir::asg::IdentKind;
use crate::ir::asg::{Asg, DefaultAsg, Object, ObjectRef, Source};
+use crate::obj::xmle::writer::{Sections, XmleWriter};
use crate::obj::xmlo::reader::{XmloError, XmloEvent, XmloReader};
use crate::sym::{DefaultInterner, Interner, Symbol};
use fxhash::{FxHashMap, FxHashSet};
use petgraph::visit::DfsPostOrder;
-use quick_xml::events::{BytesEnd, BytesStart, BytesText, Event};
-use quick_xml::Writer;
use std::convert::TryInto;
use std::error::Error;
use std::fs;
-use std::io::{BufReader, Write};
+use std::io::BufReader;
+use std::io::Cursor;
type LinkerAsg<'i> = DefaultAsg<'i, global::ProgIdentSize>;
type LinkerObjectRef = ObjectRef<global::ProgIdentSize>;
@@ -74,14 +74,14 @@ pub fn main() -> Result<(), Box<dyn Error>> {
.filter_map(|sym| depgraph.lookup(sym)),
);
- let sorted = sort_deps(&depgraph, &roots);
+ let mut sorted = sort_deps(&depgraph, &roots);
//println!("Sorted ({}): {:?}", sorted.len(), sorted);
output_xmle(
&depgraph,
&interner,
- sorted,
+ &mut sorted,
name.expect("missing root package name"),
relroot.expect("missing root package relroot"),
)?;
@@ -185,10 +185,9 @@ fn load_xmlo<'a, 'i, I: Interner<'i>>(
Ok(XmloEvent::Fragment(sym, text)) => {
let result = depgraph.set_fragment(
- depgraph.lookup(sym).expect(&format!(
- "missing symbol for fragment: {}",
- sym
- )),
+ depgraph.lookup(sym).unwrap_or_else(|| {
+ panic!("missing symbol for fragment: {}", sym)
+ }),
text,
);
@@ -238,30 +237,14 @@ fn load_xmlo<'a, 'i, I: Interner<'i>>(
}
}
-type ObjectVec<'a, 'i> = Vec<&'a Object<'i>>;
-
-// Note that the classifier has nothing in it anymore; it's only there for
-// API compability, so we don't include it here.
-#[derive(Default)]
-struct SortedDeps<'a, 'i> {
- map: ObjectVec<'a, 'i>,
- retmap: ObjectVec<'a, 'i>,
- meta: ObjectVec<'a, 'i>,
- worksheet: ObjectVec<'a, 'i>,
- params: ObjectVec<'a, 'i>,
- types: ObjectVec<'a, 'i>,
- funcs: ObjectVec<'a, 'i>,
- rater: ObjectVec<'a, 'i>,
-}
-
fn sort_deps<'a, 'i>(
depgraph: &'a LinkerAsg<'i>,
roots: &Vec<LinkerObjectRef>,
-) -> SortedDeps<'a, 'i> {
+) -> Sections<'a, 'i> {
// @type=meta, @preproc:elig-class-yields
// @type={ret}map{,:head,:tail}
- let mut deps: SortedDeps = Default::default();
+ let mut deps: Sections = Sections::new();
// This is technically a topological sort, but functions have
// cycles. Once we have more symbol metadata, we can filter them out
@@ -282,18 +265,18 @@ fn sort_deps<'a, 'i>(
match ident {
Object::Ident(_, kind, _)
| Object::IdentFragment(_, kind, _, _) => match kind {
- IdentKind::Meta => deps.meta.push(ident),
- IdentKind::Worksheet => deps.worksheet.push(ident),
- IdentKind::Param(_, _) => deps.params.push(ident),
- IdentKind::Type(_) => deps.types.push(ident),
- IdentKind::Func(_, _) => deps.funcs.push(ident),
+ IdentKind::Meta => deps.meta.push_body(ident),
+ IdentKind::Worksheet => deps.worksheet.push_body(ident),
+ IdentKind::Param(_, _) => deps.params.push_body(ident),
+ IdentKind::Type(_) => deps.types.push_body(ident),
+ IdentKind::Func(_, _) => deps.funcs.push_body(ident),
IdentKind::MapHead | IdentKind::Map | IdentKind::MapTail => {
- deps.map.push(ident)
+ deps.map.push_body(ident)
}
IdentKind::RetMapHead
| IdentKind::RetMap
- | IdentKind::RetMapTail => deps.retmap.push(ident),
- _ => deps.rater.push(ident),
+ | IdentKind::RetMapTail => deps.retmap.push_body(ident),
+ _ => deps.rater.push_body(ident),
},
_ => panic!("unexpected node: {:?}", ident),
}
@@ -302,251 +285,63 @@ fn sort_deps<'a, 'i>(
deps
}
+fn get_interner_value<'a, 'i, I: Interner<'i>>(
+ depgraph: &'a LinkerAsg<'i>,
+ interner: &'i I,
+ name: &str,
+) -> &'a Object<'i> {
+ depgraph
+ .get(
+ depgraph
+ .lookup(interner.intern(name))
+ .unwrap_or_else(|| panic!("missing identifier: {}", name)),
+ )
+ .expect("Could not get interner value")
+}
+
fn output_xmle<'a, 'i, I: Interner<'i>>(
depgraph: &'a LinkerAsg<'i>,
interner: &'i I,
- sorted: SortedDeps<'a, 'i>,
+ sorted: &mut Sections<'a, 'i>,
name: &'i Symbol<'i>,
relroot: String,
) -> Result<(), Box<dyn Error>> {
- use std::io::Cursor;
-
- let mut writer =
- Writer::new_with_indent(Cursor::new(Vec::new()), ' ' as u8, 2);
-
- let root =
- BytesStart::owned_name(b"package".to_vec()).with_attributes(vec![
- ("xmlns", "http://www.lovullo.com/rater"),
- ("xmlns:preproc", "http://www.lovullo.com/rater/preproc"),
- ("xmlns:l", "http://www.lovullo.com/rater/linker"),
- ("title", &name), // TODO
- ("program", "true"),
- ("name", &name),
- ("__rootpath", &relroot),
- ]);
-
- writer.write_event(Event::Start(root))?;
-
- // All of the other namespaces output in the existing xmle files are
- // unneeded.
- writer.write_event(Event::Start(BytesStart::borrowed_name(b"l:dep")))?;
-
- let all = sorted
- .meta
- .iter()
- .chain(sorted.map.iter())
- .chain(sorted.retmap.iter())
- .chain(sorted.worksheet.iter())
- .chain(sorted.params.iter())
- .chain(sorted.types.iter())
- .chain(sorted.funcs.iter())
- .chain(sorted.rater.iter());
-
- for ident in all {
- // TODO: we're doing this in two places!
- match ident {
- Object::Ident(sym, kind, src)
- | Object::IdentFragment(sym, kind, src, _) => {
- let name: &str = sym;
-
- // this'll be formalized more sanely
- let mut attrs = match kind {
- IdentKind::Cgen(dim) => {
- vec![("type", "cgen"), ("dim", dim.as_ref())]
- }
- IdentKind::Class(dim) => {
- vec![("type", "class"), ("dim", dim.as_ref())]
- }
- IdentKind::Const(dim, dtype) => vec![
- ("type", "const"),
- ("dim", dim.as_ref()),
- ("dtype", dtype.as_ref()),
- ],
- IdentKind::Func(dim, dtype) => vec![
- ("type", "func"),
- ("dim", dim.as_ref()),
- ("dtype", dtype.as_ref()),
- ],
- IdentKind::Gen(dim, dtype) => vec![
- ("type", "gen"),
- ("dim", dim.as_ref()),
- ("dtype", dtype.as_ref()),
- ],
- IdentKind::Lparam(dim, dtype) => vec![
- ("type", "lparam"),
- ("dim", dim.as_ref()),
- ("dtype", dtype.as_ref()),
- ],
- IdentKind::Param(dim, dtype) => vec![
- ("type", "param"),
- ("dim", dim.as_ref()),
- ("dtype", dtype.as_ref()),
- ],
- IdentKind::Rate(dtype) => {
- vec![("type", "rate"), ("dtype", dtype.as_ref())]
- }
- IdentKind::Tpl => vec![("type", "tpl")],
- IdentKind::Type(dtype) => {
- vec![("type", "type"), ("dtype", dtype.as_ref())]
- }
- IdentKind::MapHead => vec![("type", "map:head")],
- IdentKind::Map => vec![("type", "map")],
- IdentKind::MapTail => vec![("type", "map:tail")],
- IdentKind::RetMapHead => vec![("type", "retmap:head")],
- IdentKind::RetMap => vec![("type", "retmap")],
- IdentKind::RetMapTail => vec![("type", "retmap:tail")],
- IdentKind::Meta => vec![("type", "meta")],
- IdentKind::Worksheet => vec![("type", "worksheet")],
- };
-
- attrs.push(("name", name));
-
- if src.generated {
- attrs.push(("preproc:generated", "true"));
- }
-
- let srcpath: String;
- if let Some(pkg_name) = src.pkg_name {
- srcpath = relroot.clone() + pkg_name;
- attrs.push(("src", &srcpath));
- }
- if let Some(parent) = src.parent {
- attrs.push(("parent", parent));
- }
- if let Some(yields) = src.yields {
- attrs.push(("yields", yields));
- }
- if let Some(desc) = &src.desc {
- attrs.push(("desc", &desc));
- }
-
- let sym = BytesStart::owned_name(b"preproc:sym".to_vec())
- .with_attributes(attrs);
-
- writer.write_event(Event::Empty(sym))?;
- }
- _ => unreachable!("filtered out during sorting"),
- }
- }
-
- writer.write_event(Event::End(BytesEnd::borrowed(b"l:dep")))?;
-
- // This was not in the original linker, but we need to be able to convey
- // this information for `standalones` (which has received some logic
- // from the old linker for the time being).
- writer
- .write_event(Event::Start(BytesStart::borrowed_name(b"l:map-from")))?;
-
- let mut map_froms: FxHashSet<&str> = Default::default();
-
- for map_ident in &sorted.map {
- match map_ident {
- Object::Ident(_, _, src) | Object::IdentFragment(_, _, src, _) => {
- if let Some(froms) = &src.from {
- for from in froms {
- map_froms.insert(from);
- }
- }
- }
-
- _ => unreachable!("filtered out during sorting"),
- }
- }
-
- for from in map_froms {
- let name: &str = from;
-
- writer.write_event(Event::Empty(
- BytesStart::borrowed_name(b"l:from")
- .with_attributes(vec![("name", name)]),
- ))?;
- }
-
- writer.write_event(Event::End(BytesEnd::borrowed(b"l:map-from")))?;
- writer
- .write_event(Event::Start(BytesStart::borrowed_name(b"l:map-exec")))?;
-
- if sorted.map.len() > 0 {
- write_fragments(
- &mut writer,
- &vec![depgraph
- .get(depgraph.lookup(interner.intern(":map:___head")).unwrap())
- .unwrap()],
- )?;
- write_fragments(&mut writer, &sorted.map)?;
- write_fragments(
- &mut writer,
- &vec![depgraph
- .get(depgraph.lookup(interner.intern(":map:___tail")).unwrap())
- .unwrap()],
- )?;
+ if !sorted.map.is_empty() {
+ sorted.map.push_head(get_interner_value(
+ depgraph,
+ interner,
+ &String::from(":map:___head"),
+ ));
+ sorted.map.push_tail(get_interner_value(
+ depgraph,
+ interner,
+ &String::from(":map:___tail"),
+ ));
}
- writer.write_event(Event::End(BytesEnd::borrowed(b"l:map-exec")))?;
- writer.write_event(Event::Start(BytesStart::borrowed_name(
- b"l:retmap-exec",
- )))?;
-
- if sorted.retmap.len() > 0 {
- write_fragments(
- &mut writer,
- &vec![depgraph
- .get(
- depgraph
- .lookup(interner.intern(":retmap:___head"))
- .unwrap(),
- )
- .unwrap()],
- )?;
- write_fragments(&mut writer, &sorted.retmap)?;
- write_fragments(
- &mut writer,
- &vec![depgraph
- .get(
- depgraph
- .lookup(interner.intern(":retmap:___tail"))
- .unwrap(),
- )
- .unwrap()],
- )?;
+ if !sorted.retmap.is_empty() {
+ sorted.retmap.push_head(get_interner_value(
+ depgraph,
+ interner,
+ &String::from(":retmap:___head"),
+ ));
+ sorted.retmap.push_tail(get_interner_value(
+ depgraph,
+ interner,
+ &String::from(":retmap:___tail"),
+ ));
}
- writer.write_event(Event::End(BytesEnd::borrowed(b"l:retmap-exec")))?;
- writer.write_event(Event::Start(BytesStart::borrowed_name(b"l:static")))?;
+ let writer = Cursor::new(Vec::new());
+ let mut xmle_writer = XmleWriter::new(writer);
+ xmle_writer
+ .write(&sorted, name, &relroot)
+ .expect("Could not write xmle output");
- write_fragments(&mut writer, &sorted.meta)?;
- write_fragments(&mut writer, &sorted.worksheet)?;
- write_fragments(&mut writer, &sorted.params)?;
- write_fragments(&mut writer, &sorted.types)?;
- write_fragments(&mut writer, &sorted.funcs)?;
-
- writer.write_event(Event::End(BytesEnd::borrowed(b"l:static")))?;
- writer.write_event(Event::Start(BytesStart::borrowed_name(b"l:exec")))?;
-
- write_fragments(&mut writer, &sorted.rater)?;
-
- writer.write_event(Event::End(BytesEnd::borrowed(b"l:exec")))?;
- writer.write_event(Event::End(BytesEnd::borrowed(b"package")))?;
-
- print!("{}", String::from_utf8(writer.into_inner().into_inner())?);
-
- Ok(())
-}
-
-fn write_fragments<W: Write>(
- writer: &mut Writer<W>,
- idents: &ObjectVec,
-) -> Result<(), Box<dyn Error>> {
- for ident in idents {
- match ident {
- Object::IdentFragment(_, _, _, frag) => {
- writer.write_event(Event::Text(BytesText::from_plain_str(
- frag,
- )))?;
- }
- _ => (),
- }
- }
+ print!(
+ "{}",
+ String::from_utf8(xmle_writer.into_inner().into_inner())?
+ );
Ok(())
}
diff --git a/tamer/src/obj/mod.rs b/tamer/src/obj/mod.rs
index 3b44798..eb45061 100644
--- a/tamer/src/obj/mod.rs
+++ b/tamer/src/obj/mod.rs
@@ -31,4 +31,5 @@
//!
//! [ELF]: https://en.wikipedia.org/wiki/Executable_and_Linkable_Format
+pub mod xmle;
pub mod xmlo;
diff --git a/tamer/src/obj/xmle/mod.rs b/tamer/src/obj/xmle/mod.rs
new file mode 100644
index 0000000..1b1c360
--- /dev/null
+++ b/tamer/src/obj/xmle/mod.rs
@@ -0,0 +1,69 @@
+// xmle object files
+//
+// Copyright (C) 2014-2019 Ryan Specialty Group, LLC.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+//! `xmle` file construction and processing.
+//!
+//! This file format exists for compatibility with the old compiler
+//! written in XSLT; it will be removed in the future.
+//!
+//!
+//! `xmle` Files
+//! ===================
+//! An `xmle` file is produced by the for each source file.
+//! The format is XML because the original compiler was written in XSLT.
+//!
+//! The general structure of an `xmle` file consists of different sections:
+//! - map
+//! - return map
+//! - statics
+//! - rater
+//!
+//! For example (with some extra information omitted):
+//!
+//! ```xml
+//! <package xmlns="http://www.lovullo.com/rater"
+//! xmlns:preproc="http://www.lovullo.com/rater/preproc"
+//! xmlns:l="http://www.lovullo.com/rater/linker"
+//! title="suppliers/tax"
+//! program="true"
+//! name="suppliers/tax"
+//! __rootpath="../">
+//! <l:dep>
+//! <preproc:sym type="func"
+//! dim="0"
+//! dtype="float"
+//! name="min"
+//! src="../rater/core/numeric/minmax"
+//! desc="Return the lesser value"/>
+//! </l:dep>
+//! <l:map-from>
+//! <l:from name="latest_operation_hour"/>
+//! </l:map-from>
+//! <l:map-exec>
+//! function( input, callback ) {)
+//! </l:map-exec>
+//! <l:retmap-exec>
+//! function( input, callback ) {)
+//! </l:retmap-exec>
+//! <l:static>
+//! function func_min( args , min1, min2) {return min1;}
+//! </l:static>
+//! <l:exec>consts[&apos;CMP_OP_EQ&apos;] = 1;</l:exec>
+//! </package>
+//! ```
+
+pub mod writer;
diff --git a/tamer/src/obj/xmle/writer/mod.rs b/tamer/src/obj/xmle/writer/mod.rs
new file mode 100644
index 0000000..4287699
--- /dev/null
+++ b/tamer/src/obj/xmle/writer/mod.rs
@@ -0,0 +1,47 @@
+// Object file writer
+//
+// Copyright (C) 2014-2019 Ryan Specialty Group, LLC.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+//! xmle file writer.
+//!
+//! This defines a lower-level event-based `XmleWriter` similar to that of
+//! `quick_xml`, where the events are a slightly higher-level abstraction
+//! over the types of nodes present in the file.
+//!
+//! For more information on xmle files, see the [parent crate][`super`].
+//!
+//! The example below is incomplete, but shows the general usage.
+//!
+//! ```
+//! use tamer::obj::xmle::writer::{Sections, XmleWriter};
+//! use tamer::sym::{DefaultInterner, Interner, Symbol};
+//! use std::io::Cursor;
+//!
+//! let interner = DefaultInterner::new();
+//! let name = interner.intern(&String::from("foo"));
+//!
+//! let sections = Sections::new();
+//! let writer = Cursor::new(Vec::new());
+//! let mut xmle_writer = XmleWriter::new(writer);
+//! xmle_writer.write(&sections, name, &String::from(""));
+//! ```
+
+mod writer;
+mod xmle;
+
+pub use writer::{Result, Section, Sections, Writer, WriterError};
+
+pub use xmle::XmleWriter;
diff --git a/tamer/src/obj/xmle/writer/writer.rs b/tamer/src/obj/xmle/writer/writer.rs
new file mode 100644
index 0000000..05d89f9
--- /dev/null
+++ b/tamer/src/obj/xmle/writer/writer.rs
@@ -0,0 +1,320 @@
+// xmle object file writer
+//
+// Copyright (C) 2014-2019 Ryan Specialty Group, LLC.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+use crate::ir::asg::Object;
+use crate::sym::Symbol;
+use quick_xml::Error as XmlError;
+use std::io::{Error as IoError, Write};
+use std::result;
+use std::str::Utf8Error;
+
+type ObjectRef<'a, 'i> = &'a Object<'i>;
+pub type Result<T = ()> = result::Result<T, WriterError>;
+pub type ObjectVec<'a, 'i> = Vec<ObjectRef<'a, 'i>>;
+
+/// A wrapper around a `Write` object
+///
+/// This is used to take the [`Sections`] and write out the xmle files.
+pub trait Writer<W: Write> {
+ fn write(
+ &mut self,
+ sections: &Sections,
+ name: Symbol,
+ relroot: &str,
+ ) -> Result<()>
+ where
+ Self: Sized;
+}
+
+/// A Section that needs to be written to the buffer
+///
+/// Most sections will only need a `body`, but some innlude `head` and `tail`
+/// information. Rather than dealing with those differently, each `Section`
+/// will have a `head` and `tail` that are empty by default.
+#[derive(Clone, Default)]
+pub struct Section<'a, 'i> {
+ head: ObjectVec<'a, 'i>,
+ body: ObjectVec<'a, 'i>,
+ tail: ObjectVec<'a, 'i>,
+}
+
+impl<'a, 'i> Section<'a, 'i> {
+ /// Constructor for Sections
+ ///
+ /// ```
+ /// use tamer::obj::xmle::writer::Section;
+ ///
+ /// let section = Section::new();
+ /// ```
+ pub fn new() -> Self {
+ Self {
+ head: Vec::new(),
+ body: Vec::new(),
+ tail: Vec::new(),
+ }
+ }
+
+ /// The length of the `Section`
+ pub fn len(&self) -> usize {
+ self.head.len() + self.body.len() + self.tail.len()
+ }
+
+ /// Check if the `Section` is empty
+ pub fn is_empty(&self) -> bool {
+ self.len() == 0
+ }
+
+ /// Push an `Object` into a `Section`'s head
+ pub fn push_head(&mut self, obj: ObjectRef<'a, 'i>) {
+ self.head.push(&obj)
+ }
+
+ /// Push an `Object` into a `Section`'s body
+ pub fn push_body(&mut self, obj: ObjectRef<'a, 'i>) {
+ self.body.push(&obj)
+ }
+
+ /// Push an `Object` into a `Section`'s tail
+ pub fn push_tail(&mut self, obj: ObjectRef<'a, 'i>) {
+ self.tail.push(&obj)
+ }
+
+ /// Merge the parts of a `Section` into one [`SectionIterator`]
+ ///
+ /// The `Section` internals need to be iterated as a group so we needed to
+ /// create a custom iterator, [`SectionIterator`] to do this for us. This
+ /// method allows us to access the iterator.
+ ///
+ /// ```
+ /// use tamer::obj::xmle::writer::Section;
+ /// use tamer::ir::asg::Object;
+ ///
+ /// let mut section = Section::new();
+ /// let obj = Object::Empty;
+ /// let expect = vec![&obj, &obj, &obj];
+ ///
+ /// section.push_head(&obj);
+ /// section.push_body(&obj);
+ /// section.push_tail(&obj);
+ /// let section_iter = section.iter();
+ ///
+ /// for object in section_iter {
+ /// assert_eq!(&obj, object);
+ /// }
+ /// ```
+ pub fn iter(&self) -> SectionIterator {
+ SectionIterator {
+ inner: Box::new(
+ self.head
+ .iter()
+ .chain(self.body.iter())
+ .chain(self.tail.iter())
+ .copied(),
+ ),
+ }
+ }
+}
+
+/// Wrapper for an Iterator
+///
+/// This allows us to iterate over all parts of a [`Section`].
+pub struct SectionIterator<'a, 'i> {
+ inner: Box<dyn Iterator<Item = &'a Object<'i>> + 'a>,
+}
+
+impl<'a, 'i> Iterator for SectionIterator<'a, 'i> {
+ type Item = &'a Object<'i>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ self.inner.next()
+ }
+}
+
+/// Sections that need to be written to a buffer
+///
+/// All the properties are public [`Section`] objects and will be accessed
+/// directly by the [`Writer`].
+#[derive(Default)]
+pub struct Sections<'a, 'i> {
+ pub map: Section<'a, 'i>,
+ pub retmap: Section<'a, 'i>,
+ pub meta: Section<'a, 'i>,
+ pub worksheet: Section<'a, 'i>,
+ pub params: Section<'a, 'i>,
+ pub types: Section<'a, 'i>,
+ pub funcs: Section<'a, 'i>,
+ pub rater: Section<'a, 'i>,
+}
+
+impl<'a, 'i> Sections<'a, 'i> {
+ /// Constructor for Sections
+ ///
+ /// ```
+ /// use tamer::obj::xmle::writer::Sections;
+ ///
+ /// let sections = Sections::new();
+ /// ```
+ pub fn new() -> Self {
+ Self {
+ map: Section::new(),
+ retmap: Section::new(),
+ meta: Section::new(),
+ worksheet: Section::new(),
+ params: Section::new(),
+ types: Section::new(),
+ funcs: Section::new(),
+ rater: Section::new(),
+ }
+ }
+}
+
+/// Error implementations for the writer
+#[derive(Debug)]
+pub enum WriterError {
+ Io(IoError),
+ Utf8(Utf8Error),
+ XmlError(XmlError),
+ ExpectedFragment(String),
+}
+
+impl From<IoError> for WriterError {
+ fn from(err: IoError) -> Self {
+ WriterError::Io(err)
+ }
+}
+
+impl From<Utf8Error> for WriterError {
+ fn from(err: Utf8Error) -> Self {
+ WriterError::Utf8(err)
+ }
+}
+
+impl From<XmlError> for WriterError {
+ fn from(err: XmlError) -> Self {
+ WriterError::XmlError(err)
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn section_empty() {
+ let section = Section::new();
+
+ assert!(section.head.is_empty());
+ assert!(section.body.is_empty());
+ assert!(section.tail.is_empty());
+ }
+
+ #[test]
+ fn section_head() {
+ let mut section = Section::new();
+ let obj = Object::Empty;
+
+ assert!(section.head.is_empty());
+
+ section.push_head(&obj);
+
+ assert_eq!(Some(&&obj), section.head.get(0));
+ }
+
+ #[test]
+ fn section_body() {
+ let mut section = Section::new();
+ let obj = Object::Empty;
+
+ assert!(section.body.is_empty());
+
+ section.push_body(&obj);
+
+ let body = section.body;
+ assert_eq!(Some(&&obj), body.get(0));
+ }
+
+ #[test]
+ fn section_tail() {
+ let mut section = Section::new();
+ let obj = Object::Empty;
+
+ assert!(section.tail.is_empty());
+
+ section.push_tail(&obj);
+
+ assert_eq!(Some(&&obj), section.tail.get(0));
+ }
+
+ #[test]
+ fn section_len() {
+ let mut section = Section::new();
+ let obj = Object::Empty;
+
+ assert_eq!(0, section.len());
+ section.push_head(&obj);
+ assert_eq!(1, section.len());
+ section.push_body(&obj);
+ assert_eq!(2, section.len());
+ section.push_tail(&obj);
+ assert_eq!(3, section.len());
+ }
+
+ #[test]
+ fn section_is_empty_head() {
+ let mut section = Section::new();
+ let obj = Object::Empty;
+
+ assert!(section.is_empty());
+ section.push_head(&obj);
+ assert!(!section.is_empty());
+ }
+
+ #[test]
+ fn section_is_empty_body() {
+ let mut section = Section::new();
+ let obj = Object::Empty;
+
+ assert!(section.is_empty());
+ section.push_body(&obj);
+ assert!(!section.is_empty());
+ }
+
+ #[test]
+ fn section_is_empty_tail() {
+ let mut section = Section::new();
+ let obj = Object::Empty;
+
+ assert!(section.is_empty());
+ section.push_tail(&obj);
+ assert!(!section.is_empty());
+ }
+
+ #[test]
+ fn section_iterator() {
+ let mut section = Section::new();
+ let obj = Object::Empty;
+ let expect = vec![&obj, &obj, &obj];
+
+ section.push_head(&obj);
+ section.push_body(&obj);
+ section.push_tail(&obj);
+
+ let collection: Vec<_> = section.iter().collect();
+
+ assert_eq!(expect, collection);
+ }
+}
diff --git a/tamer/src/obj/xmle/writer/xmle.rs b/tamer/src/obj/xmle/writer/xmle.rs
new file mode 100644
index 0000000..fc659a6
--- /dev/null
+++ b/tamer/src/obj/xmle/writer/xmle.rs
@@ -0,0 +1,749 @@
+// Concrete xmle writer
+//
+// Copyright (C) 2014-2019 Ryan Specialty Group, LLC.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+use super::writer::{Result, SectionIterator, Sections, WriterError};
+use crate::ir::asg::{IdentKind, Object};
+use crate::sym::Symbol;
+use fxhash::FxHashSet;
+#[cfg(test)]
+use mock::MockXmlWriter as XmlWriter;
+use quick_xml::events::{BytesEnd, BytesStart, BytesText, Event};
+#[cfg(not(test))]
+use quick_xml::Writer as XmlWriter;
+use std::io::Write;
+
+/// Responsible for writing to the xmle files
+pub struct XmleWriter<W: Write> {
+ writer: XmlWriter<W>,
+}
+
+impl<W: Write> XmleWriter<W> {
+ /// Create a new instance of `XmleWriter`
+ /// ```
+ /// use std::io::Cursor;
+ /// use tamer::obj::xmle::writer::XmleWriter;
+ ///
+ /// let writer = Cursor::new(Vec::new());
+ /// let xmle_writer = XmleWriter::new(writer);
+ /// ```
+ pub fn new(write: W) -> Self {
+ let writer = XmlWriter::new_with_indent(write, b' ', 2);
+
+ Self { writer }
+ }
+
+ /// Consume the `XmleWriter` and return the inner `Write` object
+ ///
+ /// ```
+ /// use std::io::Cursor;
+ /// use tamer::obj::xmle::writer::XmleWriter;
+ ///
+ /// let writer = Cursor::new(Vec::new());
+ /// let xmle_writer = XmleWriter::new(writer);
+ /// assert!(xmle_writer.into_inner().into_inner().is_empty());
+ /// ```
+ pub fn into_inner(self) -> W {
+ self.writer.into_inner()
+ }
+
+ /// Write xmle
+ ///
+ /// Goes through each of the pre-ordered [`Sections`] and writes to the
+ /// buffer.
+ ///
+ /// ```
+ /// use std::io::Cursor;
+ /// use tamer::obj::xmle::writer::{Sections, XmleWriter};
+ /// use tamer::sym::{Symbol, SymbolIndex};
+ /// use tamer::sym::{DefaultInterner, Interner};
+ ///
+ /// let writer = Cursor::new(Vec::new());
+ /// let mut xmle_writer = XmleWriter::new(writer);
+ /// let sections = Sections::new();
+ /// let a = "foo";
+ /// let interner = DefaultInterner::new();
+ /// let name = interner.intern(&a);
+ /// xmle_writer.write(
+ /// &sections,
+ /// &name,
+ /// &String::from(""),
+ /// );
+ /// let buf = xmle_writer.into_inner().into_inner();
+ /// assert!(!buf.is_empty(), "something was written to the buffer");
+ /// ```
+ pub fn write(
+ &mut self,
+ sections: &Sections,
+ name: &Symbol,
+ relroot: &str,
+ ) -> Result {
+ self.write_start_package(name, &relroot)?
+ .write_element(b"l:dep", |writer| {
+ writer.write_sections(&sections, &relroot)
+ })?
+ // This was not in the original linker, but we need to be able to
+ // convey this information for `standalones` (which has received
+ // some logic from the old linker for the time being).
+ .write_element(b"l:map-from", |writer| {
+ writer.write_froms(&sections)
+ })?
+ .write_element(b"l:map-exec", |writer| {
+ writer.write_section(sections.map.iter())
+ })?
+ .write_element(b"l:retmap-exec", |writer| {
+ writer.write_section(sections.retmap.iter())
+ })?
+ .write_element(b"l:static", |writer| {
+ writer
+ .write_section(sections.meta.iter())?
+ .write_section(sections.worksheet.iter())?
+ .write_section(sections.params.iter())?
+ .write_section(sections.types.iter())?
+ .write_section(sections.funcs.iter())
+ })?
+ .write_element(b"l:exec", |writer| {
+ writer.write_section(sections.rater.iter())
+ })?
+ .write_end_tag(b"package")?;
+
+ Ok(())
+ }
+
+ /// Write an element
+ ///
+ /// This writes the opening tag, the content, and the closing tag for a
+ /// given element. The callback is what will write the element's body.
+ #[inline]
+ fn write_element<F>(
+ &mut self,
+ name: &[u8],
+ callback: F,
+ ) -> Result<&mut XmleWriter<W>>
+ where
+ F: FnOnce(&mut Self) -> Result<&mut XmleWriter<W>>,
+ {
+ self.write_start_tag(name)?;
+ (callback)(self)?;
+ self.write_end_tag(name)?;
+
+ Ok(self)
+ }
+
+ /// Open the `package` element
+ ///
+ /// The `package` element's opening tag needs attributes, so it cannot use
+ /// `write_start_tag` directly.
+ fn write_start_package(
+ &mut self,
+ name: &Symbol,
+ relroot: &str,
+ ) -> Result<&mut XmleWriter<W>> {
+ let root =
+ BytesStart::owned_name(b"package".to_vec()).with_attributes(vec![
+ ("xmlns", "http://www.lovullo.com/rater"),
+ ("xmlns:preproc", "http://www.lovullo.com/rater/preproc"),
+ ("xmlns:l", "http://www.lovullo.com/rater/linker"),
+ ("title", &name), // TODO
+ ("program", "true"),
+ ("name", &name),
+ ("__rootpath", &relroot),
+ ]);
+
+ self.writer.write_event(Event::Start(root))?;
+
+ Ok(self)
+ }
+
+ /// Open an element's tag
+ fn write_start_tag(&mut self, name: &[u8]) -> Result<&mut XmleWriter<W>> {
+ self.writer
+ .write_event(Event::Start(BytesStart::borrowed_name(name)))?;
+
+ Ok(self)
+ }
+
+ /// Close an element's tag
+ fn write_end_tag(&mut self, name: &[u8]) -> Result<&mut XmleWriter<W>> {
+ self.writer
+ .write_event(Event::End(BytesEnd::borrowed(name)))?;
+
+ Ok(self)
+ }
+
+ /// Write all [`Sections`]
+ ///
+ /// All the [`Sections`] found need to be written out using the `writer`
+ /// object.
+ fn write_sections(
+ &mut self,
+ sections: &Sections,
+ relroot: &str,
+ ) -> Result<&mut XmleWriter<W>> {
+ let all = sections
+ .meta
+ .iter()
+ .chain(sections.map.iter())
+ .chain(sections.retmap.iter())
+ .chain(sections.worksheet.iter())
+ .chain(sections.params.iter())
+ .chain(sections.types.iter())
+ .chain(sections.funcs.iter())
+ .chain(sections.rater.iter());
+
+ for ident in all {
+ match ident {
+ Object::Ident(sym, kind, src)
+ | Object::IdentFragment(sym, kind, src, _) => {
+ let name: &str = sym;
+
+ // this'll be formalized more sanely
+ let mut attrs = match kind {
+ IdentKind::Cgen(dim) => {
+ vec![("type", "cgen"), ("dim", dim.as_ref())]
+ }
+ IdentKind::Class(dim) => {
+ vec![("type", "class"), ("dim", dim.as_ref())]
+ }
+ IdentKind::Const(dim, dtype) => vec![
+ ("type", "const"),
+ ("dim", dim.as_ref()),
+ ("dtype", dtype.as_ref()),
+ ],
+ IdentKind::Func(dim, dtype) => vec![
+ ("type", "func"),
+ ("dim", dim.as_ref()),
+ ("dtype", dtype.as_ref()),
+ ],
+ IdentKind::Gen(dim, dtype) => vec![
+ ("type", "gen"),
+ ("dim", dim.as_ref()),
+ ("dtype", dtype.as_ref()),
+ ],
+ IdentKind::Lparam(dim, dtype) => vec![
+ ("type", "lparam"),
+ ("dim", dim.as_ref()),
+ ("dtype", dtype.as_ref()),
+ ],
+ IdentKind::Param(dim, dtype) => vec![
+ ("type", "param"),
+ ("dim", dim.as_ref()),
+ ("dtype", dtype.as_ref()),
+ ],
+ IdentKind::Rate(dtype) => {
+ vec![("type", "rate"), ("dtype", dtype.as_ref())]
+ }
+ IdentKind::Tpl => vec![("type", "tpl")],
+ IdentKind::Type(dtype) => {
+ vec![("type", "type"), ("dtype", dtype.as_ref())]
+ }
+ IdentKind::MapHead => vec![("type", "map:head")],
+ IdentKind::Map => vec![("type", "map")],
+ IdentKind::MapTail => vec![("type", "map:tail")],
+ IdentKind::RetMapHead => vec![("type", "retmap:head")],
+ IdentKind::RetMap => vec![("type", "retmap")],
+ IdentKind::RetMapTail => vec![("type", "retmap:tail")],
+ IdentKind::Meta => vec![("type", "meta")],
+ IdentKind::Worksheet => vec![("type", "worksheet")],
+ };
+
+ attrs.push(("name", name));
+
+ if src.generated {
+ attrs.push(("preproc:generated", "true"));
+ }
+
+ let srcpath: String;
+ if let Some(pkg_name) = src.pkg_name {
+ srcpath = format!("{}{}", relroot, pkg_name);
+ attrs.push(("src", &srcpath));
+ }
+ if let Some(parent) = src.parent {
+ attrs.push(("parent", parent));
+ }
+ if let Some(yields) = src.yields {
+ attrs.push(("yields", yields));
+ }
+ if let Some(desc) = &src.desc {
+ attrs.push(("desc", &desc));
+ }
+
+ let sym = BytesStart::owned_name(b"preproc:sym".to_vec())
+ .with_attributes(attrs);
+
+ self.writer.write_event(Event::Empty(sym))?;
+ }
+ _ => unreachable!("filtered out during sorting"),
+ }
+ }
+
+ Ok(self)
+ }
+
+ /// Write the source `from`
+ ///
+ /// If a `map` object has a `from` attribute in its source, we need to
+ /// write them using the `writer`'s `write_event`.
+ fn write_froms(
+ &mut self,
+ sections: &Sections,
+ ) -> Result<&mut XmleWriter<W>> {
+ let mut map_froms: FxHashSet<&str> = Default::default();
+
+ let map_iter = sections.map.iter();
+
+ for map_ident in map_iter {
+ match map_ident {
+ Object::Ident(_, _, src)
+ | Object::IdentFragment(_, _, src, _) => {
+ if let Some(froms) = &src.from {
+ for from in froms {
+ map_froms.insert(from);
+ }
+ }
+ }
+ _ => unreachable!("filtered out during sorting"),
+ }
+ }
+
+ for from in map_froms {
+ let name: &str = from;
+
+ self.writer.write_event(Event::Empty(
+ BytesStart::borrowed_name(b"l:from")
+ .with_attributes(vec![("name", name)]),
+ ))?;
+ }
+
+ Ok(self)
+ }
+
+ /// Write a ['Section`]
+ ///
+ /// Iterates through the parts of a `Section` and writes them using the
+ /// `writer`'s 'write_event`.
+ fn write_section(
+ &mut self,
+ idents: SectionIterator,
+ ) -> Result<&mut XmleWriter<W>> {
+ for ident in idents {
+ match ident {
+ Object::IdentFragment(_, _, _, frag) => {
+ self.writer.write_event(Event::Text(
+ BytesText::from_plain_str(frag),
+ ))?;
+ }
+ // Cgen, Gen, and Lparam are not expected to be present, so we
+ // can ignore them when we determeing when to return an Err.
+ Object::Ident(_, IdentKind::Cgen(_), _)
+ | Object::Ident(_, IdentKind::Gen(_, _), _)
+ | Object::Ident(_, IdentKind::Lparam(_, _), _) => (),
+ obj => {
+ return Err(WriterError::ExpectedFragment(format!(
+ "fragment expected: {:?}",
+ obj
+ )));
+ }
+ }
+ }
+
+ Ok(self)
+ }
+}
+
+#[cfg(test)]
+mod mock {
+ use super::*;
+
+ pub struct MockXmlWriter<W: Write> {
+ inner: W,
+ pub write_callback: Option<Box<dyn for<'a> Fn(&Event<'a>) -> Result>>,
+ }
+
+ impl<W: Write> MockXmlWriter<W> {
+ pub fn new(inner: W) -> Self {
+ Self {
+ inner,
+ write_callback: None,
+ }
+ }
+
+ pub fn new_with_indent(inner: W, _: u8, _: u8) -> Self {
+ Self::new(inner)
+ }
+
+ pub fn write_event<'a, E: AsRef<Event<'a>>>(
+ &mut self,
+ event: E,
+ ) -> Result<usize> {
+ (self
+ .write_callback
+ .as_ref()
+ .expect("missing mock write_callback"))(
+ event.as_ref()
+ )?;
+
+ Ok(0)
+ }
+
+ pub fn into_inner(self) -> W {
+ self.inner
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use crate::ir::asg::Dim;
+ use crate::ir::asg::Source;
+ use crate::ir::legacyir::SymAttrs;
+ use crate::obj::xmle::writer::Section;
+ use crate::sym::{Symbol, SymbolIndex};
+ use std::str;
+
+ type Sut<W> = XmleWriter<W>;
+
+ #[test]
+ fn writer_uses_inner_buffer() -> Result {
+ let expected = vec![1, 2, 3];
+ let buf = expected.clone();
+
+ let sut = Sut::new(buf);
+
+ assert_eq!(expected, sut.into_inner());
+
+ Ok(())
+ }
+
+ #[test]
+ fn write_start_package() -> Result {
+ let mut sut = Sut::new(vec![]);
+ sut.writer.write_callback = Some(Box::new(|event| match event {
+ Event::Start(bytes_start) => {
+ let name = str::from_utf8(bytes_start.name());
+ match name {
+ Ok("package") => {
+ let attributes = bytes_start.attributes();
+ assert_eq!(7, attributes.count());
+ Ok(())
+ }
+ _ => panic!("unreachable"),
+ }
+ }
+ _ => panic!("did not match expected event"),
+ }));
+
+ let sym = Symbol::new_dummy(SymbolIndex::from_u32(1), "sym");
+
+ sut.write_start_package(&sym, &String::from(""))?;
+
+ Ok(())
+ }
+
+ #[test]
+ fn write_start_tag() -> Result {
+ let mut sut = Sut::new(vec![]);
+ sut.writer.write_callback = Some(Box::new(|event| match event {
+ Event::Start(bytes_start) => {
+ let name = str::from_utf8(bytes_start.name());
+ match name {
+ Ok("l:dep") => {
+ let attributes = bytes_start.attributes();
+ assert_eq!(0, attributes.count());
+ Ok(())
+ }
+ _ => panic!("unreachable"),
+ }
+ }
+ _ => panic!("did not match expected event"),
+ }));
+
+ sut.write_start_tag(b"l:dep")?;
+
+ Ok(())
+ }
+
+ #[test]
+ fn write_end_tag() -> Result {
+ let mut sut = Sut::new(vec![]);
+ sut.writer.write_callback = Some(Box::new(|event| match event {
+ Event::End(bytes_end) => {
+ let name = str::from_utf8(bytes_end.name());
+ assert_eq!("package", name?);
+ Ok(())
+ }
+ _ => panic!("did not match expected event"),
+ }));
+
+ sut.write_end_tag(b"package")?;
+
+ Ok(())
+ }
+
+ #[test]
+ fn write_section() -> Result {
+ let mut sut = Sut::new(vec![]);
+ sut.writer.write_callback = Some(Box::new(|event| match event {
+ Event::Text(_) => (Ok(())),
+ _ => panic!("did not trigger event"),
+ }));
+
+ let sym = Symbol::new_dummy(SymbolIndex::from_u32(1), "sym");
+ let obj = Object::IdentFragment(
+ &sym,
+ IdentKind::Meta,
+ Source::default(),
+ String::from(""),
+ );
+
+ let mut section = Section::new();
+ section.push_body(&obj);
+ sut.write_section(section.iter())?;
+
+ Ok(())
+ }
+
+ #[test]
+ fn write_section_ignores_other_kinds() -> Result {
+ let mut sut = Sut::new(vec![]);
+ sut.writer.write_callback = Some(Box::new(|_| {
+ panic!("callback should not have been called");
+ }));
+
+ let sym = Symbol::new_dummy(SymbolIndex::from_u32(1), "sym");
+ let obj = Object::Ident(
+ &sym,
+ IdentKind::Cgen(Dim::default()),
+ Source::default(),
+ );
+
+ let mut section = Section::new();
+ section.push_body(&obj);
+ sut.write_section(section.iter())?;
+
+ Ok(())
+ }
+
+ #[test]
+ fn write_section_catch_missing() -> Result {
+ let mut sut = Sut::new(vec![]);
+ sut.writer.write_callback = Some(Box::new(|_| {
+ panic!("callback should not have been called");
+ }));
+
+ let obj = Object::Empty;
+
+ let mut section = Section::new();
+ section.push_body(&obj);
+ let result = sut.write_section(section.iter());
+
+ match result {
+ Err(WriterError::ExpectedFragment(_)) => {}
+ _ => panic!("expected Err"),
+ }
+
+ Ok(())
+ }
+
+ #[test]
+ fn write_sections() -> Result {
+ let mut sut = Sut::new(vec![]);
+
+ sut.writer.write_callback = Some(Box::new(|event| match event {
+ Event::Empty(bytes_start) => {
+ let name = str::from_utf8(bytes_start.name())?;
+ assert_eq!("preproc:sym", name);
+ let mut attributes = bytes_start.attributes();
+ assert_eq!(2, attributes.clone().count());
+
+ let attr = attributes.next().expect("Expects attributes")?;
+ assert_eq!("type", str::from_utf8(attr.key)?);
+ assert_eq!("worksheet", str::from_utf8(&attr.value)?);
+
+ let attr = attributes.next().expect("Expects attributes")?;
+ assert_eq!("name", str::from_utf8(attr.key)?);
+ assert_eq!("random_symbol", str::from_utf8(&attr.value)?);
+
+ Ok(())
+ }
+ _ => panic!("unexpected event"),
+ }));
+
+ let sym = Symbol::new_dummy(SymbolIndex::from_u32(1), "random_symbol");
+ let object =
+ Object::Ident(&sym, IdentKind::Worksheet, Source::default());
+ let mut sections = Sections::new();
+ sections.map.push_body(&object);
+ sut.write_sections(&sections, &String::from(""))?;
+
+ Ok(())
+ }
+
+ #[test]
+ fn write_sections_with_sources() -> Result {
+ let mut sut = Sut::new(vec![]);
+
+ sut.writer.write_callback = Some(Box::new(|event| match event {
+ Event::Empty(bytes_start) => {
+ let name = str::from_utf8(bytes_start.name())?;
+ assert_eq!("preproc:sym", name);
+
+ let mut attributes = bytes_start.attributes();
+ assert_eq!(7, attributes.clone().count());
+
+ let attr = attributes.next().expect("Expects attributes")?;
+ assert_eq!("type", str::from_utf8(attr.key)?);
+ assert_eq!("worksheet", str::from_utf8(&attr.value)?);
+
+ let attr = attributes.next().expect("Expects attributes")?;
+ assert_eq!("name", str::from_utf8(attr.key)?);
+ assert_eq!("name", str::from_utf8(&attr.value)?);
+
+ let attr = attributes.next().expect("Expects attributes")?;
+ assert_eq!("preproc:generated", str::from_utf8(attr.key)?);
+ assert_eq!("true", str::from_utf8(&attr.value)?);
+
+ let attr = attributes.next().expect("Expects attributes")?;
+ assert_eq!("src", str::from_utf8(attr.key)?);
+ assert_eq!("rootname", str::from_utf8(&attr.value)?);
+
+ let attr = attributes.next().expect("Expects attributes")?;
+ assert_eq!("parent", str::from_utf8(attr.key)?);
+ assert_eq!("parent", str::from_utf8(&attr.value)?);
+
+ let attr = attributes.next().expect("Expects attributes")?;
+ assert_eq!("yields", str::from_utf8(attr.key)?);
+ assert_eq!("yields", str::from_utf8(&attr.value)?);
+
+ let attr = attributes.next().expect("Expects attributes")?;
+ assert_eq!("desc", str::from_utf8(attr.key)?);
+ assert_eq!("sym desc", str::from_utf8(&attr.value)?);
+
+ Ok(())
+ }
+ _ => panic!("unexpected event"),
+ }));
+
+ let nsym = Symbol::new_dummy(SymbolIndex::from_u32(1), "name");
+ let ssym = Symbol::new_dummy(SymbolIndex::from_u32(2), "src");
+ let psym = Symbol::new_dummy(SymbolIndex::from_u32(3), "parent");
+ let ysym = Symbol::new_dummy(SymbolIndex::from_u32(4), "yields");
+ let fsym = Symbol::new_dummy(SymbolIndex::from_u32(5), "from");
+
+ let attrs = SymAttrs {
+ pkg_name: Some(&nsym),
+ src: Some(&ssym),
+ generated: true,
+ parent: Some(&psym),
+ yields: Some(&ysym),
+ desc: Some("sym desc".to_string()),
+ from: Some(vec![&fsym]),
+ virtual_: true,
+ ..Default::default()
+ };
+ let object = Object::Ident(&nsym, IdentKind::Worksheet, attrs.into());
+ let mut sections = Sections::new();
+ sections.map.push_body(&object);
+ sut.write_sections(&sections, &String::from("root"))?;
+
+ Ok(())
+ }
+
+ #[test]
+ fn write_froms() -> Result {
+ let mut sut = Sut::new(vec![]);
+
+ sut.writer.write_callback = Some(Box::new(|event| match event {
+ Event::Empty(bytes_start) => {
+ let name = str::from_utf8(bytes_start.name())?;
+ assert_eq!("l:from", name);
+
+ let mut attributes = bytes_start.attributes();
+ assert_eq!(1, attributes.clone().count());
+
+ let attr = attributes.next().expect("Expects attributes")?;
+ assert_eq!("name", str::from_utf8(attr.key)?);
+ assert_eq!("dest symbol", str::from_utf8(&attr.value)?);
+
+ Ok(())
+ }
+ _ => panic!("unexpected event"),
+ }));
+
+ let sym = Symbol::new_dummy(SymbolIndex::from_u32(1), "source symbol");
+ let symb = Symbol::new_dummy(SymbolIndex::from_u32(2), "dest symbol");
+
+ let mut src = Source::default();
+ src.from = Some(vec![&symb]);
+ let object = Object::Ident(&sym, IdentKind::Worksheet, src);
+ let mut sections = Sections::new();
+ sections.map.push_body(&object);
+ sut.write_froms(&sections)?;
+
+ Ok(())
+ }
+
+ #[test]
+ fn write_froms_no_from_no_write() -> Result {
+ let mut sut = Sut::new(vec![]);
+
+ sut.writer.write_callback = Some(Box::new(|event| match event {
+ _ => panic!("unexpected write"),
+ }));
+
+ let sym = Symbol::new_dummy(SymbolIndex::from_u32(1), "random_symbol");
+
+ let object =
+ Object::Ident(&sym, IdentKind::Worksheet, Source::default());
+ let mut sections = Sections::new();
+ sections.map.push_body(&object);
+ sut.write_froms(&sections)?;
+
+ Ok(())
+ }
+
+ #[test]
+ fn write_element() -> Result {
+ let mut sut = Sut::new(vec![]);
+
+ sut.writer.write_callback = Some(Box::new(|event| match event {
+ Event::Start(bytes) => {
+ let name = str::from_utf8(bytes.name());
+ match name {
+ Ok("foo") => {
+ let attributes = bytes.attributes();
+ assert_eq!(0, attributes.count());
+ Ok(())
+ }
+ _ => panic!("unreachable"),
+ }
+ }
+ Event::End(bytes) => {
+ let name = str::from_utf8(bytes.name());
+ match name {
+ Ok("foo") => Ok(()),
+ _ => panic!("unreachable"),
+ }
+ }
+ _ => panic!("did not match expected event"),
+ }));
+
+ sut.write_element(b"foo", |writer| Ok(writer))?;
+
+ Ok(())
+ }
+}