From 6ac764108768e85db597c478ef7a754be416076b Mon Sep 17 00:00:00 2001 From: Joseph Frazer Date: Thu, 13 Feb 2020 15:43:25 -0500 Subject: [PATCH] [DEV-7083] TAMER: xmle writer This introduces the writer for xmle files. --- tamer/src/ir/asg/ident.rs | 2 +- tamer/src/ir/asg/mod.rs | 2 +- tamer/src/ld/poc.rs | 333 +++---------- tamer/src/obj/mod.rs | 1 + tamer/src/obj/xmle/mod.rs | 69 +++ tamer/src/obj/xmle/writer/mod.rs | 47 ++ tamer/src/obj/xmle/writer/writer.rs | 320 ++++++++++++ tamer/src/obj/xmle/writer/xmle.rs | 749 ++++++++++++++++++++++++++++ 8 files changed, 1252 insertions(+), 271 deletions(-) create mode 100644 tamer/src/obj/xmle/mod.rs create mode 100644 tamer/src/obj/xmle/writer/mod.rs create mode 100644 tamer/src/obj/xmle/writer/writer.rs create mode 100644 tamer/src/obj/xmle/writer/xmle.rs diff --git a/tamer/src/ir/asg/ident.rs b/tamer/src/ir/asg/ident.rs index 40386cd2..a8291ae8 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 ed364b1e..0e49eb2b 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 7dd764b1..53f9f7bb 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; @@ -74,14 +74,14 @@ pub fn main() -> Result<(), Box> { .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, -) -> 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> { - 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"), - } + 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: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"), - } + 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"), + )); } - for from in map_froms { - let name: &str = from; + 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"); - 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()], - )?; - } - - 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()], - )?; - } - - writer.write_event(Event::End(BytesEnd::borrowed(b"l:retmap-exec")))?; - writer.write_event(Event::Start(BytesStart::borrowed_name(b"l:static")))?; - - 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( - writer: &mut Writer, - idents: &ObjectVec, -) -> Result<(), Box> { - 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 3b447987..eb450615 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 00000000..1b1c360b --- /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 . + +//! `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 +//! +//! +//! +//! +//! +//! +//! +//! +//! function( input, callback ) {) +//! +//! +//! function( input, callback ) {) +//! +//! +//! function func_min( args , min1, min2) {return min1;} +//! +//! consts['CMP_OP_EQ'] = 1; +//! +//! ``` + +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 00000000..4287699f --- /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 . + +//! 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(§ions, 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 00000000..05d89f94 --- /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 . + +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 = result::Result; +pub type ObjectVec<'a, 'i> = Vec>; + +/// A wrapper around a `Write` object +/// +/// This is used to take the [`Sections`] and write out the xmle files. +pub trait Writer { + 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> + 'a>, +} + +impl<'a, 'i> Iterator for SectionIterator<'a, 'i> { + type Item = &'a Object<'i>; + + fn next(&mut self) -> Option { + 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 for WriterError { + fn from(err: IoError) -> Self { + WriterError::Io(err) + } +} + +impl From for WriterError { + fn from(err: Utf8Error) -> Self { + WriterError::Utf8(err) + } +} + +impl From 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 00000000..fc659a64 --- /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 . + +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 { + writer: XmlWriter, +} + +impl XmleWriter { + /// 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( + /// §ions, + /// &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(§ions, &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(§ions) + })? + .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( + &mut self, + name: &[u8], + callback: F, + ) -> Result<&mut XmleWriter> + where + F: FnOnce(&mut Self) -> Result<&mut XmleWriter>, + { + 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> { + 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> { + 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> { + 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> { + 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> { + 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> { + 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 { + inner: W, + pub write_callback: Option Fn(&Event<'a>) -> Result>>, + } + + impl MockXmlWriter { + 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>>( + &mut self, + event: E, + ) -> Result { + (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 = XmleWriter; + + #[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(§ions, &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(§ions, &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(§ions)?; + + 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(§ions)?; + + 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(()) + } +}