tamer: obj::xmlo: Bind packages to canonical name

NOTE: This fixes the aforementioned commit that caused the linker to
temporarily fail (670c5d3a5d at time of
writing).  This does introduce an extra forward slash into
`l:dep/preproc:sym/@src`, but that does not appear to cause any
problems.  That will eventually go away, so I'm not going to bother with it
any further.

As the `xmlo` file is lowered into AIR, the name will be prefixed with a
leading slash (if necessary, which it is atm) and will emit an
`Air::BindIdent`.

This means that packages will be properly indexed by their canonical name on
load, which will be important when we share this with tamec.

DEV-13162
main
Mike Gerwitz 2023-05-05 10:22:12 -04:00
parent 13bac8382f
commit 00492ace01
4 changed files with 71 additions and 19 deletions

View File

@ -125,7 +125,7 @@ impl ParseState for XmloToAir {
ctx.prog_name = Some(name.symbol()); ctx.prog_name = Some(name.symbol());
} }
Transition(Package(name)).incomplete() Transition(Package(name)).ok(Air::BindIdent(name))
} }
(st @ Package(..), PkgRootPath(relroot)) => { (st @ Package(..), PkgRootPath(relroot)) => {

View File

@ -51,7 +51,7 @@ fn data_from_package_event() {
#[rustfmt::skip] #[rustfmt::skip]
Ok(vec![ Ok(vec![
O(Air::PkgStart(S1)), O(Air::PkgStart(S1)),
Incomplete, // PkgName O(Air::BindIdent(SPair(name, S2))),
Incomplete, // PkgRootPath Incomplete, // PkgRootPath
O(Air::PkgEnd(S4)), O(Air::PkgEnd(S4)),
]), ]),
@ -81,7 +81,7 @@ fn adds_elig_as_root() {
#[rustfmt::skip] #[rustfmt::skip]
Ok(vec![ Ok(vec![
O(Air::PkgStart(S1)), O(Air::PkgStart(S1)),
Incomplete, // PkgName O(Air::BindIdent(SPair(name, S2))),
O(Air::IdentRoot(SPair(elig_sym, S3))), O(Air::IdentRoot(SPair(elig_sym, S3))),
O(Air::PkgEnd(S4)), // Eoh O(Air::PkgEnd(S4)), // Eoh
]), ]),
@ -91,6 +91,7 @@ fn adds_elig_as_root() {
#[test] #[test]
fn adds_sym_deps() { fn adds_sym_deps() {
let name = "name".into();
let sym_from = "from".into(); let sym_from = "from".into();
let sym_to1 = "to1".into(); let sym_to1 = "to1".into();
let sym_to2 = "to2".into(); let sym_to2 = "to2".into();
@ -98,7 +99,7 @@ fn adds_sym_deps() {
#[rustfmt::skip] #[rustfmt::skip]
let toks = vec![ let toks = vec![
PkgStart(S1), PkgStart(S1),
PkgName(SPair("name".into(), S2)), PkgName(SPair(name, S2)),
SymDepStart(SPair(sym_from, S3)), SymDepStart(SPair(sym_from, S3)),
Symbol(SPair(sym_to1, S4)), Symbol(SPair(sym_to1, S4)),
@ -110,7 +111,7 @@ fn adds_sym_deps() {
#[rustfmt::skip] #[rustfmt::skip]
Ok(vec![ Ok(vec![
O(Air::PkgStart(S1)), O(Air::PkgStart(S1)),
Incomplete, // PkgName O(Air::BindIdent(SPair(name, S2))),
Incomplete, // SymDepStart Incomplete, // SymDepStart
O(Air::IdentDep(SPair(sym_from, S3), SPair(sym_to1, S4))), O(Air::IdentDep(SPair(sym_from, S3), SPair(sym_to1, S4))),
O(Air::IdentDep(SPair(sym_from, S3), SPair(sym_to2, S5))), O(Air::IdentDep(SPair(sym_from, S3), SPair(sym_to2, S5))),
@ -122,6 +123,7 @@ fn adds_sym_deps() {
#[test] #[test]
fn sym_decl_with_src_not_added_and_populates_found() { fn sym_decl_with_src_not_added_and_populates_found() {
let name = "name".into();
let sym = "sym".into(); let sym = "sym".into();
let src_a = "src_a".into(); let src_a = "src_a".into();
let src_b = "src_b".into(); let src_b = "src_b".into();
@ -129,7 +131,7 @@ fn sym_decl_with_src_not_added_and_populates_found() {
#[rustfmt::skip] #[rustfmt::skip]
let toks = vec![ let toks = vec![
PkgStart(S1), PkgStart(S1),
PkgName(SPair("name".into(), S2)), PkgName(SPair(name, S2)),
SymDecl( SymDecl(
SPair(sym, S3), SPair(sym, S3),
@ -154,7 +156,7 @@ fn sym_decl_with_src_not_added_and_populates_found() {
#[rustfmt::skip] #[rustfmt::skip]
Ok(vec![ Ok(vec![
O(Air::PkgStart(S1)), O(Air::PkgStart(S1)),
Incomplete, // PkgName O(Air::BindIdent(SPair(name, S2))),
Incomplete, // SymDecl (@src) Incomplete, // SymDecl (@src)
Incomplete, // SymDecl (@src) Incomplete, // SymDecl (@src)
O(Air::PkgEnd(S5)), O(Air::PkgEnd(S5)),
@ -174,6 +176,7 @@ fn sym_decl_with_src_not_added_and_populates_found() {
#[test] #[test]
fn sym_decl_added_to_graph() { fn sym_decl_added_to_graph() {
let name = "name".into();
let sym_extern = "sym_extern".into(); let sym_extern = "sym_extern".into();
let sym_non_extern = "sym_non_extern".into(); let sym_non_extern = "sym_non_extern".into();
let sym_map = "sym_map".into(); let sym_map = "sym_map".into();
@ -183,7 +186,7 @@ fn sym_decl_added_to_graph() {
#[rustfmt::skip] #[rustfmt::skip]
let toks = vec![ let toks = vec![
PkgStart(S1), PkgStart(S1),
PkgName(SPair("name".into(), S2)), PkgName(SPair(name, S2)),
SymDecl( SymDecl(
SPair(sym_extern, S3), SPair(sym_extern, S3),
@ -226,7 +229,7 @@ fn sym_decl_added_to_graph() {
// Note that each of these will have their package names cleared // Note that each of these will have their package names cleared
// since this is considered to be the first package encountered. // since this is considered to be the first package encountered.
assert_eq!(Some(Ok(O(Air::PkgStart(S1)))), sut.next()); // PkgStart assert_eq!(Some(Ok(O(Air::PkgStart(S1)))), sut.next()); // PkgStart
assert_eq!(Some(Ok(Incomplete)), sut.next()); // PkgName assert_eq!(Some(Ok(O(Air::BindIdent(SPair(name, S2))))), sut.next());
assert_eq!( assert_eq!(
Some(Ok(O(Air::IdentExternDecl( Some(Ok(O(Air::IdentExternDecl(
SPair(sym_extern, S3), SPair(sym_extern, S3),
@ -316,7 +319,7 @@ fn sym_decl_pkg_name_retained_if_not_first() {
#[rustfmt::skip] #[rustfmt::skip]
Ok(vec![ Ok(vec![
O(Air::PkgStart(S1)), O(Air::PkgStart(S1)),
Incomplete, // PkgName O(Air::BindIdent(SPair(pkg_name, S2))),
O(Air::IdentDecl( O(Air::IdentDecl(
SPair(sym, S3), SPair(sym, S3),
@ -364,7 +367,7 @@ fn sym_decl_pkg_name_set_if_empty_and_not_first() {
#[rustfmt::skip] #[rustfmt::skip]
Ok(vec![ Ok(vec![
O(Air::PkgStart(S1)), O(Air::PkgStart(S1)),
Incomplete, // PkgName O(Air::BindIdent(SPair(pkg_name, S2))),
O(Air::IdentDecl( O(Air::IdentDecl(
SPair(sym, S3), SPair(sym, S3),
@ -400,13 +403,14 @@ fn ident_kind_conversion_error_propagates() {
#[test] #[test]
fn sets_fragment() { fn sets_fragment() {
let name = "name".into();
let sym = "sym".into(); let sym = "sym".into();
let frag = FragmentText::from("foo"); let frag = FragmentText::from("foo");
#[rustfmt::skip] #[rustfmt::skip]
let toks = vec![ let toks = vec![
PkgStart(S1), PkgStart(S1),
PkgName(SPair("name".into(), S2)), PkgName(SPair(name, S2)),
Fragment(SPair(sym, S3), frag.clone()), Fragment(SPair(sym, S3), frag.clone()),
Eoh(S4), Eoh(S4),
]; ];
@ -415,7 +419,7 @@ fn sets_fragment() {
#[rustfmt::skip] #[rustfmt::skip]
Ok(vec![ Ok(vec![
O(Air::PkgStart(S1)), O(Air::PkgStart(S1)),
Incomplete, // PkgName O(Air::BindIdent(SPair(name, S2))),
O(Air::IdentFragment(SPair(sym, S3), frag)), O(Air::IdentFragment(SPair(sym, S3), frag)),
O(Air::PkgEnd(S4)), O(Air::PkgEnd(S4)),
]), ]),

View File

@ -29,7 +29,7 @@ use crate::{
ParseState, Token, Transition, TransitionResult, Transitionable, ParseState, Token, Transition, TransitionResult, Transitionable,
}, },
span::Span, span::Span,
sym::{st::raw, SymbolId}, sym::{st::raw, GlobalSymbolIntern, GlobalSymbolResolve, SymbolId},
xir::{ xir::{
attr::{Attr, AttrSpan}, attr::{Attr, AttrSpan},
flat::{Text, XirfToken as Xirf}, flat::{Text, XirfToken as Xirf},
@ -227,9 +227,10 @@ impl<SS: XmloState, SD: XmloState, SF: XmloState> ParseState
// which can result in confusing output depending on the context; // which can result in confusing output depending on the context;
// we ought to retain _both_ token- and value-spans. // we ought to retain _both_ token- and value-spans.
Transition(Package(span)).ok(match name { Transition(Package(span)).ok(match name {
QN_NAME => { QN_NAME => XmloToken::PkgName(SPair(
XmloToken::PkgName(SPair(value, aspan.value_span())) canonical_slash(value),
} aspan.value_span(),
)),
QN_UUROOTPATH => { QN_UUROOTPATH => {
XmloToken::PkgRootPath(SPair(value, aspan.value_span())) XmloToken::PkgRootPath(SPair(value, aspan.value_span()))
} }
@ -321,6 +322,27 @@ impl<SS: XmloState, SD: XmloState, SF: XmloState> ParseState
} }
} }
/// Introduce a leading `/` to `name` if missing.
///
/// A new [`SymbolId`] will be allocated if the leading slash is missing
/// from `name.
///
/// The XSLT-based compiler at the time of writing produced canonical names
/// _without_ a leading slash.
/// This convention was not changed until TAMER,
/// so that canonical paths could be used as namespecs for import in an
/// unambiguous way.
/// We want to support both,
/// so that TAMER-compiled object files will also work.
fn canonical_slash(name: SymbolId) -> SymbolId {
let s = name.lookup_str();
match s.starts_with('/') {
true => name,
false => format!("/{s}").intern(),
}
}
impl<SS: XmloState, SD: XmloState, SF: XmloState> Display impl<SS: XmloState, SD: XmloState, SF: XmloState> Display
for XmloReader<SS, SD, SF> for XmloReader<SS, SD, SF>
{ {

View File

@ -54,7 +54,7 @@ fn fails_on_invalid_root() {
} }
fn common_parses_package_attrs(package: QName) { fn common_parses_package_attrs(package: QName) {
let name = "pkgroot".into(); let name = "/pkgroot".into();
let relroot = "../../".into(); let relroot = "../../".into();
let elig = "elig-class-yields".into(); let elig = "elig-class-yields".into();
@ -101,11 +101,37 @@ fn parses_package_attrs_with_ns_prefix() {
common_parses_package_attrs(("lv", "package").unwrap_into()); common_parses_package_attrs(("lv", "package").unwrap_into());
} }
// For compatibility with XSLT-based compiler.
#[test]
fn adds_missing_leading_slash_to_canonical_name() {
let name = "needs/leading".into();
#[rustfmt::skip]
let toks = [
open(QN_PACKAGE, S1, Depth(0)),
attr("name", name, (S2, S3)),
close(Some(QN_PACKAGE), S2, Depth(0)),
]
.into_iter();
let sut = Sut::parse(toks);
assert_eq!(
#[rustfmt::skip]
Ok(vec![
O(PkgStart(S1)),
O(PkgName(SPair("/needs/leading".into(), S3))),
Incomplete,
]),
sut.collect(),
);
}
// Maintains BC with existing system, // Maintains BC with existing system,
// but this ought to reject in the future. // but this ought to reject in the future.
#[test] #[test]
fn ignores_unknown_package_attr() { fn ignores_unknown_package_attr() {
let name = "pkgroot".into(); let name = "/pkgroot".into();
#[rustfmt::skip] #[rustfmt::skip]
let toks = [ let toks = [