diff --git a/tamer/src/obj/xmlo/air.rs b/tamer/src/obj/xmlo/air.rs index 703bba71..165de3f5 100644 --- a/tamer/src/obj/xmlo/air.rs +++ b/tamer/src/obj/xmlo/air.rs @@ -29,6 +29,7 @@ use fxhash::FxHashSet; use crate::{ asg::{air::Air, IdentKind, Source}, diagnose::{AnnotatedSpan, Diagnostic}, + fmt::{DisplayWrapper, TtQuote}, obj::xmlo::{SymAttrs, SymType}, parse::{util::SPair, ParseState, ParseStatus, Transition, Transitionable}, span::Span, @@ -95,6 +96,7 @@ pub enum XmloToAir { PackageFound(Span), Package(PackageSPair), SymDep(PackageSPair, SPair), + SymDepEnded(PackageSPair, Span), /// End of header (EOH) reached. Done(Span), } @@ -212,13 +214,22 @@ impl ParseState for XmloToAir { .transition(Package(pkg_name)) } - (Package(pkg_name) | SymDep(pkg_name, _), Fragment(name, text)) => { + (Package(pkg_name) | SymDep(pkg_name, _), SymDepEnd(span)) => { + Transition(SymDepEnded(pkg_name, span)).incomplete() + } + + ( + Package(pkg_name) + | SymDep(pkg_name, _) + | SymDepEnded(pkg_name, _), + Fragment(name, text), + ) => { Transition(Package(pkg_name)).ok(Air::IdentFragment(name, text)) } // We don't need to read any further than the end of the // header (symtable, sym-deps, fragments). - (Package(..) | SymDep(..), Eoh(span)) => { + (Package(..) | SymDep(..) | SymDepEnded(..), Eoh(span)) => { // It's important to set this _after_ we're done processing, // otherwise our `first` checks above will be inaccurate. ctx.first = false; @@ -234,15 +245,36 @@ impl ParseState for XmloToAir { tok @ (PkgStart(..) | PkgName(..) | Symbol(..)), ) => Transition(st).dead(tok), - (st @ (PackageFound(..) | SymDep(..) | Done(..)), tok) => { - Transition(st).dead(tok) - } + ( + st @ (PackageFound(..) | SymDep(..) | SymDepEnded(..) + | Done(..)), + tok, + ) => Transition(st).dead(tok), } } fn is_accepting(&self, _: &Self::Context) -> bool { matches!(*self, Self::Done(_)) } + + fn eof_tok(&self, _ctx: &Self::Context) -> Option { + use XmloToAir::*; + + match self { + // We are able to stop parsing immediately after symbol + // dependencies have ended if the caller wishes to ignore + // fragments. + // Pretend that we received an `Eoh` token in this case so that + // we can conclude parsing. + SymDepEnded(_, span) => Some(XmloToken::Eoh(*span)), + + Package(_) + | PackageExpected + | PackageFound(_) + | SymDep(_, _) + | Done(_) => None, + } + } } impl Display for XmloToAir { @@ -258,6 +290,13 @@ impl Display for XmloToAir { SymDep(pkg_name, sym) => { write!(f, "expecting dependency for symbol `/{pkg_name}/{sym}`") } + SymDepEnded(pkg_name, _) => { + write!( + f, + "expecting fragments or end of header for package {}", + TtQuote::wrap(pkg_name) + ) + } Done(_) => write!(f, "done lowering xmlo into AIR"), } } diff --git a/tamer/src/obj/xmlo/air/test.rs b/tamer/src/obj/xmlo/air/test.rs index d74ebd1e..83cf984d 100644 --- a/tamer/src/obj/xmlo/air/test.rs +++ b/tamer/src/obj/xmlo/air/test.rs @@ -102,9 +102,10 @@ fn adds_sym_deps() { PkgName(SPair(name, S2)), SymDepStart(SPair(sym_from, S3)), - Symbol(SPair(sym_to1, S4)), - Symbol(SPair(sym_to2, S5)), - Eoh(S6), + Symbol(SPair(sym_to1, S4)), + Symbol(SPair(sym_to2, S5)), + SymDepEnd(S6), + Eoh(S7), ]; assert_eq!( @@ -115,7 +116,42 @@ fn adds_sym_deps() { Incomplete, // SymDepStart 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)), + Incomplete, // EndOfDeps + O(Air::PkgEnd(S7)), + ]), + Sut::parse(toks.into_iter()).collect(), + ); +} + +#[test] +fn accepting_state_after_sym_deps() { + let name = "name".into(); + let sym_from = "from".into(); + let sym_to1 = "to1".into(); + + #[rustfmt::skip] + let toks = vec![ + PkgStart(S1), + PkgName(SPair(name, S2)), + + SymDepStart(SPair(sym_from, S3)), + Symbol(SPair(sym_to1, S4)), + SymDepEnd(S5), + // Missing EOH; this should be a valid accepting state so that + // parsing can end early. + ]; + + assert_eq!( + #[rustfmt::skip] + Ok(vec![ + Incomplete, // PkgStart + O(Air::PkgStart(S1, SPair(name, S2))), + Incomplete, // SymDepStart + O(Air::IdentDep(SPair(sym_from, S3), SPair(sym_to1, S4))), + Incomplete, // EndOfDeps + // The missing EOH is added automatically. + // TODO: Span of last-encountered token. + O(Air::PkgEnd(S5)), ]), Sut::parse(toks.into_iter()).collect(), ); diff --git a/tamer/src/obj/xmlo/reader.rs b/tamer/src/obj/xmlo/reader.rs index 59e8a652..17562d04 100644 --- a/tamer/src/obj/xmlo/reader.rs +++ b/tamer/src/obj/xmlo/reader.rs @@ -80,6 +80,17 @@ pub enum XmloToken { /// object file representing the source location of this symbol. Symbol(SPair), + /// End of symbol dependencies. + /// + /// This token indicates that all symbols and their dependencies have + /// been parsed. + /// This is a safe stopping point for subsystems that do not wish to + /// load fragments. + /// + /// (This is not named `Eos` because that is not a commonly used + /// initialism and is not clear.) + SymDepEnd(Span), + /// Text (compiled code) fragment for a given symbol. /// /// This contains the compiler output for a given symbol, @@ -120,6 +131,7 @@ impl Token for XmloToken { | SymDecl(SPair(_, span), _) | SymDepStart(SPair(_, span)) | Symbol(SPair(_, span)) + | SymDepEnd(span) | Fragment(SPair(_, span), _) | Eoh(span) => *span, } @@ -155,6 +167,7 @@ impl Display for XmloToken { ) } Symbol(sym) => write!(f, "symbol {}", TtQuote::wrap(sym)), + SymDepEnd(_) => write!(f, "end of symbol dependencies"), Fragment(sym, _) => { write!(f, "symbol {} code fragment", TtQuote::wrap(sym)) } @@ -259,11 +272,11 @@ impl ParseState for XmloReader { .incomplete() } - (SymDeps(_, sd), Xirf::Close(None | Some(QN_P_SYM_DEPS), ..)) - if sd.is_accepting(ctx) => - { - Transition(FragmentsExpected).incomplete() - } + ( + SymDeps(_, sd), + Xirf::Close(None | Some(QN_P_SYM_DEPS), cspan, _), + ) if sd.is_accepting(ctx) => Transition(FragmentsExpected) + .ok(XmloToken::SymDepEnd(cspan.span())), (SymDeps(span, sd), tok) => sd.delegate( tok, @@ -308,7 +321,7 @@ impl ParseState for XmloReader { } fn is_accepting(&self, _: &Self::Context) -> bool { - *self == Self::Eoh || *self == Self::Done + matches!(self, Self::FragmentsExpected | Self::Eoh | Self::Done) } } diff --git a/tamer/src/obj/xmlo/reader/test.rs b/tamer/src/obj/xmlo/reader/test.rs index a784f3ba..23aa64bc 100644 --- a/tamer/src/obj/xmlo/reader/test.rs +++ b/tamer/src/obj/xmlo/reader/test.rs @@ -701,6 +701,7 @@ fn xmlo_composite_parsers_header() { O(PkgStart(S1)), O(SymDecl(SPair(sym_name, S3), Default::default(),)), O(SymDepStart(SPair(symdep_name, S3))), + O(SymDepEnd(S3)), O(Fragment(SPair(symfrag_id, S4), frag)), O(Eoh(S3)), ]), @@ -711,3 +712,45 @@ fn xmlo_composite_parsers_header() { .collect(), ); } + +#[test] +fn xmlo_end_after_sym_deps_before_fragments() { + let sym_name = "sym".into(); + let symdep_name = "symdep".into(); + + #[rustfmt::skip] + let toks_header = [ + open(QN_PACKAGE, S1, Depth(0)), + open(QN_P_SYMTABLE, S2, Depth(1)), + open(QN_P_SYM, S3, Depth(2)), + attr(QN_NAME, sym_name, (S2, S3)), + close_empty(S4, Depth(2)), + close(Some(QN_P_SYMTABLE), S4, Depth(1)), + + open(QN_P_SYM_DEPS, S2, Depth(1)), + open(QN_P_SYM_DEP, S3, Depth(3)), + attr(QN_NAME, symdep_name, (S2, S3)), + close(Some(QN_P_SYM_DEP), S4, Depth(3)), + close(Some(QN_P_SYM_DEPS), S3, Depth(1)), + + // End before fragments. + ] + .into_iter(); + + let sut = Sut::parse(toks_header); + + #[rustfmt::skip] + assert_eq!( + Ok(vec![ + O(PkgStart(S1)), + O(SymDecl(SPair(sym_name, S3), Default::default(),)), + O(SymDepStart(SPair(symdep_name, S3))), + O(SymDepEnd(S3)), + ]), + sut.filter(|parsed| match parsed { + Ok(Incomplete) => false, + _ => true, + }) + .collect(), + ); +}