parent
c2e6efc0b5
commit
6ac7641087
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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"),
|
||||
}
|
||||
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<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(())
|
||||
}
|
||||
|
|
|
@ -31,4 +31,5 @@
|
|||
//!
|
||||
//! [ELF]: https://en.wikipedia.org/wiki/Executable_and_Linkable_Format
|
||||
|
||||
pub mod xmle;
|
||||
pub mod xmlo;
|
||||
|
|
|
@ -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['CMP_OP_EQ'] = 1;</l:exec>
|
||||
//! </package>
|
||||
//! ```
|
||||
|
||||
pub mod writer;
|
|
@ -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(§ions, name, &String::from(""));
|
||||
//! ```
|
||||
|
||||
mod writer;
|
||||
mod xmle;
|
||||
|
||||
pub use writer::{Result, Section, Sections, Writer, WriterError};
|
||||
|
||||
pub use xmle::XmleWriter;
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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(
|
||||
/// §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<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(§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(())
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue