tamer: obj::xmlo: Add Pkg nodes for identifiers

This modifies the xmlo reader, xmlo->AIR lowering, and AIR->ASG to introduce
a package for identifiers.  It does not yet, however, add edges from the
package to the identifier.

Once edges are added, the DFS will change in undesirable ways, which will
require a new implementation.  This is desirable to decouple from Petgraph
anyway, and then will be able to restore the prior single-pass sort+cycle
check.

That will also encapsulate visiting behavior within the `asg::graph` module
and, in turn, allow encapsulating `Asg.graph` finally.

DEV-13162
main
Mike Gerwitz 2023-04-21 16:24:11 -04:00
parent 34dad122fd
commit 48d9bca3b7
8 changed files with 277 additions and 156 deletions

View File

@ -232,27 +232,27 @@ impl ParseState for AirAggregate {
tok @ (AirExpr(..) | AirBind(..) | AirTpl(..) | AirDoc(..)),
) => Transition(Empty).err(AsgError::PkgExpected(tok.span())),
(Empty, AirIdent(IdentDecl(name, kind, src))) => {
(Toplevel(oi_pkg), AirIdent(IdentDecl(name, kind, src))) => {
let asg = ctx.asg_mut();
let oi_root = asg.root(name);
asg.lookup_or_missing(oi_root, name)
.declare(asg, name, kind, src)
.map(|_| ())
.transition(Empty)
.transition(Toplevel(oi_pkg))
}
(Empty, AirIdent(IdentExternDecl(name, kind, src))) => {
(Toplevel(oi_pkg), AirIdent(IdentExternDecl(name, kind, src))) => {
let asg = ctx.asg_mut();
let oi_root = asg.root(name);
asg.lookup_or_missing(oi_root, name)
.declare_extern(asg, name, kind, src)
.map(|_| ())
.transition(Empty)
.transition(Toplevel(oi_pkg))
}
(Empty, AirIdent(IdentDep(name, dep))) => {
(Toplevel(oi_pkg), AirIdent(IdentDep(name, dep))) => {
let asg = ctx.asg_mut();
let oi_root = asg.root(dep);
@ -260,24 +260,24 @@ impl ParseState for AirAggregate {
let oi_to = asg.lookup_or_missing(oi_root, dep);
oi_from.add_opaque_dep(ctx.asg_mut(), oi_to);
Transition(Empty).incomplete()
Transition(Toplevel(oi_pkg)).incomplete()
}
(Empty, AirIdent(IdentFragment(name, text))) => {
(Toplevel(oi_pkg), AirIdent(IdentFragment(name, text))) => {
let asg = ctx.asg_mut();
let oi_root = asg.root(name);
asg.lookup_or_missing(oi_root, name)
.set_fragment(asg, text)
.map(|_| ())
.transition(Empty)
.transition(Toplevel(oi_pkg))
}
(Empty, AirIdent(IdentRoot(name))) => {
(Toplevel(oi_pkg), AirIdent(IdentRoot(name))) => {
let asg = ctx.asg_mut();
asg.root(name).root_ident(asg, name);
Transition(Empty).incomplete()
Transition(Toplevel(oi_pkg)).incomplete()
}
(st, tok @ AirIdent(..)) => todo!("{st:?}, {tok:?}"),

View File

@ -24,12 +24,11 @@ use super::{super::Ident, *};
use crate::{
asg::{
graph::object::{ObjectRel, ObjectRelFrom, ObjectRelatable},
IdentKind, ObjectIndexRelTo, Source,
IdentKind, ObjectIndexRelTo, Source, TransitionError,
},
parse::{ParseError, Parsed, Parser},
span::dummy::*,
};
use std::assert_matches::assert_matches;
type Sut = AirAggregate;
@ -38,17 +37,38 @@ use Parsed::Incomplete;
#[test]
fn ident_decl() {
let id = SPair("foo".into(), S1);
let id = SPair("foo".into(), S2);
let kind = IdentKind::Tpl;
let src = Source {
src: Some("test/decl".into()),
..Default::default()
};
let toks = vec![IdentDecl(id, kind.clone(), src.clone())].into_iter();
#[rustfmt::skip]
let toks = vec![
PkgStart(S1),
IdentDecl(id, kind.clone(), src.clone()),
// Attempt re-declaration.
IdentDecl(id, kind.clone(), src.clone()),
PkgEnd(S3),
].into_iter();
let mut sut = Sut::parse(toks);
assert_eq!(Some(Ok(Incomplete)), sut.next());
assert_eq!(
#[rustfmt::skip]
vec![
Ok(Incomplete), // PkgStart
Ok(Incomplete), // IdentDecl
// Redeclare identifier
Err(ParseError::StateError(AsgError::IdentTransition(
TransitionError::Redeclare(id, S2)
))),
// RECOVERY: Ignore redeclaration
Ok(Incomplete), // PkgEnd
],
sut.by_ref().collect::<Vec<Result<Parsed<()>, _>>>(),
);
let asg = sut.finalize().unwrap().into_context();
@ -59,35 +79,51 @@ fn ident_decl() {
assert_eq!(
Ok(ident),
Ident::declare(id)
.resolve(S1, kind.clone(), src.clone())
.resolve(S2, kind.clone(), src.clone())
.as_ref(),
);
// Re-instantiate the parser and test an error by attempting to
// redeclare the same identifier.
let bad_toks =
vec![IdentDecl(SPair(id.symbol(), S2), kind, src)].into_iter();
let mut sut = Sut::parse_with_context(bad_toks, asg);
assert_matches!(
sut.next(),
Some(Err(ParseError::StateError(AsgError::IdentTransition(_)))),
);
}
#[test]
fn ident_extern_decl() {
let id = SPair("foo".into(), S1);
let id = SPair("foo".into(), S2);
let re_id = SPair("foo".into(), S3);
let kind = IdentKind::Tpl;
let different_kind = IdentKind::Meta;
let src = Source {
src: Some("test/decl-extern".into()),
..Default::default()
};
let toks = vec![IdentExternDecl(id, kind.clone(), src.clone())].into_iter();
#[rustfmt::skip]
let toks = vec![
PkgStart(S1),
IdentExternDecl(id, kind.clone(), src.clone()),
// Redeclare with a different kind
IdentExternDecl(re_id, different_kind.clone(), src.clone()),
PkgEnd(S4),
].into_iter();
let mut sut = Sut::parse(toks);
assert_eq!(Some(Ok(Incomplete)), sut.next());
assert_eq!(
#[rustfmt::skip]
vec![
Ok(Incomplete), // PkgStart
Ok(Incomplete), // IdentDecl
// Redeclare identifier with a different kind
Err(ParseError::StateError(AsgError::IdentTransition(
TransitionError::ExternResolution(
id,
kind.clone(),
(different_kind, S3)
)
))),
// RECOVERY: Ignore redeclaration
Ok(Incomplete), // PkgEnd
],
sut.by_ref().collect::<Vec<Result<Parsed<()>, _>>>(),
);
let asg = sut.finalize().unwrap().into_context();
@ -97,32 +133,33 @@ fn ident_extern_decl() {
assert_eq!(
Ok(ident),
Ident::declare(id).extern_(S1, kind, src.clone()).as_ref(),
);
// Re-instantiate the parser and test an error by attempting to
// redeclare with a different kind.
let different_kind = IdentKind::Meta;
let bad_toks =
vec![IdentExternDecl(SPair(id.symbol(), S2), different_kind, src)]
.into_iter();
let mut sut = Sut::parse_with_context(bad_toks, asg);
assert_matches!(
sut.next(),
Some(Err(ParseError::StateError(AsgError::IdentTransition(_)))),
Ident::declare(id).extern_(S2, kind, src.clone()).as_ref(),
);
}
#[test]
fn ident_dep() {
let id = SPair("foo".into(), S1);
let dep = SPair("dep".into(), S2);
let id = SPair("foo".into(), S2);
let dep = SPair("dep".into(), S3);
#[rustfmt::skip]
let toks = vec![
PkgStart(S1),
IdentDep(id, dep),
PkgEnd(S4),
].into_iter();
let toks = vec![IdentDep(id, dep)].into_iter();
let mut sut = Sut::parse(toks);
assert_eq!(Some(Ok(Incomplete)), sut.next());
assert_eq!(
#[rustfmt::skip]
Ok(vec![
Incomplete, // PkgStart
Incomplete, // IdentDep
Incomplete, // PkgEnd
]),
sut.by_ref().collect(),
);
let asg = sut.finalize().unwrap().into_context();
@ -135,7 +172,7 @@ fn ident_dep() {
#[test]
fn ident_fragment() {
let id = SPair("frag".into(), S1);
let id = SPair("frag".into(), S2);
let kind = IdentKind::Tpl;
let src = Source {
src: Some("test/frag".into()),
@ -143,18 +180,36 @@ fn ident_fragment() {
};
let frag = "fragment text".into();
#[rustfmt::skip]
let toks = vec![
// Identifier must be declared before it can be given a
// fragment.
IdentDecl(id, kind.clone(), src.clone()),
IdentFragment(id, frag),
]
.into_iter();
PkgStart(S1),
// Identifier must be declared before it can be given a
// fragment.
IdentDecl(id, kind.clone(), src.clone()),
IdentFragment(id, frag),
// Reset fragment (error)
IdentFragment(id, frag),
// RECOVERY: Ignore reset
PkgEnd(S4),
] .into_iter();
let mut sut = Sut::parse(toks);
assert_eq!(Some(Ok(Incomplete)), sut.next()); // IdentDecl
assert_eq!(Some(Ok(Incomplete)), sut.next()); // IdentFragment
assert_eq!(
#[rustfmt::skip]
vec![
Ok(Incomplete), // PkgStart
Ok(Incomplete), // IdentDecl
Ok(Incomplete), // IdentFragment
// Reset fragment
Err(ParseError::StateError(AsgError::IdentTransition(
TransitionError::BadFragmentDest(id)
))),
// RECOVERY: Ignore reset
Ok(Incomplete), // PkgEnd
],
sut.by_ref().collect::<Vec<Result<Parsed<()>, _>>>(),
);
let asg = sut.finalize().unwrap().into_context();
@ -165,32 +220,36 @@ fn ident_fragment() {
assert_eq!(
Ok(ident),
Ident::declare(id)
.resolve(S1, kind.clone(), src.clone())
.resolve(S2, kind.clone(), src.clone())
.and_then(|resolved| resolved.set_fragment(frag))
.as_ref(),
);
// Re-instantiate the parser and test an error by attempting to
// re-set the fragment.
let bad_toks = vec![IdentFragment(id, frag)].into_iter();
let mut sut = Sut::parse_with_context(bad_toks, asg);
assert_matches!(
sut.next(),
Some(Err(ParseError::StateError(AsgError::IdentTransition(_)))),
);
}
// Adding a root before the identifier exists should add a
// `Ident::Missing`.
#[test]
fn ident_root_missing() {
let id = SPair("toroot".into(), S1);
let id = SPair("toroot".into(), S2);
#[rustfmt::skip]
let toks = vec![
PkgStart(S1),
IdentRoot(id),
PkgEnd(S3),
].into_iter();
let toks = vec![IdentRoot(id)].into_iter();
let mut sut = Sut::parse(toks);
assert_eq!(Some(Ok(Incomplete)), sut.next());
assert_eq!(
#[rustfmt::skip]
Ok(vec![
Incomplete, // PkgStart
Incomplete, // IdentRoot
Incomplete, // PkgEnd
]),
sut.by_ref().collect(),
);
let asg = sut.finalize().unwrap().into_context();
@ -208,7 +267,7 @@ fn ident_root_missing() {
#[test]
fn ident_root_existing() {
let id = SPair("toroot".into(), S1);
let id = SPair("toroot".into(), S2);
let kind = IdentKind::Tpl;
let src = Source {
src: Some("test/root-existing".into()),
@ -219,16 +278,27 @@ fn ident_root_existing() {
// otherwise we won't be testing the right thing.
assert!(!kind.is_auto_root());
#[rustfmt::skip]
let toks = vec![
IdentDecl(id, kind.clone(), src.clone()),
IdentRoot(SPair(id.symbol(), S2)),
PkgStart(S1),
IdentDecl(id, kind.clone(), src.clone()),
IdentRoot(SPair(id.symbol(), S3)),
PkgEnd(S3),
]
.into_iter();
let mut sut = Sut::parse(toks);
assert_eq!(Some(Ok(Incomplete)), sut.next()); // IdentDecl
assert_eq!(Some(Ok(Incomplete)), sut.next()); // IdentRoot
assert_eq!(
#[rustfmt::skip]
Ok(vec![
Incomplete, // PkgStart
Incomplete, // IdentDecl
Incomplete, // IdentRoot
Incomplete, // PkgEnd
]),
sut.by_ref().collect(),
);
let asg = sut.finalize().unwrap().into_context();
@ -240,7 +310,7 @@ fn ident_root_existing() {
assert_eq!(
Ok(ident),
Ident::declare(id)
.resolve(S1, kind.clone(), src.clone())
.resolve(S2, kind.clone(), src.clone())
.as_ref()
);
@ -257,21 +327,36 @@ fn declare_kind_auto_root() {
assert!(auto_kind.is_auto_root());
assert!(!no_auto_kind.is_auto_root());
let id_auto = SPair("auto_root".into(), S1);
let id_no_auto = SPair("no_auto_root".into(), S2);
let id_auto = SPair("auto_root".into(), S2);
let id_no_auto = SPair("no_auto_root".into(), S3);
let src = Source {
src: Some("src/pkg".into()),
..Default::default()
};
#[rustfmt::skip]
let toks = [
// auto-rooting
IdentDecl(id_auto, auto_kind, Default::default()),
// non-auto-rooting
IdentDecl(id_no_auto, no_auto_kind, Default::default()),
]
.into_iter();
PkgStart(S1),
// auto-rooting
IdentDecl(id_auto, auto_kind, src.clone()),
// non-auto-rooting
IdentDecl(id_no_auto, no_auto_kind, src),
PkgEnd(S4),
].into_iter();
let mut sut = Sut::parse(toks);
assert_eq!(Some(Ok(Incomplete)), sut.next());
assert_eq!(Some(Ok(Incomplete)), sut.next());
assert_eq!(
#[rustfmt::skip]
Ok(vec![
Incomplete, // PkgStart
Incomplete, // IdentDecl
Incomplete, // IdentDecl
Incomplete, // PkgEnd
]),
sut.by_ref().collect(),
);
let asg = sut.finalize().unwrap().into_context();

View File

@ -257,12 +257,9 @@ impl Asg {
/// you must resolve the [`ObjectIndex`] and inspect it.
///
/// See [`Self::index`] for more information.
pub(super) fn lookup_or_missing<
O: ObjectRelatable,
OS: ObjectIndexTreeRelTo<O>,
>(
pub(super) fn lookup_or_missing<O: ObjectRelatable>(
&mut self,
imm_env: OS,
imm_env: impl ObjectIndexTreeRelTo<O>,
name: SPair,
) -> ObjectIndex<O>
where

View File

@ -72,6 +72,10 @@ where
Object::Root(_) => (),
Object::Ident(ident) => dest.push(ident)?,
// Identifiers are parented to their packages,
// but they're nothing more than containers.
Object::Pkg(_) => (),
obj => {
diagnostic_unreachable!(
obj.internal_error(

View File

@ -92,6 +92,7 @@ type PackageSPair = SPair;
pub enum XmloToAir {
#[default]
PackageExpected,
PackageFound(Span),
Package(PackageSPair),
SymDep(PackageSPair, SPair),
/// End of header (EOH) reached.
@ -113,7 +114,13 @@ impl ParseState for XmloToAir {
use XmloToAir::*;
match (self, tok) {
(PackageExpected, PkgName(name)) => {
(PackageExpected, PkgStart(span)) => {
Transition(PackageFound(span)).ok(Air::PkgStart(span))
}
(PackageExpected, tok) => Transition(PackageExpected).dead(tok),
(PackageFound(_), PkgName(name)) => {
if ctx.is_first() {
ctx.prog_name = Some(name.symbol());
}
@ -143,7 +150,7 @@ impl ParseState for XmloToAir {
Transition(Package(name)).ok(Air::IdentRoot(pkg_elig))
}
(st @ (PackageExpected | Package(..)), PkgProgramFlag(_)) => {
(st @ (PackageFound(..) | Package(..)), PkgProgramFlag(_)) => {
// TODO: Unused
Transition(st).incomplete()
}
@ -219,13 +226,15 @@ impl ParseState for XmloToAir {
// Note that this uses `incomplete` because we have nothing
// to yield,
// but we are in fact done.
Transition(Done(span)).incomplete()
Transition(Done(span)).ok(Air::PkgEnd(span))
}
(st @ Package(..), tok @ (PkgName(..) | Symbol(..))) => {
Transition(st).dead(tok)
}
(st @ (PackageExpected | SymDep(..) | Done(..)), tok) => {
(
st @ Package(..),
tok @ (PkgStart(..) | PkgName(..) | Symbol(..)),
) => Transition(st).dead(tok),
(st @ (PackageFound(..) | SymDep(..) | Done(..)), tok) => {
Transition(st).dead(tok)
}
}
@ -242,6 +251,7 @@ impl Display for XmloToAir {
match self {
PackageExpected => write!(f, "expecting package definition"),
PackageFound(_) => write!(f, "package found, awaiting definition"),
Package(name) => {
write!(f, "expecting package `/{name}` declarations")
}

View File

@ -37,9 +37,10 @@ fn data_from_package_event() {
let relroot = "some/path".into();
let toks = vec![
PkgName(SPair(name, S1)),
PkgRootPath(SPair(relroot, S2)),
Eoh(S3),
PkgStart(S1),
PkgName(SPair(name, S2)),
PkgRootPath(SPair(relroot, S4)),
Eoh(S4),
]
.into_iter();
@ -47,9 +48,10 @@ fn data_from_package_event() {
assert_eq!(
Ok(vec![
Incomplete, // PkgName
Incomplete, // PkgRootPath
Incomplete, // Eoh
O(Air::PkgStart(S1)),
Incomplete, // PkgName
Incomplete, // PkgRootPath
O(Air::PkgEnd(S4)), // Eoh
]),
sut.by_ref().collect(),
);
@ -66,16 +68,18 @@ fn adds_elig_as_root() {
let elig_sym = "elig".into();
let toks = vec![
PkgName(SPair(name, S1)),
PkgEligClassYields(SPair(elig_sym, S2)),
Eoh(S3),
PkgStart(S1),
PkgName(SPair(name, S2)),
PkgEligClassYields(SPair(elig_sym, S3)),
Eoh(S4),
];
assert_eq!(
Ok(vec![
O(Air::PkgStart(S1)),
Incomplete, // PkgName
O(Air::IdentRoot(SPair(elig_sym, S2))),
Incomplete, // Eoh
O(Air::IdentRoot(SPair(elig_sym, S3))),
O(Air::PkgEnd(S4)), // Eoh
]),
Sut::parse(toks.into_iter()).collect(),
);
@ -88,20 +92,22 @@ fn adds_sym_deps() {
let sym_to2 = "to2".into();
let toks = vec![
PkgName(SPair("name".into(), S1)),
SymDepStart(SPair(sym_from, S2)),
Symbol(SPair(sym_to1, S3)),
Symbol(SPair(sym_to2, S4)),
Eoh(S1),
PkgStart(S1),
PkgName(SPair("name".into(), S2)),
SymDepStart(SPair(sym_from, S3)),
Symbol(SPair(sym_to1, S4)),
Symbol(SPair(sym_to2, S5)),
Eoh(S6),
];
assert_eq!(
Ok(vec![
O(Air::PkgStart(S1)),
Incomplete, // PkgName
Incomplete, // SymDepStart
O(Air::IdentDep(SPair(sym_from, S2), SPair(sym_to1, S3))),
O(Air::IdentDep(SPair(sym_from, S2), SPair(sym_to2, S4))),
Incomplete, // Eoh
O(Air::IdentDep(SPair(sym_from, S3), SPair(sym_to1, S4))),
O(Air::IdentDep(SPair(sym_from, S3), SPair(sym_to2, S5))),
O(Air::PkgEnd(S6)),
]),
Sut::parse(toks.into_iter()).collect(),
);
@ -114,32 +120,34 @@ fn sym_decl_with_src_not_added_and_populates_found() {
let src_b = "src_b".into();
let toks = vec![
PkgName(SPair("name".into(), S1)),
PkgStart(S1),
PkgName(SPair("name".into(), S2)),
SymDecl(
SPair(sym, S2),
SPair(sym, S3),
SymAttrs {
src: Some(src_a),
..Default::default()
},
),
SymDecl(
SPair(sym, S3),
SPair(sym, S4),
SymAttrs {
src: Some(src_b),
..Default::default()
},
),
Eoh(S1),
Eoh(S5),
];
let mut sut = Sut::parse(toks.into_iter());
assert_eq!(
Ok(vec![
O(Air::PkgStart(S1)),
Incomplete, // PkgName
Incomplete, // SymDecl (@src)
Incomplete, // SymDecl (@src)
Incomplete, // Eoh
O(Air::PkgEnd(S5)),
]),
sut.by_ref().collect(),
);
@ -163,9 +171,10 @@ fn sym_decl_added_to_graph() {
let pkg_name = "pkg name".into();
let toks = vec![
PkgName(SPair("name".into(), S1)),
PkgStart(S1),
PkgName(SPair("name".into(), S2)),
SymDecl(
SPair(sym_extern, S1),
SPair(sym_extern, S3),
SymAttrs {
pkg_name: Some(pkg_name),
extern_: true,
@ -174,7 +183,7 @@ fn sym_decl_added_to_graph() {
},
),
SymDecl(
SPair(sym_non_extern, S2),
SPair(sym_non_extern, S4),
SymAttrs {
pkg_name: Some(pkg_name),
ty: Some(SymType::Meta),
@ -182,7 +191,7 @@ fn sym_decl_added_to_graph() {
},
),
SymDecl(
SPair(sym_map, S3),
SPair(sym_map, S5),
SymAttrs {
pkg_name: Some(pkg_name),
ty: Some(SymType::Map),
@ -190,24 +199,25 @@ fn sym_decl_added_to_graph() {
},
),
SymDecl(
SPair(sym_retmap, S4),
SPair(sym_retmap, S6),
SymAttrs {
pkg_name: Some(pkg_name),
ty: Some(SymType::RetMap),
..Default::default()
},
),
Eoh(S1),
Eoh(S7),
];
let mut sut = Sut::parse(toks.into_iter());
// Note that each of these will have their package names cleared
// 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(Incomplete)), sut.next()); // PkgName
assert_eq!(
Some(Ok(O(Air::IdentExternDecl(
SPair(sym_extern, S1),
SPair(sym_extern, S3),
IdentKind::Meta,
Source {
pkg_name: None,
@ -218,7 +228,7 @@ fn sym_decl_added_to_graph() {
);
assert_eq!(
Some(Ok(O(Air::IdentDecl(
SPair(sym_non_extern, S2),
SPair(sym_non_extern, S4),
IdentKind::Meta,
Source {
pkg_name: None,
@ -229,7 +239,7 @@ fn sym_decl_added_to_graph() {
);
assert_eq!(
Some(Ok(O(Air::IdentDecl(
SPair(sym_map, S3),
SPair(sym_map, S5),
IdentKind::Map,
Source {
pkg_name: None,
@ -240,7 +250,7 @@ fn sym_decl_added_to_graph() {
);
assert_eq!(
Some(Ok(O(Air::IdentDecl(
SPair(sym_retmap, S4),
SPair(sym_retmap, S6),
IdentKind::RetMap,
Source {
pkg_name: None,
@ -249,7 +259,8 @@ fn sym_decl_added_to_graph() {
)))),
sut.next(),
);
assert_eq!(Some(Ok(Incomplete)), sut.next()); // Eoh
assert_eq!(Some(Ok(O(Air::PkgEnd(S7)))), sut.next());
let ctx = sut.finalize().unwrap().into_context();
@ -274,30 +285,32 @@ fn sym_decl_pkg_name_retained_if_not_first() {
};
let toks = vec![
PkgName(SPair(pkg_name, S1)),
PkgStart(S1),
PkgName(SPair(pkg_name, S2)),
SymDecl(
SPair(sym, S2),
SPair(sym, S3),
SymAttrs {
pkg_name: Some(pkg_name),
ty: Some(SymType::Meta),
..Default::default()
},
),
Eoh(S1),
Eoh(S4),
];
assert_eq!(
Ok(vec![
O(Air::PkgStart(S1)),
Incomplete, // PkgName
O(Air::IdentDecl(
SPair(sym, S2),
SPair(sym, S3),
IdentKind::Meta,
Source {
pkg_name: Some(pkg_name),
..Default::default()
}
)),
Incomplete, // Eoh
O(Air::PkgEnd(S4)),
]),
Sut::parse_with_context(toks.into_iter(), ctx).collect(),
);
@ -316,30 +329,32 @@ fn sym_decl_pkg_name_set_if_empty_and_not_first() {
};
let toks = vec![
PkgName(SPair(pkg_name, S1)),
PkgStart(S1),
PkgName(SPair(pkg_name, S2)),
SymDecl(
SPair(sym, S2),
SPair(sym, S3),
SymAttrs {
// No name
ty: Some(SymType::Meta),
..Default::default()
},
),
Eoh(S1),
Eoh(S4),
];
assert_eq!(
Ok(vec![
O(Air::PkgStart(S1)),
Incomplete, // PkgName
O(Air::IdentDecl(
SPair(sym, S2),
SPair(sym, S3),
IdentKind::Meta,
Source {
pkg_name: Some(pkg_name), // Name inherited
..Default::default()
},
)),
Incomplete, // Eoh
O(Air::PkgEnd(S4)),
]),
Sut::parse_with_context(toks.into_iter(), ctx).collect(),
);
@ -351,8 +366,9 @@ fn ident_kind_conversion_error_propagates() {
let bad_attrs = SymAttrs::default();
let toks = vec![
PkgName(SPair("name".into(), S1)),
SymDecl(SPair(sym, S2), bad_attrs),
PkgStart(S1),
PkgName(SPair("name".into(), S2)),
SymDecl(SPair(sym, S3), bad_attrs),
Eoh(S1),
];
@ -367,16 +383,18 @@ fn sets_fragment() {
let frag = FragmentText::from("foo");
let toks = vec![
PkgName(SPair("name".into(), S1)),
Fragment(SPair(sym, S2), frag.clone()),
Eoh(S1),
PkgStart(S1),
PkgName(SPair("name".into(), S2)),
Fragment(SPair(sym, S3), frag.clone()),
Eoh(S4),
];
assert_eq!(
Ok(vec![
O(Air::PkgStart(S1)),
Incomplete, // PkgName
O(Air::IdentFragment(SPair(sym, S2), frag)),
Incomplete, // Eoh
O(Air::IdentFragment(SPair(sym, S3), frag)),
O(Air::PkgEnd(S4)),
]),
Sut::parse(toks.into_iter()).collect(),
);

View File

@ -49,6 +49,9 @@ use crate::{
/// be useful and can't be easily skipped without parsing.
#[derive(Debug, PartialEq, Eq)]
pub enum XmloToken {
/// A new package has been found.
PkgStart(Span),
/// Canonical package name.
PkgName(SPair),
/// Relative path from package to project root.
@ -109,7 +112,8 @@ impl Token for XmloToken {
// important since these initial tokens seed
// `Parser::last_span`,
// which is used for early error messages.
PkgName(SPair(_, span))
PkgStart(span)
| PkgName(SPair(_, span))
| PkgRootPath(SPair(_, span))
| PkgProgramFlag(span)
| PkgEligClassYields(SPair(_, span))
@ -127,6 +131,7 @@ impl Display for XmloToken {
use XmloToken::*;
match self {
PkgStart(_) => write!(f, "package start"),
PkgName(sym) => write!(f, "package of name {}", TtQuote::wrap(sym)),
PkgRootPath(sym) => {
write!(f, "package root path {}", TtQuote::wrap(sym))
@ -208,8 +213,9 @@ impl<SS: XmloState, SD: XmloState, SF: XmloState> ParseState
use XmloReader::*;
match (self, tok) {
(Ready, Xirf::Open(QN_LV_PACKAGE | QN_PACKAGE, span, ..)) => {
Transition(Package(span.tag_span())).incomplete()
(Ready, Xirf::Open(QN_LV_PACKAGE | QN_PACKAGE, ospan, ..)) => {
let span = ospan.tag_span();
Transition(Package(span)).ok(XmloToken::PkgStart(span))
}
(Ready, tok) => {

View File

@ -81,7 +81,7 @@ fn common_parses_package_attrs(package: QName) {
assert_eq!(
Ok(vec![
Incomplete,
O(PkgStart(S1)),
O(PkgName(SPair(name, S3))),
O(PkgRootPath(SPair(relroot, S3))),
// Span for the program flag is the attr name,
@ -127,7 +127,7 @@ fn ignores_unknown_package_attr() {
assert_eq!(
Ok(vec![
Incomplete,
O(PkgStart(S1)),
O(PkgName(SPair(name, S3))),
Incomplete, // The unknown attribute
Incomplete,
@ -669,6 +669,7 @@ fn xmlo_composite_parsers_header() {
assert_eq!(
Ok(vec![
O(PkgStart(S1)),
O(SymDecl(SPair(sym_name, S3), Default::default(),)),
O(SymDepStart(SPair(symdep_name, S3))),
O(Fragment(SPair(symfrag_id, S4), frag)),