TAMER: POC: Output xmle

This is a working proof-of-concept that will be finalized in future commits.
master
Mike Gerwitz 2020-01-13 16:41:06 -05:00
parent 85a4934db5
commit 6939753ca0
9 changed files with 595 additions and 81 deletions

View File

@ -1,4 +1,4 @@
# This number is incremented for every linker change to force rebuilding
# of xmle files.
0
1

View File

@ -132,7 +132,8 @@ standalones: $(dest_standalone)
strip: $(dest_standalone_strip) ui/package.strip.js
%.xmle: %.xmlo $(path_tame)/.rev-xmle
$(TAME_TS)
$(TAME) link $< $@
@echo "WARNING: using WIP proof-of-concept linker!"
set -o pipefail; $(path_tame)/tamer/target/release/tameld $< | awk '/^<package/{p=1};p' > $@
%.js: %.xmle
$(TAME_TS)
$(TAME) standalone $< $@

View File

@ -62,7 +62,7 @@
@return compiled JS
-->
<template match="lv:package" mode="compiler:entry">
<template name="compiler:entry">
<!-- enclose everything in a self-executing function to sandbox our data -->
<text>( function() { </text>
<!-- to store debug information for equations (we have to put this out here
@ -76,7 +76,7 @@
</template>
<template match="lv:package" mode="compiler:entry-rater">
<template name="compiler:entry-rater">
<!-- the rater itself -->
<value-of select="$compiler:nl" />
<text>function rater( arglist, _canterm ) {</text>
@ -107,16 +107,13 @@
<text>/**@expose*/var genclasses = {};</text>
</template>
<template match="lv:package" mode="compiler:entry-classifier">
<template name="compiler:classifier">
<!-- allow classification of any arbitrary dataset -->
<value-of select="$compiler:nl" />
<text>rater.classify = function( args, _canterm ) {</text>
return rater( args, _canterm ).classes;
<text> };</text>
</template>
<template match="lv:package" mode="compiler:exit-classifier">
<!-- TODO: make sure fromMap has actually been compiled -->
<text>rater.classify.fromMap = function( args_base, _canterm ) { </text>
<text>var ret = {}; </text>
<text>rater.fromMap( args_base, function( args ) {</text>
@ -136,8 +133,10 @@
<text> }; </text>
</template>
<template match="lv:package" mode="compiler:exit-rater">
<template name="compiler:exit-rater">
<param name="name" as="xs:string "/>
<param name="symbols" as="element( preproc:sym )*" />
<param name="mapfrom" as="element()*" />
<value-of select="$compiler:nl" />
<text>return { </text>
@ -152,7 +151,7 @@
<!-- make the name of the supplier available -->
<text>/**@expose*/rater.supplier = '</text>
<value-of select="substring-after( @name, '/' )" />
<value-of select="substring-after( $name, '/' )" />
<text>'; </text>
<text>/**@expose*/rater.meta = meta;</text>
@ -174,20 +173,8 @@
$symbols[ @type='class' ] )" />
<text> }; </text>
<variable name="mapfrom" select="
preproc:symtable/preproc:sym[
@type='map'
]/preproc:from[
not(
@name = parent::preproc:sym
/preceding-sibling::preproc:sym[
@type='map'
]/preproc:from/@name
)
]
" />
<!-- mapped fields (external names) -->
<value-of select="$compiler:nl" />
<text>/**@expose*/rater.knownFields = {</text>
<for-each select="$mapfrom">
<if test="position() > 1">
@ -221,6 +208,7 @@
<text>'</text>
<value-of select="substring-after( @name, ':class:' )" />
<text>':'</text>
<!-- yields -->
<value-of select="@yields" />
<text>'</text>
</for-each>
@ -239,7 +227,7 @@
<value-of select="substring-after( @name, ':class:' )" />
<text>':'</text>
<!-- todo: escape -->
<value-of select="translate( @desc, &quot;'&quot;, '' )" />
<value-of select="translate( normalize-space(@desc), &quot;'&quot;, '' )" />
<text>'</text>
</for-each>
</function>

View File

@ -799,8 +799,6 @@
</with-param>
</call-template>
<apply-templates select="." mode="compiler:entry" />
<apply-templates select="." mode="l:link-meta">
<with-param name="deps" select="$deps" />
@ -808,9 +806,6 @@
<apply-templates select="." mode="l:link-worksheet">
<with-param name="deps" select="$deps" />
</apply-templates>
<apply-templates select="." mode="l:link-classifier">
<with-param name="deps" select="$deps" />
</apply-templates>
<apply-templates select="." mode="l:link-params">
<with-param name="deps" select="$deps" />
</apply-templates>
@ -824,9 +819,6 @@
<with-param name="deps" select="$deps" />
</apply-templates>
<!-- common stuff -->
<call-template name="compiler:static" />
<!-- finally, finish up -->
<call-template name="log:info">
<with-param name="name" select="'link'" />
@ -908,21 +900,6 @@
</template>
<template match="lv:package" mode="l:link-classifier">
<call-template name="log:info">
<with-param name="name" select="'link'" />
<with-param name="msg">
<text>** linking classifier...</text>
</with-param>
</call-template>
<!-- link everything that shall be a part of the classifier -->
<apply-templates select="." mode="compiler:entry-classifier" />
<!-- TODO: get rid of me completely! -->
<apply-templates select="." mode="compiler:exit-classifier" />
</template>
<template match="lv:package" mode="l:link-params">
<param name="deps" />
@ -990,8 +967,6 @@
</with-param>
</call-template>
<apply-templates select="." mode="compiler:entry-rater" />
<!-- TODO: this list of exclusions is a mess -->
<apply-templates select="." mode="l:do-link">
<with-param name="symbols" select="
@ -1008,10 +983,6 @@
<sequence select="l:link-exit-fragments(
$rater-exit-fragments,
. )" />
<apply-templates select="." mode="compiler:exit-rater">
<with-param name="symbols" select="$deps" />
</apply-templates>
</template>

View File

@ -36,10 +36,7 @@
xmlns:preproc="http://www.lovullo.com/rater/preproc">
<output
indent="yes"
omit-xml-declaration="yes"
/>
<output method="text" />
<include href="include/dslc-base.xsl" />
@ -54,9 +51,22 @@
<template match="/" priority="5">
<!-- the rater itself -->
<text>var rater = </text>
<!-- (moved from linker during TAMER POC linker) -->
<call-template name="compiler:entry" />
<call-template name="compiler:classifier" />
<call-template name="compiler:entry-rater" />
<value-of disable-output-escaping="yes" select="/lv:package/l:exec/text()" />
<text>; </text>
<!--(moved from linker during TAMER POC linker) -->
<call-template name="compiler:exit-rater">
<with-param name="name" select="/*/@name" />
<with-param name="symbols" select="/*/l:dep/preproc:sym" />
<with-param name="mapfrom" select="/*/l:map-from/l:from" />
</call-template>
<call-template name="compiler:static" />
<!-- maps may or may not exist -->
<variable name="map" select="/lv:package/l:map-exec" />
<variable name="retmap" select="/lv:package/l:retmap-exec" />
@ -64,7 +74,7 @@
<!-- store a reference to the mapper in rater.fromMap() -->
<text>rater.fromMap = </text>
<choose>
<when test="$map">
<when test="/lv:package/l:dep/preproc:sym[@type='map'][1]">
<value-of disable-output-escaping="yes" select="$map/text()" />
</when>
@ -79,7 +89,7 @@
<!-- return map -->
<text>rater._retmap = </text>
<choose>
<when test="$retmap">
<when test="/lv:package/l:dep/preproc:sym[@type='retmap'][1]">
<value-of disable-output-escaping="yes" select="$retmap/text()" />
</when>

View File

@ -113,6 +113,19 @@ pub struct Source<'i> {
///
/// Identifiers created by templates are not considered to be generated.
pub generated: bool,
/// Related identifiers.
///
/// These data represent a kluge created to add additional symbol
/// information in two different contexts:
///
/// - [`IdentKind::Map`] includes the name of the source field; and
/// - [`IdentKind::Func`] lists params in order (so that the compiler
/// knows application order).
///
/// TODO: We have `parent`, `yields`, and `from`.
/// We should begin to consolodate.
pub from: Option<Vec<&'i Symbol<'i>>>,
}
impl<'i> From<SymAttrs<'i>> for Source<'i> {
@ -125,6 +138,7 @@ impl<'i> From<SymAttrs<'i>> for Source<'i> {
parent: attrs.parent,
yields: attrs.yields,
desc: attrs.desc,
from: attrs.from,
}
}
}
@ -138,12 +152,14 @@ mod test {
fn source_from_sym_attrs() {
let psym = Symbol::new_dummy(SymbolIndex::from_u32(1), "parent");
let ysym = Symbol::new_dummy(SymbolIndex::from_u32(2), "yields");
let fsym = Symbol::new_dummy(SymbolIndex::from_u32(2), "from");
let attrs = SymAttrs {
generated: true,
parent: Some(&psym),
yields: Some(&ysym),
desc: Some("sym desc".to_string()),
from: Some(vec![&fsym]),
..Default::default()
};
@ -153,6 +169,7 @@ mod test {
parent: attrs.parent,
yields: attrs.yields,
desc: Some("sym desc".to_string()),
from: Some(vec![&fsym]),
},
attrs.into(),
);

View File

@ -131,6 +131,16 @@ pub struct SymAttrs<'i> {
///
/// This is used primarily by [`SymType::Class`] and [`SymType::Gen`].
pub desc: Option<String>,
/// Related identifiers.
///
/// These data represent a kluge created to add additional symbol
/// information in two different contexts:
///
/// - [`SymType::Map`] includes the name of the source field; and
/// - [`SymType::Func`] lists params in order (so that the compiler
/// knows application order).
pub from: Option<Vec<&'i Symbol<'i>>>,
}
/// Legacy symbol types.
@ -232,6 +242,18 @@ pub enum SymDtype {
Empty,
}
impl AsRef<str> for SymDtype {
/// Produce `xmlo`-compatible representation.
fn as_ref(&self) -> &str {
match self {
SymDtype::Boolean => &"boolean",
SymDtype::Integer => &"integer",
SymDtype::Float => &"float",
SymDtype::Empty => &"empty",
}
}
}
impl TryFrom<&[u8]> for SymDtype {
type Error = String;
@ -289,4 +311,10 @@ mod test {
bad => panic!("expected error: {:?}", bad),
}
}
#[test]
fn symdtype_as_str() {
let boolean: &str = SymDtype::Boolean.as_ref();
assert_eq!("boolean", boolean);
}
}

View File

@ -24,11 +24,13 @@ use crate::ir::asg::{Asg, DefaultAsg, Object, ObjectRef};
use crate::obj::xmlo::reader::{XmloError, XmloEvent, XmloReader};
use crate::sym::{DefaultInterner, Interner};
use petgraph::visit::DfsPostOrder;
use quick_xml::events::{BytesEnd, BytesStart, BytesText, Event};
use quick_xml::Writer;
use std::collections::{HashMap, HashSet};
use std::convert::TryInto;
use std::error::Error;
use std::fs;
use std::io::BufReader;
use std::io::{BufReader, Write};
type LinkerAsg<'i> = DefaultAsg<'i, global::ProgIdentSize>;
type LinkerObjectRef = ObjectRef<global::ProgIdentSize>;
@ -73,7 +75,9 @@ pub fn main() -> Result<(), Box<dyn Error>> {
let sorted = sort_deps(&depgraph, &roots);
println!("Sorted ({}): {:?}", sorted.len(), sorted);
//println!("Sorted ({}): {:?}", sorted.len(), sorted);
output_xmle(&depgraph, &interner, sorted)?;
Ok(())
}
@ -118,13 +122,18 @@ fn load_xmlo<'a, 'i, I: Interner<'i>>(
.lookup(sym)
.expect(&format!("missing sym for deps: `{}`", sym));
for dep_sym in deps {
let dep_node = depgraph.lookup(dep_sym).expect(&format!(
"missing dep sym for deps: `{}` -> `{}`",
sym, dep_sym
));
// Maps should not pull in symbols since we may end up
// mapping to params that are never actually used
if !sym.starts_with(":map:") {
for dep_sym in deps {
let dep_node =
depgraph.lookup(dep_sym).expect(&format!(
"missing dep sym for deps: `{}` -> `{}`",
sym, dep_sym
));
depgraph.add_dep(sym_node, dep_node);
depgraph.add_dep(sym_node, dep_node);
}
}
}
@ -147,8 +156,8 @@ fn load_xmlo<'a, 'i, I: Interner<'i>>(
// TODO: inefficient
let link_root = owned
&& (kindval == IdentKind::Meta
|| sym.starts_with(":map:")
|| sym.starts_with(":retmap:"));
|| kindval == IdentKind::Map
|| kindval == IdentKind::RetMap);
let node = depgraph.declare(sym, kindval, src)?;
@ -211,18 +220,35 @@ fn load_xmlo<'a, 'i, I: Interner<'i>>(
Ok(())
}
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>,
) -> Vec<&'a Object<'i>> {
) -> SortedDeps<'a, 'i> {
// @type=meta, @preproc:elig-class-yields
// @type={ret}map{,:head,:tail}
let mut deps: SortedDeps = Default::default();
// This is technically a topological sort, but functions have
// cycles. Once we have more symbol metadata, we can filter them out
// and actually invoke toposort.
let mut dfs = DfsPostOrder::empty(&depgraph);
let mut sorted = Vec::new();
//println!("discovered roots: {:?}", roots);
@ -233,10 +259,266 @@ fn sort_deps<'a, 'i>(
// TODO: can we encapsulate NodeIndex?
while let Some(index) = dfs.next(&depgraph) {
sorted.push(depgraph.get(index).unwrap());
let ident = depgraph.get(index).unwrap();
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::MapHead | IdentKind::Map | IdentKind::MapTail => {
deps.map.push(ident)
}
IdentKind::RetMapHead
| IdentKind::RetMap
| IdentKind::RetMapTail => deps.retmap.push(ident),
_ => deps.rater.push(ident),
},
_ => panic!("unexpected node: {:?}", ident),
}
}
sorted
deps
}
fn output_xmle<'a, 'i, I: Interner<'i>>(
depgraph: &'a LinkerAsg<'i>,
interner: &'i I,
sorted: SortedDeps<'a, 'i>,
) -> 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", "Title TODO"), // TODO
("program", "true"),
("name", "name/todo"), // TODO
]);
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"));
}
if let Some(parent) = src.parent {
attrs.push(("parent", parent));
}
if let Some(yields) = src.yields {
attrs.push(("yields", yields));
}
if let Some(desc) = &src.desc {
attrs.push(("desc", &desc));
}
let sym = BytesStart::owned_name(b"preproc:sym".to_vec())
.with_attributes(attrs);
writer.write_event(Event::Empty(sym))?;
}
_ => unreachable!("filtered out during sorting"),
}
}
writer.write_event(Event::End(BytesEnd::borrowed(b"l:dep")))?;
// This was not in the original linker, but we need to be able to convey
// this information for `standalones` (which has received some logic
// from the old linker for the time being).
writer
.write_event(Event::Start(BytesStart::borrowed_name(b"l:map-from")))?;
let mut map_froms = HashSet::<&str>::new();
for map_ident in &sorted.map {
match map_ident {
Object::Ident(_, _, src) | Object::IdentFragment(_, _, src, _) => {
if let Some(froms) = &src.from {
for from in froms {
map_froms.insert(from);
}
}
}
_ => unreachable!("filtered out during sorting"),
}
}
for from in map_froms {
let name: &str = from;
writer.write_event(Event::Empty(
BytesStart::borrowed_name(b"l:from")
.with_attributes(vec![("name", name)]),
))?;
}
writer.write_event(Event::End(BytesEnd::borrowed(b"l:map-from")))?;
writer
.write_event(Event::Start(BytesStart::borrowed_name(b"l:map-exec")))?;
if sorted.map.len() > 0 {
write_fragments(
&mut writer,
&vec![depgraph
.get(depgraph.lookup(interner.intern(":map:___head")).unwrap())
.unwrap()],
)?;
write_fragments(&mut writer, &sorted.map)?;
write_fragments(
&mut writer,
&vec![depgraph
.get(depgraph.lookup(interner.intern(":map:___tail")).unwrap())
.unwrap()],
)?;
}
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:exec")))?;
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)?;
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,
)))?;
}
_ => (),
}
}
Ok(())
}
#[cfg(test)]

View File

@ -129,7 +129,7 @@
//! # }
//! ```
use crate::ir::legacyir::{PackageAttrs, SymAttrs};
use crate::ir::legacyir::{PackageAttrs, SymAttrs, SymType};
use crate::sym::{Interner, Symbol};
#[cfg(test)]
use mock::MockBytesStart as BytesStart;
@ -233,6 +233,8 @@ impl<'i, B: BufRead, I: Interner<'i>> XmloReader<'i, B, I> {
/// ======
/// - Any of [`XmloError`].
/// See private methods for more information.
///
/// TODO: Augment failures with context
pub fn read_event<'a>(&mut self) -> XmloResult<XmloEvent<'i>> {
let event = self.reader.read_event(&mut self.buffer)?;
@ -278,15 +280,33 @@ impl<'i, B: BufRead, I: Interner<'i>> XmloReader<'i, B, I> {
),
// `func` symbols include additional data for param
// ordering. We don't care about that, so process the
// declaration and then skip the rest.
// ordering, which we don't care about. But `map` includes
// source field information which we want to keep. (We
// don't care about `retmap` for our purposes.)
b"preproc:sym" => {
let event = Self::process_sym(&ele, self.interner);
let mut event = Self::process_sym(&ele, self.interner)?;
self.reader
.read_to_end(ele.name(), &mut self.sub_buffer)?;
match &mut event {
XmloEvent::SymDecl(_, attrs)
if attrs.ty == Some(SymType::Map) =>
{
attrs.from = Some(Self::process_map_from(
self.interner,
&mut self.reader,
&mut self.sub_buffer,
)?);
event
Ok(event)
}
_ => {
self.reader.read_to_end(
ele.name(),
&mut self.sub_buffer,
)?;
Ok(event)
}
}
}
// Just like the outer match, recurse
@ -425,6 +445,56 @@ impl<'i, B: BufRead, I: Interner<'i>> XmloReader<'i, B, I> {
.ok_or(XmloError::UnassociatedSym)
}
/// Process `preproc:from` for `preproc:sym[@type="map"]` elements.
///
/// Map symbols contain additional information describing source
/// inputs external to the system.
///
/// Errors
/// ======
/// - [`XmloError::InvalidMapFrom`] if `@name` missing or if unexpected
/// data (e.g. elements) are encountered.
/// - [`XmloError::XmlError`] on XML parsing failure.
fn process_map_from<'a>(
interner: &'i I,
reader: &mut XmlReader<B>,
buffer: &mut Vec<u8>,
) -> XmloResult<Vec<&'i Symbol<'i>>> {
let mut froms = Vec::new();
loop {
match reader.read_event(buffer)? {
XmlEvent::Empty(ele) if ele.name() == b"preproc:from" => froms
.push(
ele.attributes()
.with_checks(false)
.filter_map(Result::ok)
.find(|attr| attr.key == b"name")
.map_or(
Err(XmloError::InvalidMapFrom(
"preproc:from/@name missing".into(),
)),
|attr| {
Ok(unsafe {
interner
.intern_utf8_unchecked(&attr.value)
})
},
)?,
),
XmlEvent::End(ele) if ele.name() == b"preproc:sym" => break,
// Note that whitespace counts as text
XmlEvent::Text(_) => (),
_ => Err(XmloError::InvalidMapFrom("unexpected data".into()))?,
};
}
Ok(froms)
}
/// Process `preproc:sym-dep` element.
///
/// This represents an adjacency list for a given identifier in the
@ -638,6 +708,8 @@ pub enum XmloError {
InvalidDim(String),
/// A `preproc:sym-dep` element was found, but is missing `@name`.
UnassociatedSymDep,
/// The `preproc:sym[@type="map"]` contains unexpected or invalid data.
InvalidMapFrom(String),
/// Invalid dependency in adjacency list
/// (`preproc:sym-dep/preproc:sym-ref`).
MalformedSymRef(String),
@ -673,6 +745,9 @@ impl Display for XmloError {
XmloError::InvalidDim(dim) => {
write!(fmt, "invalid preproc:sym/@dim `{}`", dim)
}
XmloError::InvalidMapFrom(msg) => {
write!(fmt, "invalid preproc:sym[@type=\"map\"]: {}", msg)
}
XmloError::UnassociatedSymDep => write!(
fmt,
"unassociated dependency list: preproc:sym-dep/@name missing"
@ -1282,7 +1357,9 @@ mod test {
);
}
// Some preproc:sym nodes have children (`func` symbols, specifically)
// Some preproc:sym nodes have children (`func` symbols,
// specifically) that we choose to ignore. See next test for
// data we do care about.
fn sym_nonempty_element(sut, interner) {
sut.reader.next_event = Some(Box::new(|_, _| {
// Notice Start, not Empty
@ -1318,6 +1395,146 @@ mod test {
// next symbol.
assert_eq!(Some("preproc:sym".into()), sut.reader.read_to_end_name);
}
// `map` symbols include information about their source
// fields.
fn sym_map_from(sut, interner) {
sut.reader.next_event = Some(Box::new(|_, event_i| match event_i {
// Notice Start, not Empty
0 => Ok(XmlEvent::Start(MockBytesStart::new(
b"preproc:sym",
Some(MockAttributes::new(vec![
MockAttribute::new(
b"name", b"sym-map-from",
),
MockAttribute::new(
b"type", b"map",
),
])),
))),
1 => Ok(XmlEvent::Empty(MockBytesStart::new(
b"preproc:from",
Some(MockAttributes::new(vec![
MockAttribute::new(
b"name", b"from-a",
),
])),
))),
// make sure that whitespace is permitted
2 => Ok(XmlEvent::Text(MockBytesText::new(
b" ",
))),
3 => Ok(XmlEvent::Empty(MockBytesStart::new(
b"preproc:from",
Some(MockAttributes::new(vec![
MockAttribute::new(
b"name", b"from-b",
),
])),
))),
4 => Ok(XmlEvent::End(MockBytesEnd::new(
b"preproc:sym",
))),
_ => Err(XmlError::UnexpectedEof(
format!("MockXmlReader out of events: {}", event_i).into(),
)),
}));
let result = sut.read_event()?;
assert_eq!(
XmloEvent::SymDecl(
interner.intern("sym-map-from"),
SymAttrs {
ty: Some(SymType::Map),
from: Some(vec![
interner.intern("from-a"),
interner.intern("from-b"),
]),
..Default::default()
},
),
result
);
// Should _not_ have read to the end.
assert_eq!(None, sut.reader.read_to_end_name);
}
fn sym_map_from_missing_name(sut, interner) {
sut.reader.next_event = Some(Box::new(|_, event_i| match event_i {
// Notice Start, not Empty
0 => Ok(XmlEvent::Start(MockBytesStart::new(
b"preproc:sym",
Some(MockAttributes::new(vec![
MockAttribute::new(
b"name", b"sym-map-from-bad",
),
MockAttribute::new(
b"type", b"map",
),
])),
))),
// missing @name
1 => Ok(XmlEvent::Empty(MockBytesStart::new(
b"preproc:from",
Some(MockAttributes::new(vec![])),
))),
2 => Ok(XmlEvent::End(MockBytesEnd::new(
b"preproc:sym",
))),
_ => Err(XmlError::UnexpectedEof(
format!("MockXmlReader out of events: {}", event_i).into(),
)),
}));
match sut.read_event() {
Err(XmloError::InvalidMapFrom(msg)) => {
assert!(msg.contains("preproc:from"))
}
bad => panic!("expected XmloError: {:?}", bad),
}
}
fn sym_map_from_unexpected_data(sut, interner) {
sut.reader.next_event = Some(Box::new(|_, event_i| match event_i {
// Notice Start, not Empty
0 => Ok(XmlEvent::Start(MockBytesStart::new(
b"preproc:sym",
Some(MockAttributes::new(vec![
MockAttribute::new(
b"name", b"sym-map-from-bad",
),
MockAttribute::new(
b"type", b"map",
),
])),
))),
// garbage
1 => Ok(XmlEvent::Empty(MockBytesStart::new(
b"preproc:nonsense",
Some(MockAttributes::new(vec![])),
))),
_ => Err(XmlError::UnexpectedEof(
format!("MockXmlReader out of events: {}", event_i).into(),
)),
}));
match sut.read_event() {
Err(XmloError::InvalidMapFrom(_)) => (),
bad => panic!("expected XmloError: {:?}", bad),
}
}
}
macro_rules! sym_test_reader_event {