tamer: Start of XIR-based xmle writer

This has been a long time coming, and has been repeatedly stashed as other
parts of the system have evolved to support it.  The introduction of the XIR
tree was to write tests for this (which are sloppy atm).

This currently writes out the `xmle` header and _most_ of the `l:dep`
section; it's missing the object-type-specific attributes.  There is,
relatively speaking, not much more work to do here.

The feature flag `wip-xir-xmle-writer` was introduced to toggle this system
in place of `XmleWriter`.  Initial benchmarks show that it will be
competitive with the quick-xml-based writer, but remember that is not the
goal: the purpose of this is to test XIR in a production system before we
continue to implement it for a frontend, and to refactor so that we do not
have multiple implementations writing XML files (once we echo the source XML
files).

I'm excited to get this done with so that I can move on.  This has been
rather exhausting.
main
Mike Gerwitz 2021-09-28 14:52:31 -04:00
parent 863d990cbd
commit 6864fbc1cd
15 changed files with 729 additions and 10 deletions

7
tamer/Cargo.lock generated
View File

@ -11,6 +11,12 @@ dependencies = [
"memchr",
]
[[package]]
name = "arrayvec"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be4dc07131ffa69b8072d35f5007352af944213cde02545e2103680baed38fcd"
[[package]]
name = "assert_cmd"
version = "0.10.2"
@ -302,6 +308,7 @@ dependencies = [
name = "tamer"
version = "0.0.0"
dependencies = [
"arrayvec",
"assert_cmd",
"bumpalo",
"exitcode",

View File

@ -27,6 +27,7 @@ assert_cmd = "0.10"
predicates = "1"
[dependencies]
arrayvec = ">= 0.7.1"
bumpalo = ">= 2.6.0"
fxhash = ">= 0.2.1"
petgraph = "0.5.1" # TODO: petgraph-graphml holds this back
@ -53,3 +54,6 @@ paste = ">= 1.0.5"
# compilation from XSLT into TAMER, and so the XSLT-based compiler must be
# expecting it so that it can skip those compilation steps.
wip-frontends = []
# Write xmle files using the new XIR-based writer.
wip-xir-xmle-writer = []

View File

@ -73,4 +73,5 @@
pub mod asg;
pub mod legacyir;
#[macro_use]
pub mod xir;

View File

@ -30,14 +30,42 @@
//! _This is a work in progress!_
use crate::span::Span;
use crate::sym::{GlobalSymbolIntern, SymbolId};
use crate::sym::{
CIdentStaticSymbolId, GlobalSymbolIntern, SymbolId, UriStaticSymbolId,
};
use std::convert::{TryFrom, TryInto};
use std::fmt::Display;
use std::ops::Deref;
pub mod pred;
pub mod tree;
pub mod writer;
macro_rules! qname_const_inner {
($name:ident = :$local:ident) => {
const $name: QName = QName::st_cid_local($local);
};
($name:ident = $prefix:ident:$local:ident) => {
const $name: QName = QName::st_cid($prefix, $local);
};
}
/// Construct a series of [`QName`] constants.
///
/// The syntax for each constant is `NAME: [PREFIX]:LOCAL`,
/// where `PREFIX` is optional.
///
/// See [`crate::sym::st`] for usable symbol constants.
#[macro_export]
macro_rules! qname_const {
($($name:ident: $($prefix:ident)? : $local:ident,)*) => {
$(
qname_const_inner!($name = $($prefix)?:$local);
)*
}
}
// TODO: Move into crate::sym if this is staying around.
macro_rules! newtype_symbol {
{$($(#[$meta:meta])* pub struct $name:ident;)*} => {
@ -250,6 +278,23 @@ impl QName {
pub fn local_name(&self) -> LocalPart {
self.1
}
/// Construct a constant QName from static C-style symbols.
pub const fn st_cid(
prefix_sym: CIdentStaticSymbolId,
local_sym: CIdentStaticSymbolId,
) -> Self {
Self(
Some(Prefix(NCName(prefix_sym.as_sym()))),
LocalPart(NCName(local_sym.as_sym())),
)
}
/// Construct a constant QName with a local name only from a static
/// C-style symbol.
pub const fn st_cid_local(local_sym: CIdentStaticSymbolId) -> Self {
Self(None, LocalPart(NCName(local_sym.as_sym())))
}
}
impl<P, L> TryFrom<(P, L)> for QName
@ -340,6 +385,20 @@ pub enum AttrValue {
Escaped(SymbolId),
}
impl AttrValue {
/// Construct a constant escaped attribute from a static C-style symbol.
pub const fn st_cid(sym: CIdentStaticSymbolId) -> Self {
Self::Escaped(sym.as_sym())
}
/// Construct a constant escaped attribute from a static URI symbol.
///
/// URIs are expected _not_ to contain quotes.
pub const fn st_uri(sym: UriStaticSymbolId) -> Self {
Self::Escaped(sym.as_sym())
}
}
/// Lightly-structured XML tokens with associated [`Span`]s.
///
/// This is a streamable IR for XML.
@ -465,6 +524,16 @@ mod test {
Ok(())
}
#[test]
fn local_name_from_option_tuple() -> TestResult {
let name: QName = (Option::<&str>::None, "foo").try_into()?;
assert_eq!(name.local_name(), "foo".try_into()?);
assert_eq!(None, name.prefix());
Ok(())
}
#[test]
fn fully_qualified_name() -> TestResult {
let name: QName = ("foons", "foo").try_into()?;

View File

@ -0,0 +1,37 @@
// XIR predicates
//
// Copyright (C) 2014-2021 Ryan Specialty Group, LLC.
//
// This file is part of TAME.
//
// 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/>.
//! Functional predicates for XIR token streams.
use super::{QName, Token};
#[inline]
pub fn not<T>(mut f: impl FnMut(&T) -> bool) -> impl FnMut(&T) -> bool {
move |x| !f(x)
}
#[inline]
pub fn open(name: QName) -> impl FnMut(&Token) -> bool {
move |tok| matches!(tok, Token::Open(tokname, _) if *tokname == name)
}
#[inline]
pub fn close(name: QName) -> impl FnMut(&Token) -> bool {
move |tok| matches!(tok, Token::Close(Some(tokname), _) if *tokname == name)
}

View File

@ -216,15 +216,50 @@ pub use attr::{Attr, AttrList, AttrParts, SimpleAttr};
pub enum Tree {
/// XML element.
Element(Element),
/// This variant exists purely because `#[non_exhaustive]` has no effect
/// within the crate.
///
/// This ensures that matches must account for other variants that will
/// be introduced in the future,
/// easing the maintenance burden
/// (for both implementation and unit tests).
_NonExhaustive,
}
impl Into<Option<Element>> for Tree {
#[inline]
fn into(self) -> Option<Element> {
match self {
Self::Element(ele) => Some(ele),
_ => None,
}
}
}
impl Tree {
/// If the tree object is an [`Element`], retrieve it.
pub fn element(self) -> Option<Element> {
/// Yield a reference to the inner value if it is an [`Element`],
/// otherwise [`None`].
#[inline]
pub fn as_element<'a>(&'a self) -> Option<&'a Element> {
match self {
Self::Element(ele) => Some(ele),
_ => None,
}
}
/// Yield the inner value if it is an [`Element`],
/// otherwise [`None`].
#[inline]
pub fn into_element(self) -> Option<Element> {
self.into()
}
/// Whether the inner value is an [`Element`].
#[inline]
pub fn is_element(&self) -> bool {
matches!(self, Self::Element(_))
}
}
/// Element node.
@ -247,6 +282,24 @@ pub struct Element {
}
impl Element {
/// Element name.
#[inline]
pub fn name(&self) -> QName {
self.name
}
/// Child [`Tree`] objects of this element.
#[inline]
pub fn children(&self) -> &Vec<Tree> {
&self.children
}
/// Attributes of this element.
#[inline]
pub fn attrs(&self) -> &AttrList {
&self.attrs
}
/// Opens an element for incremental construction.
///
/// This is intended for use by the parser to begin building an element.

View File

@ -103,6 +103,38 @@ impl Attr {
Self::Extensible(parts) => parts,
}
}
/// Attribute name.
#[inline]
pub fn name(&self) -> QName {
match self {
Self::Simple(attr) => attr.name,
Self::Extensible(attr) => attr.name,
}
}
/// Attempt to retrieve a cost-free atom from the attribute.
///
/// An atom is available if either
/// (a) this is [`Attr::Simple`]; or
/// (b) this is [`Attr::Extensible`] with one fragment.
/// Otherwise,
/// rather than assuming what the caller may want to do,
/// return [`None`] and let the caller decide how to proceed with
/// deriving an atom.
///
/// Since [`AttrValue`] implements [`Copy`],
/// this returns an owned value.
#[inline]
pub fn value_atom(&self) -> Option<AttrValue> {
match self {
Self::Simple(attr) => Some(attr.value),
Self::Extensible(attr) if attr.value_frags.len() == 1 => {
Some(attr.value_frags[0].0)
}
_ => None,
}
}
}
/// Element attribute with an atomic value.
@ -238,6 +270,18 @@ impl AttrList {
pub fn push(&mut self, attr: Attr) {
self.attrs.push(attr)
}
/// Search for an attribute of the given `name`.
///
/// _You should use this method only when a linear search makes sense._
///
/// This performs an `O(n)` linear search in the worst case.
/// Future implementations may perform an `O(1)` lookup under certain
/// circumstances,
/// but this should not be expected.
pub fn find(&self, name: QName) -> Option<&Attr> {
self.attrs.iter().find(|attr| attr.name() == name)
}
}
impl From<Vec<Attr>> for AttrList {

View File

@ -44,7 +44,29 @@ mod tree {
let tree = Tree::Element(ele.clone());
assert_eq!(Some(ele), tree.element());
assert_eq!(Some(&ele), tree.as_element());
}
}
mod attrs {
use super::*;
#[test]
fn linear_search_for_attr_name_in_list() {
let a = "a".unwrap_into();
let b = "b".unwrap_into();
let attra =
Attr::new(a, AttrValue::Escaped("a value".into()), (*S, *S2));
let attrb =
Attr::new(b, AttrValue::Escaped("b value".into()), (*S, *S2));
let attrs = AttrList::from([attra.clone(), attrb.clone()]);
assert_eq!(attrs.find(a), Some(&attra));
assert_eq!(attrs.find(b), Some(&attrb));
assert_eq!(attrs.find("unknown".unwrap_into()), None);
}
}

View File

@ -109,4 +109,13 @@
//! which is especially important since templates may expand into many
//! identifiers.
use crate::span::Span;
use crate::sym::st16;
pub mod poc;
/// Span denoting a general linker operation.
///
/// This span may be used when the source of a given object is the linker
/// and it is not possible to derive a more useful span.
pub const LSPAN: Span = Span::st_ctx(st16::CTX_LINKER);

View File

@ -28,7 +28,6 @@ use crate::ir::asg::{
Asg, DefaultAsg, IdentObject, IdentObjectData, Sections, SortableAsg,
SortableAsgError,
};
use crate::obj::xmle::writer::XmleWriter;
use crate::obj::xmlo::{AsgBuilder, AsgBuilderState, XmloReader};
use crate::sym::SymbolId;
use crate::sym::{GlobalSymbolIntern, GlobalSymbolResolve};
@ -36,7 +35,7 @@ use fxhash::FxBuildHasher;
use petgraph_graphml::GraphMl;
use std::error::Error;
use std::fs;
use std::io::{BufReader, BufWriter};
use std::io::{BufReader, BufWriter, Write};
use std::path::{Path, PathBuf};
type LinkerAsg = DefaultAsg<IdentObject, global::ProgIdentSize>;
@ -233,8 +232,29 @@ fn output_xmle<'a>(
}
let file = fs::File::create(output)?;
let mut xmle_writer = XmleWriter::new(BufWriter::new(file));
xmle_writer.write(&sorted, name, &relroot)?;
#[cfg(not(feature = "wip-xir-xmle-writer"))]
{
use crate::obj::xmle::writer::XmleWriter;
let mut xmle_writer = XmleWriter::new(BufWriter::new(file));
xmle_writer.write(&sorted, name, &relroot)?;
}
#[cfg(feature = "wip-xir-xmle-writer")]
{
use crate::ir::xir::writer::XmlWriter;
use crate::obj::xmle::xir::lower_iter;
eprintln!("warning: using wip-xir-xmle-writer");
let mut buf = BufWriter::new(file);
// TODO: check writer final state to make sure it actually finished
lower_iter(&sorted, name, relroot.intern())
.write(&mut buf, Default::default())?;
buf.flush()?;
}
Ok(())
}

View File

@ -34,6 +34,7 @@ pub mod frontend;
pub mod convert;
pub mod fs;
#[macro_use]
pub mod ir;
pub mod ld;
pub mod obj;

View File

@ -69,3 +69,4 @@
//! ```
pub mod writer;
pub mod xir;

View File

@ -0,0 +1,412 @@
// Lowering operations into XIR.
//
// Copyright (C) 2014-2021 Ryan Specialty Group, LLC.
//
// This file is part of TAME.
//
// 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/>.
//! WIP lowering to XIR-based `xmle`.
use crate::{
ir::{
asg::{IdentObject, IdentObjectData, Sections, SectionsIter},
xir::{AttrValue, QName, Token},
},
ld::LSPAN,
sym::{st::*, GlobalSymbolIntern, SymbolId},
};
use arrayvec::ArrayVec;
use std::array;
use std::iter::Chain;
qname_const! {
QN_DESC: :L_DESC,
QN_GENERATED: L_PREPROC:L_GENERATED,
QN_L_DEP: L_L:L_DEP,
QN_NAME: :L_NAME,
QN_PACKAGE: :L_PACKAGE,
QN_PARENT: :L_PARENT,
QN_PROGRAM: :L_PROGRAM,
QN_P_SYM: L_PREPROC:L_SYM,
QN_SRC: :L_SRC,
QN_TITLE: :L_TITLE,
QN_TYPE: :L_TYPE,
QN_UUROOTPATH: :L_UUROOTPATH,
QN_XMLNS: :L_XMLNS,
QN_XMLNS_L: L_XMLNS:L_L,
QN_XMLNS_PREPROC: L_XMLNS:L_PREPROC,
QN_YIELDS: :L_YIELDS,
}
const HEADER_SIZE: usize = 16;
type HeaderIter = array::IntoIter<Token, HEADER_SIZE>;
fn header(pkg_name: SymbolId, relroot: SymbolId) -> HeaderIter {
let pkg_name_val = AttrValue::Escaped(pkg_name);
// See [`array`] docs regarding differences between Rust 2018 and 2021
// editions regarding arrays and [`IntoIter`]. This was written in
// edition 2018; 2021 will be out in a few months at the time of
// writing.
array::IntoIter::new([
Token::Open(QN_PACKAGE, LSPAN),
Token::AttrName(QN_XMLNS, LSPAN),
Token::AttrValue(AttrValue::st_uri(URI_LV_RATER), LSPAN),
Token::AttrName(QN_XMLNS_PREPROC, LSPAN),
Token::AttrValue(AttrValue::st_uri(URI_LV_PREPROC), LSPAN),
Token::AttrName(QN_XMLNS_L, LSPAN),
Token::AttrValue(AttrValue::st_uri(URI_LV_LINKER), LSPAN),
Token::AttrName(QN_TITLE, LSPAN),
Token::AttrValue(pkg_name_val, LSPAN),
Token::AttrName(QN_PROGRAM, LSPAN),
Token::AttrValue(AttrValue::st_cid(L_TRUE), LSPAN),
Token::AttrName(QN_NAME, LSPAN),
Token::AttrValue(pkg_name_val, LSPAN),
Token::AttrName(QN_UUROOTPATH, LSPAN),
Token::AttrValue(AttrValue::Escaped(relroot), LSPAN),
Token::Open(QN_L_DEP, LSPAN),
])
}
const DEP_MAX_ATTRS: usize = 9;
const DEP_MAX_ATTRS_KEY_VAL: usize = DEP_MAX_ATTRS * 2;
const DEP_CLOSE: usize = 1; // open is never stored; see `refill_toks`
const DEP_TOK_SIZE: usize = DEP_MAX_ATTRS_KEY_VAL + DEP_CLOSE;
struct DepListIter<'a, T: IdentObjectData> {
iter: SectionsIter<'a, T>,
toks: ArrayVec<Token, DEP_TOK_SIZE>,
relroot: AttrValue,
}
type DepIter<'a, T> = DepListIter<'a, T>;
impl<'a, T: IdentObjectData> DepListIter<'a, T> {
fn refill_toks(&mut self) -> Option<Token> {
// Tokens will be popped, so push in reverse.
self.iter.next().map(|obj| {
let ident = obj.as_ident().expect("unexpected non-identifier object");
match ident {
IdentObject::Ident(sym, kind, src)
| IdentObject::IdentFragment(sym, kind, src, _) => (*sym, kind, src),
_ => unreachable!(
"identifier should have been filtered out during sorting: {:?}",
ident,
),
}
}).and_then(|(sym, kind, src)| {
self.toks.push(Token::Close(None, LSPAN));
if let Some(pkg_name) = src.pkg_name {
self.toks.push(Token::AttrValue(AttrValue::Escaped(pkg_name), LSPAN));
self.toks.push(Token::AttrValueFragment(self.relroot, LSPAN));
self.toks.push(Token::AttrName(QN_SRC, LSPAN));
}
self.toks_push_attr(QN_GENERATED, match src.generated {
true => Some(L_TRUE.as_sym()),
false => None,
});
// TODO: interning ought to be done during read, not by us
self.toks_push_attr(
QN_DESC,
src.desc.as_ref().map(|s| GlobalSymbolIntern::clone_uninterned(s.as_ref()))
);
self.toks_push_attr(QN_YIELDS, src.yields);
self.toks_push_attr(QN_PARENT, src.parent);
self.toks_push_attr(QN_NAME, Some(sym));
self.toks_push_attr(QN_TYPE, Some(*kind.as_ref()));
Some(Token::Open(QN_P_SYM, LSPAN))
})
}
#[inline]
fn toks_push_attr(&mut self, name: QName, value: Option<SymbolId>) {
if let Some(val) = value {
self.toks
.push(Token::AttrValue(AttrValue::Escaped(val), LSPAN));
self.toks.push(Token::AttrName(name, LSPAN));
}
}
}
impl<'a, T: IdentObjectData> Iterator for DepListIter<'a, T> {
type Item = Token;
fn next(&mut self) -> Option<Self::Item> {
self.toks.pop().or_else(|| self.refill_toks())
}
}
fn deps<'a, T: IdentObjectData>(
sections: &'a Sections<T>,
relroot: SymbolId,
) -> DepIter<'a, T> {
DepListIter {
iter: sections.iter_all(),
toks: ArrayVec::new(),
// TODO: we cannot trust that an arbitrary symbol is escaped; this
// needs better typing, along with other things.
relroot: AttrValue::Escaped(relroot),
}
}
const FOOTER_SIZE: usize = 2;
type FooterIter = array::IntoIter<Token, FOOTER_SIZE>;
#[inline]
fn footer() -> FooterIter {
array::IntoIter::new([
Token::Close(Some(QN_L_DEP), LSPAN),
Token::Close(Some(QN_PACKAGE), LSPAN),
])
}
/// Iterator that lazily lowers `xmle` object files into Xir.
///
/// This serves primarily to encapsulate the nasty iterator type without
/// having to resort to dynamic dispatch,
/// since this iterator will receive hundreds of thousands of calls for
/// large programs.
pub struct LowerIter<'a, T: IdentObjectData>(
Chain<Chain<HeaderIter, DepIter<'a, T>>, FooterIter>,
);
impl<'a, T: IdentObjectData> Iterator for LowerIter<'a, T> {
type Item = Token;
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
}
pub fn lower_iter<'a, T: IdentObjectData>(
sections: &'a Sections<T>,
pkg_name: SymbolId,
relroot: SymbolId,
) -> LowerIter<'a, T> {
LowerIter(
header(pkg_name, relroot)
.chain(deps(sections, relroot))
.chain(footer()),
)
}
// Note: at the time of writing, the Xir tree did not exist, and so these
// assert verbosely on the stream itself.
#[cfg(test)]
pub mod test {
use super::*;
use crate::convert::ExpectInto;
use crate::ir::{
asg::{Dim, IdentKind, Source},
xir::{
pred::{not, open},
tree::{parser_from, Attr},
},
};
use crate::sym::GlobalSymbolResolve;
type TestResult = Result<(), Box<dyn std::error::Error>>;
#[test]
fn test_produces_header() -> TestResult {
let empty = Sections::<IdentObject>::new();
let name = "test-pkg".intern();
let relroot = "rel/root/".intern();
let result = lower_iter(&empty, name, relroot)
.take_while(|tok| match tok {
// TODO
Token::Close(_, _) => false,
_ => true,
})
.collect::<Vec<Token>>();
assert_eq!(Token::Open(QN_PACKAGE, LSPAN), result[0]);
Ok(())
}
#[test]
fn test_closes_package() -> TestResult {
let empty = Sections::<IdentObject>::new();
let result =
lower_iter(&empty, "foo".intern(), "relroot".intern()).last();
assert_eq!(Some(Token::Close(Some(QN_PACKAGE), LSPAN)), result);
Ok(())
}
#[test]
fn test_writes_deps() -> TestResult {
let mut sections = Sections::new();
let relroot = "relroot-deps".intern();
let objs = [
IdentObject::Ident(
"a".intern(),
IdentKind::Meta,
Source {
desc: Some(String::from("test desc")),
..Default::default()
},
),
IdentObject::Ident(
"b".intern(),
IdentKind::MapHead,
Default::default(),
),
IdentObject::Ident(
"c".intern(),
IdentKind::Cgen(Dim::from_u8(1)),
Source {
yields: Some("yieldsValue".intern()),
parent: Some("cparent".intern()),
generated: true,
pkg_name: Some("pkg/name".intern()),
..Default::default()
},
),
];
objs.iter().for_each(|x| sections.consts.push_body(x));
let mut iter = parser_from(
lower_iter(&sections, "pkg".intern(), relroot)
.skip_while(not(open(QN_L_DEP))),
);
let given = iter
.next()
.expect("tree object expected")
.unwrap() // Tree
.into_element()
.expect("element expected");
// Sanity check to ensure we have the element we're expecting.
assert_eq!(QN_L_DEP, given.name());
let children = given.children();
assert_eq!(
children.len(),
objs.len(),
"expected child node for each dependency"
);
let p_syms = children.into_iter().map(|child| {
let ele =
child.as_element().expect("child expected to be an element");
assert_eq!(QN_P_SYM, ele.name());
ele
});
p_syms.enumerate().for_each(|(i, sym)| {
let ident = objs[i].as_ident().unwrap();
let attrs = sym.attrs();
assert_eq!(
attrs.find(QN_NAME).and_then(|a| a.value_atom()),
Some(AttrValue::Escaped(ident.name().unwrap())),
);
assert_eq!(
attrs.find(QN_TYPE).and_then(|a| a.value_atom()),
Some(AttrValue::Escaped(*ident.kind().unwrap().as_ref()))
);
let generated =
attrs.find(QN_GENERATED).and_then(|a| a.value_atom());
if let Some(Source {
generated: true, ..
}) = ident.src()
{
assert_eq!(
generated,
Some(AttrValue::Escaped("true".intern()))
);
} else {
assert_eq!(generated, None);
}
if let Some(Source { parent, .. }) = ident.src() {
assert_eq!(
attrs
.find("parent".unwrap_into())
.and_then(|a| a.value_atom()),
parent.map(|x| AttrValue::Escaped(x)),
);
}
if let Some(Source { yields, .. }) = ident.src() {
assert_eq!(
attrs
.find("yields".unwrap_into())
.and_then(|a| a.value_atom()),
yields.map(|x| AttrValue::Escaped(x)),
);
}
if let Some(Source {
desc: Some(desc), ..
}) = ident.src()
{
// We must take extra effort to compare these, since desc is
// uninterned and therefore cannot be compared as a
// `SymbolId`. Once the reader takes care of creating the
// symbol, we'll have no such problem.
match attrs
.find("desc".unwrap_into())
.and_then(|a| a.value_atom())
{
Some(AttrValue::Escaped(given)) => {
assert_eq!(desc, &given.lookup_str() as &str);
}
invalid => panic!("unexpected desc: {:?}", invalid),
}
}
if let Some(Source {
pkg_name: Some(pkg_name),
..
}) = ident.src()
{
match attrs.find("src".unwrap_into()) {
Some(Attr::Extensible(parts)) => {
assert_eq!(
parts.value_fragments(),
&vec![
(AttrValue::Escaped(relroot), LSPAN),
(AttrValue::Escaped(*pkg_name), LSPAN),
]
);
}
invalid => panic!("unexpected desc: {:?}", invalid),
}
}
});
Ok(())
}
}

View File

@ -183,7 +183,10 @@
//!
//! [rustc-span]: https://doc.rust-lang.org/stable/nightly-rustc/rustc_span/struct.Span.html
use crate::{global, sym::SymbolId};
use crate::{
global,
sym::{ContextStaticSymbolId, SymbolId},
};
use std::convert::TryInto;
/// A symbol size sufficient for holding interned paths.
@ -228,6 +231,15 @@ impl Span {
}
}
/// Create a constant span from a static context.
pub const fn st_ctx(sym: ContextStaticSymbolId) -> Self {
Self {
ctx: Context(PathIndex(sym.as_sym())),
offset: 0,
len: 0,
}
}
/// Create a span from a byte interval and context.
///
/// Panics

View File

@ -192,6 +192,13 @@ static_symbol_newtypes! {
/// its string value is incidental and should not be relied upon.
mark: MarkStaticSymbolId<global::ProgSymSize>,
/// Symbol representing a URI.
///
/// This is intended for use primarily as an XML namespace.
/// URIs are expected to _not_ contain quotes and other characters that
/// may need escaping in XML attributes.
uri: UriStaticSymbolId<global::ProgSymSize>,
/// Static 16-bit [`Span`](crate::span::Span) context.
///
/// These contexts are intended for use in generated code where a better
@ -227,8 +234,28 @@ pub mod st {
static_symbols! {
<global::ProgSymSize>;
L_TRUE: cid "true",
L_DEP: cid "dep",
L_DESC: cid "desc",
L_FALSE: cid "false",
L_GENERATED: cid "generated",
L_L: cid "l",
L_NAME: cid "name",
L_PACKAGE: cid "package",
L_PARENT: cid "parent",
L_PREPROC: cid "preproc",
L_PROGRAM: cid "program",
L_SRC: cid "src",
L_SYM: cid "sym",
L_TITLE: cid "title",
L_TRUE: cid "true",
L_TYPE: cid "type",
L_UUROOTPATH: cid "__rootpath",
L_XMLNS: cid "xmlns",
L_YIELDS: cid "yields",
URI_LV_RATER: uri "http://www.lovullo.com/rater",
URI_LV_PREPROC: uri "http://www.lovullo.com/preproc",
URI_LV_LINKER: uri "http://www.lovullo.com/linker",
// [Symbols will be added here as they are needed.]