TAMER: POC: Output xmle
This is a working proof-of-concept that will be finalized in future commits.master
parent
85a4934db5
commit
6939753ca0
|
@ -1,4 +1,4 @@
|
|||
# This number is incremented for every linker change to force rebuilding
|
||||
# of xmle files.
|
||||
0
|
||||
1
|
||||
|
||||
|
|
|
@ -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 $< $@
|
||||
|
|
|
@ -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, "'", '' )" />
|
||||
<value-of select="translate( normalize-space(@desc), "'", '' )" />
|
||||
<text>'</text>
|
||||
</for-each>
|
||||
</function>
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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(),
|
||||
);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue