tamer: obj::xmlo::reader: preproc:sym-deps processing
This parses the symbol dependency list (adjacency list). I'm noticing some glaring issues in error handling, particularly that the token being parsed while an error occurs is not returned and so recovery is impossible. I'll have to address that later on, after I get this parser completed. Another previous question that I had a hard time answering in prior months was how I was going to compose boilerplate parsers, e.g. handling the parsing of single-attribute elements and such. A pattern is clearly taking shape, and with the composition of parsers more formalized, that'll be able to be abstracted away. But again, that's going to wait until after this parser is actually functioning. Too many delays so far. DEV-10863main
parent
3f8e397e57
commit
fb3da09fa4
|
@ -205,11 +205,11 @@ where
|
|||
// Unused
|
||||
}
|
||||
|
||||
(IS::None | IS::SymDep(_), XmloEvent::SymDepStart(sym)) => {
|
||||
(IS::None | IS::SymDep(_), XmloEvent::SymDepStart(sym, _)) => {
|
||||
istate = IS::SymDep(sym);
|
||||
}
|
||||
|
||||
(IS::SymDep(sym), XmloEvent::Symbol(dep_sym)) => {
|
||||
(IS::SymDep(sym), XmloEvent::Symbol(dep_sym, _)) => {
|
||||
self.add_dep_lookup(sym, dep_sym);
|
||||
}
|
||||
|
||||
|
@ -361,7 +361,7 @@ mod test {
|
|||
use super::*;
|
||||
use crate::asg::{DefaultAsg, FragmentText, IdentObject};
|
||||
use crate::obj::xmlo::{SymAttrs, SymType};
|
||||
use crate::span::UNKNOWN_SPAN;
|
||||
use crate::span::{DUMMY_SPAN, UNKNOWN_SPAN};
|
||||
use crate::sym::GlobalSymbolIntern;
|
||||
use std::collections::hash_map::RandomState;
|
||||
|
||||
|
@ -425,9 +425,9 @@ mod test {
|
|||
let sym_to2 = "to2".intern();
|
||||
|
||||
let evs = vec![
|
||||
Ok(XmloEvent::SymDepStart(sym_from)),
|
||||
Ok(XmloEvent::Symbol(sym_to1)),
|
||||
Ok(XmloEvent::Symbol(sym_to2)),
|
||||
Ok(XmloEvent::SymDepStart(sym_from, DUMMY_SPAN)),
|
||||
Ok(XmloEvent::Symbol(sym_to1, DUMMY_SPAN)),
|
||||
Ok(XmloEvent::Symbol(sym_to2, DUMMY_SPAN)),
|
||||
];
|
||||
|
||||
let _ = sut
|
||||
|
|
|
@ -52,14 +52,14 @@ pub enum XmloError {
|
|||
/// The provided `preproc:sym/@dim` is invalid.
|
||||
InvalidDim(SymbolId, Span),
|
||||
/// A `preproc:sym-dep` element was found, but is missing `@name`.
|
||||
UnassociatedSymDep,
|
||||
UnassociatedSymDep(Span),
|
||||
/// The `preproc:sym[@type="map"]` is missing a @name.
|
||||
MapFromNameMissing(SymbolId, Span),
|
||||
/// Multiple `preproc:from` nodes found.
|
||||
MapFromMultiple(SymbolId, Span),
|
||||
/// Invalid dependency in adjacency list
|
||||
/// (`preproc:sym-dep/preproc:sym-ref`).
|
||||
MalformedSymRef(String),
|
||||
MalformedSymRef(SymbolId, Span),
|
||||
/// A `preproc:fragment` element was found, but is missing `@id`.
|
||||
UnassociatedFragment,
|
||||
/// A `preproc:fragment` element was found, but is missing `text()`.
|
||||
|
@ -116,12 +116,17 @@ impl Display for XmloError {
|
|||
only once for symbol `{sym}` at {span}"
|
||||
)
|
||||
}
|
||||
Self::UnassociatedSymDep => write!(
|
||||
Self::UnassociatedSymDep(span) => write!(
|
||||
fmt,
|
||||
"unassociated dependency list: preproc:sym-dep/@name missing"
|
||||
"unassociated dependency list: preproc:sym-dep/@name \
|
||||
missing at {span}"
|
||||
),
|
||||
Self::MalformedSymRef(msg) => {
|
||||
write!(fmt, "malformed dependency ref: {}", msg)
|
||||
Self::MalformedSymRef(name, span) => {
|
||||
write!(
|
||||
fmt,
|
||||
"malformed dependency ref for symbol \
|
||||
{name} at {span}"
|
||||
)
|
||||
}
|
||||
Self::UnassociatedFragment => write!(
|
||||
fmt,
|
||||
|
|
|
@ -66,11 +66,11 @@ pub enum XmloEvent {
|
|||
|
||||
/// Begin adjacency list for a given symbol and interpret subsequent
|
||||
/// symbols as edges (dependencies).
|
||||
SymDepStart(SymbolId),
|
||||
SymDepStart(SymbolId, Span),
|
||||
|
||||
/// A symbol reference whose interpretation is dependent on the current
|
||||
/// state.
|
||||
Symbol(SymbolId),
|
||||
Symbol(SymbolId, Span),
|
||||
|
||||
/// Text (compiled code) fragment for a given symbol.
|
||||
///
|
||||
|
@ -103,6 +103,7 @@ qname_const! {
|
|||
QN_DTYPE: :L_DTYPE,
|
||||
QN_ELIG_CLASS_YIELDS: L_PREPROC:L_ELIG_CLASS_YIELDS,
|
||||
QN_EXTERN: :L_EXTERN,
|
||||
QN_FROM: L_PREPROC:L_FROM,
|
||||
QN_GENERATED: L_PREPROC:L_GENERATED,
|
||||
QN_ISOVERRIDE: :L_ISOVERRIDE,
|
||||
QN_LV_PACKAGE: L_LV:L_PACKAGE,
|
||||
|
@ -113,31 +114,44 @@ qname_const! {
|
|||
QN_SRC: :L_SRC,
|
||||
QN_SYM: L_PREPROC:L_SYM,
|
||||
QN_SYMTABLE: L_PREPROC:L_SYMTABLE,
|
||||
QN_SYM_DEPS: L_PREPROC:L_SYM_DEPS,
|
||||
QN_SYM_DEP: L_PREPROC:L_SYM_DEP,
|
||||
QN_SYM_REF: L_PREPROC:L_SYM_REF,
|
||||
QN_TYPE: :L_TYPE,
|
||||
QN_UUROOTPATH: :L_UUROOTPATH,
|
||||
QN_VIRTUAL: :L_VIRTUAL,
|
||||
QN_YIELDS: :L_YIELDS,
|
||||
QN_FROM: L_PREPROC:L_FROM,
|
||||
}
|
||||
|
||||
pub trait XmloSymtableState =
|
||||
ParseState<Token = Xirf, Object = (SymbolId, SymAttrs, Span)>
|
||||
where <Self as ParseState>::Error: Into<XmloError>;
|
||||
/// A parser capable of being composed with [`XmloReaderState`].
|
||||
pub trait XmloState = ParseState<Token = Xirf>
|
||||
where
|
||||
<Self as ParseState>::Error: Into<XmloError>,
|
||||
<Self as ParseState>::Object: Into<XmloEvent>;
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Eq)]
|
||||
pub enum XmloReaderState<SS: XmloSymtableState = SymtableState> {
|
||||
pub enum XmloReaderState<
|
||||
SS: XmloState = SymtableState,
|
||||
SD: XmloState = SymDepsState,
|
||||
> {
|
||||
/// Parser has not yet processed any input.
|
||||
#[default]
|
||||
Ready,
|
||||
/// Processing `package` attributes.
|
||||
Package,
|
||||
/// Expecting a symbol declaration or end of symbol table.
|
||||
/// Expecting a symbol declaration or closing `preproc:symtable`.
|
||||
Symtable(Span, SS),
|
||||
/// Symbol dependencies are expected next.
|
||||
SymDepsExpected,
|
||||
/// Expecting symbol dependency list or closing `preproc:sym-deps`.
|
||||
SymDeps(Span, SD),
|
||||
/// End of header parsing.
|
||||
Eoh,
|
||||
/// `xmlo` file has been fully read.
|
||||
Done,
|
||||
}
|
||||
|
||||
impl<SS: XmloSymtableState> ParseState for XmloReaderState<SS> {
|
||||
impl<SS: XmloState, SD: XmloState> ParseState for XmloReaderState<SS, SD> {
|
||||
type Token = Xirf;
|
||||
type Object = XmloEvent;
|
||||
type Error = XmloError;
|
||||
|
@ -177,19 +191,35 @@ impl<SS: XmloSymtableState> ParseState for XmloReaderState<SS> {
|
|||
(Symtable(_, ss), Xirf::Close(Some(QN_SYMTABLE), ..))
|
||||
if ss.is_accepting() =>
|
||||
{
|
||||
Transition(Done).incomplete()
|
||||
Transition(SymDepsExpected).incomplete()
|
||||
}
|
||||
|
||||
// TOOD: It'd be nice to augment errors with the symbol table
|
||||
// span as well (e.g. "while processing symbol table at <loc>").
|
||||
(Symtable(span, ss), tok) => ss.delegate(span, tok, Symtable),
|
||||
|
||||
(SymDepsExpected, Xirf::Open(QN_SYM_DEPS, span, _)) => {
|
||||
Transition(SymDeps(span, SD::default())).incomplete()
|
||||
}
|
||||
|
||||
(SymDeps(_, sd), Xirf::Close(Some(QN_SYM_DEPS), ..))
|
||||
if sd.is_accepting() =>
|
||||
{
|
||||
Transition(Eoh).incomplete()
|
||||
}
|
||||
|
||||
(SymDeps(span, sd), tok) => sd.delegate(span, tok, SymDeps),
|
||||
|
||||
(Eoh, Xirf::Close(Some(QN_PACKAGE), ..)) => {
|
||||
Transition(Done).incomplete()
|
||||
}
|
||||
|
||||
todo => todo!("{todo:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_accepting(&self) -> bool {
|
||||
*self == Self::Done
|
||||
*self == Self::Eoh || *self == Self::Done
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -427,5 +457,75 @@ impl From<(SymbolId, SymAttrs, Span)> for XmloEvent {
|
|||
}
|
||||
}
|
||||
|
||||
/// Symbol dependency list (graph adjacency list) parser for
|
||||
/// `preproc:sym-deps` children.
|
||||
///
|
||||
/// This parser expects a parent [`ParseState`] to indicate when dependency
|
||||
/// parsing ought to start and end—
|
||||
/// this parser does not recognize any opening or closing
|
||||
/// `preproc:sym-deps` tags.
|
||||
#[derive(Debug, Default, PartialEq, Eq)]
|
||||
pub enum SymDepsState {
|
||||
/// Symbol table declaration found;
|
||||
/// symbols declarations expected.
|
||||
#[default]
|
||||
Ready,
|
||||
SymUnnamed(Span),
|
||||
Sym(Span, SymbolId),
|
||||
SymRefUnnamed(Span, SymbolId, Span),
|
||||
SymRefDone(Span, SymbolId, Span),
|
||||
}
|
||||
|
||||
impl ParseState for SymDepsState {
|
||||
type Token = Xirf;
|
||||
type Object = XmloEvent;
|
||||
type Error = XmloError;
|
||||
|
||||
fn parse_token(self, tok: Self::Token) -> TransitionResult<Self> {
|
||||
use SymDepsState::*;
|
||||
|
||||
match (self, tok) {
|
||||
(Ready, Xirf::Open(QN_SYM_DEP, span, _)) => {
|
||||
Transition(SymUnnamed(span)).incomplete()
|
||||
}
|
||||
|
||||
(SymUnnamed(span), Xirf::Attr(Attr(QN_NAME, name, _))) => {
|
||||
Transition(Sym(span, name))
|
||||
.ok(XmloEvent::SymDepStart(name, span))
|
||||
}
|
||||
|
||||
(SymUnnamed(span), _) => Transition(SymUnnamed(span))
|
||||
.err(XmloError::UnassociatedSymDep(span)),
|
||||
|
||||
(Sym(span, name), Xirf::Open(QN_SYM_REF, span_ref, _)) => {
|
||||
Transition(SymRefUnnamed(span, name, span_ref)).incomplete()
|
||||
}
|
||||
|
||||
(
|
||||
SymRefUnnamed(span, name, span_ref),
|
||||
Xirf::Attr(Attr(QN_NAME, ref_name, (_, span_ref_name))),
|
||||
) => Transition(SymRefDone(span, name, span_ref))
|
||||
.ok(XmloEvent::Symbol(ref_name, span_ref_name)),
|
||||
|
||||
(SymRefUnnamed(span, name, span_ref), _) => {
|
||||
Transition(SymRefUnnamed(span, name, span_ref))
|
||||
.err(XmloError::MalformedSymRef(name, span_ref))
|
||||
}
|
||||
|
||||
(SymRefDone(span, name, _), Xirf::Close(..)) => {
|
||||
Transition(Sym(span, name)).incomplete()
|
||||
}
|
||||
|
||||
(Sym(..), Xirf::Close(..)) => Transition(Ready).incomplete(),
|
||||
|
||||
todo => todo!("sym-deps {todo:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_accepting(&self) -> bool {
|
||||
*self == Self::Ready
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
|
|
|
@ -510,11 +510,12 @@ where
|
|||
.with_checks(false)
|
||||
.filter_map(Result::ok)
|
||||
.find(|attr| attr.key == b"name")
|
||||
.map_or(Err(XmloError::UnassociatedSymDep), |attr| {
|
||||
Ok(unsafe { attr.value.intern_utf8_unchecked() })
|
||||
})?;
|
||||
.map_or(
|
||||
Err(XmloError::UnassociatedSymDep(UNKNOWN_SPAN)),
|
||||
|attr| Ok(unsafe { attr.value.intern_utf8_unchecked() }),
|
||||
)?;
|
||||
|
||||
event_queue.push_back(XmloEvent::SymDepStart(name));
|
||||
event_queue.push_back(XmloEvent::SymDepStart(name, UNKNOWN_SPAN));
|
||||
|
||||
loop {
|
||||
match reader.read_event(buffer)? {
|
||||
|
@ -529,7 +530,7 @@ where
|
|||
.find(|attr| attr.key == b"name")
|
||||
.map_or(
|
||||
Err(XmloError::MalformedSymRef(
|
||||
"preproc:sym-ref/@name missing".into(),
|
||||
name, UNKNOWN_SPAN
|
||||
)),
|
||||
|attr| {
|
||||
Ok(unsafe {
|
||||
|
@ -537,6 +538,7 @@ where
|
|||
})
|
||||
},
|
||||
)?,
|
||||
UNKNOWN_SPAN,
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -547,10 +549,11 @@ where
|
|||
// Note that whitespace counts as text
|
||||
XmlEvent::Text(_) => (),
|
||||
|
||||
_ => return Err(XmloError::MalformedSymRef(format!(
|
||||
// This is handled in a better way in the new parser.
|
||||
_ => panic!(
|
||||
"preproc:sym-dep must contain only preproc:sym-ref children for `{}`",
|
||||
name.lookup_str(),
|
||||
)))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -182,6 +182,7 @@ xmlo_tests! {
|
|||
);
|
||||
}
|
||||
|
||||
// DONE
|
||||
fn sym_dep_event(sut) {
|
||||
sut.reader.next_event = Some(Box::new(|_, event_i| match event_i {
|
||||
0 => Ok(XmlEvent::Start(MockBytesStart::new(
|
||||
|
@ -212,14 +213,15 @@ xmlo_tests! {
|
|||
|
||||
assert_eq!(
|
||||
vec![
|
||||
XmloEvent::SymDepStart("depsym".intern()),
|
||||
XmloEvent::Symbol("dep1".intern()),
|
||||
XmloEvent::Symbol("dep2".intern()),
|
||||
XmloEvent::SymDepStart("depsym".intern(), UNKNOWN_SPAN),
|
||||
XmloEvent::Symbol("dep1".intern(), UNKNOWN_SPAN),
|
||||
XmloEvent::Symbol("dep2".intern(), UNKNOWN_SPAN),
|
||||
],
|
||||
result
|
||||
);
|
||||
}
|
||||
|
||||
// DONE
|
||||
fn sym_dep_fails_with_missing_name(sut) {
|
||||
sut.reader.next_event = Some(Box::new(|_, _| {
|
||||
Ok(XmlEvent::Start(MockBytesStart::new(
|
||||
|
@ -229,11 +231,12 @@ xmlo_tests! {
|
|||
}));
|
||||
|
||||
match sut.read_event() {
|
||||
Err(XmloError::UnassociatedSymDep) => (),
|
||||
Err(XmloError::UnassociatedSymDep(_)) => (),
|
||||
bad => panic!("expected XmloError: {:?}", bad),
|
||||
}
|
||||
}
|
||||
|
||||
// DONE
|
||||
fn sym_dep_malformed_ref_missing_name(sut) {
|
||||
sut.reader.next_event = Some(Box::new(|_, event_i| match event_i {
|
||||
0 => Ok(XmlEvent::Start(MockBytesStart::new(
|
||||
|
@ -253,46 +256,13 @@ xmlo_tests! {
|
|||
}));
|
||||
|
||||
match sut.read_event() {
|
||||
Err(XmloError::MalformedSymRef(msg)) => {
|
||||
assert!(msg.contains("preproc:sym-ref/@name"))
|
||||
Err(XmloError::MalformedSymRef(name, _)) => {
|
||||
assert_eq!(name, "depsymbad".into());
|
||||
},
|
||||
bad => panic!("expected XmloError: {:?}", bad),
|
||||
}
|
||||
}
|
||||
|
||||
fn sym_dep_malformed_ref_unexpected_element(sut) {
|
||||
sut.reader.next_event = Some(Box::new(|_, event_i| match event_i {
|
||||
0 => Ok(XmlEvent::Start(MockBytesStart::new(
|
||||
b"preproc:sym-dep",
|
||||
Some(MockAttributes::new(vec![MockAttribute::new(
|
||||
b"name", b"depsym-unexpected",
|
||||
)])),
|
||||
))),
|
||||
// text is okay (e.g. whitespace)
|
||||
1 => Ok(XmlEvent::Text(MockBytesText::new(
|
||||
b" ",
|
||||
))),
|
||||
// unexpected (not a preproc:sym-ref)
|
||||
2 => Ok(XmlEvent::Empty(MockBytesStart::new(
|
||||
b"preproc:unexpected",
|
||||
Some(MockAttributes::new(vec![])),
|
||||
))),
|
||||
_ => Err(InnerXmlError::UnexpectedEof(
|
||||
format!("MockXmlReader out of events: {}", event_i).into(),
|
||||
)),
|
||||
}));
|
||||
|
||||
match sut.read_event() {
|
||||
Err(XmloError::MalformedSymRef(msg)) => {
|
||||
assert!(msg.contains("depsym-unexpected"))
|
||||
},
|
||||
bad => panic!("expected XmloError: {:?}", bad),
|
||||
}
|
||||
|
||||
// We should have gotten past the text
|
||||
assert_eq!(3, sut.reader.event_i, "Did not ignore Text");
|
||||
}
|
||||
|
||||
fn eoh_after_fragments(sut) {
|
||||
sut.reader.next_event = Some(Box::new(|_, _| {
|
||||
Ok(XmlEvent::End(MockBytesEnd::new(b"preproc:fragments")))
|
||||
|
|
|
@ -23,7 +23,7 @@ use super::*;
|
|||
use crate::{
|
||||
convert::ExpectInto,
|
||||
obj::xmlo::{SymDtype, SymType},
|
||||
parse::{ParseError, ParseState, ParseStatus, Parsed},
|
||||
parse::{ParseError, ParseState, Parsed},
|
||||
span::{Span, DUMMY_SPAN},
|
||||
sym::GlobalSymbolIntern,
|
||||
xir::{
|
||||
|
@ -37,6 +37,7 @@ const S1: Span = DUMMY_SPAN;
|
|||
const S2: Span = S1.offset_add(1).unwrap();
|
||||
const S3: Span = S2.offset_add(1).unwrap();
|
||||
const S4: Span = S3.offset_add(1).unwrap();
|
||||
const S5: Span = S4.offset_add(1).unwrap();
|
||||
|
||||
type Sut = XmloReaderState;
|
||||
|
||||
|
@ -135,70 +136,6 @@ fn ignores_unknown_package_attr() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xmlo_symtable_parser() {
|
||||
const SSTUB: Span = DUMMY_SPAN.offset_add(50).unwrap();
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Eq)]
|
||||
enum StubSymtableState {
|
||||
#[default]
|
||||
None,
|
||||
}
|
||||
|
||||
impl ParseState for StubSymtableState {
|
||||
type Token = Xirf;
|
||||
type Object = (SymbolId, SymAttrs, Span);
|
||||
type Error = XmloError;
|
||||
|
||||
fn parse_token(self, tok: Self::Token) -> TransitionResult<Self> {
|
||||
match tok {
|
||||
Xirf::Attr(Attr(QN_NAME, name, (s1, s2))) => {
|
||||
assert_eq!(s1, S1);
|
||||
assert_eq!(s2, S2);
|
||||
|
||||
Transition(Self::None).ok((
|
||||
name,
|
||||
SymAttrs::default(),
|
||||
SSTUB,
|
||||
))
|
||||
}
|
||||
tok => panic!("test expects @name but got {tok:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_accepting(&self) -> bool {
|
||||
*self == Self::None
|
||||
}
|
||||
}
|
||||
|
||||
let symname = "symname".into();
|
||||
let attrs = SymAttrs::default();
|
||||
|
||||
let toks = [
|
||||
Xirf::Open(QN_PACKAGE, S1, Depth(0)),
|
||||
Xirf::Open(QN_SYMTABLE, S2, Depth(1)),
|
||||
// Our stub parser doesn't need an opening or closing tag.
|
||||
// Note that S1 and S2 are expected.
|
||||
Xirf::Attr(Attr(QN_NAME, symname, (S1, S2))), // @name
|
||||
Xirf::Close(Some(QN_SYMTABLE), S4, Depth(1)),
|
||||
]
|
||||
.into_iter();
|
||||
|
||||
let sut = XmloReaderState::<StubSymtableState>::parse(toks);
|
||||
|
||||
assert_eq!(
|
||||
Ok(vec![
|
||||
Parsed::Incomplete, // <package
|
||||
Parsed::Incomplete, // <preproc:symtable
|
||||
// SSTUB is used to prove that StubSymtableState was used,
|
||||
// instead of the SS default (no, not a ship).
|
||||
Parsed::Object(XmloEvent::SymDecl(symname, attrs, SSTUB)),
|
||||
Parsed::Incomplete, // </preproc:symtable>
|
||||
]),
|
||||
sut.collect(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn symtable_err_missing_sym_name() {
|
||||
let toks = [
|
||||
|
@ -505,8 +442,136 @@ fn symtable_map_from_multiple() {
|
|||
.into_iter();
|
||||
|
||||
assert_eq!(
|
||||
Err(ParseError::StateError(XmloError::MapFromMultiple(name, S3))), // />
|
||||
Err(ParseError::StateError(XmloError::MapFromMultiple(name, S3))),
|
||||
SymtableState::parse(toks)
|
||||
.collect::<Result<Vec<Parsed<<SymtableState as ParseState>::Object>>, _>>(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sym_dep_event() {
|
||||
let name = "depsym".into();
|
||||
let dep1 = "dep1".into();
|
||||
let dep2 = "dep2".into();
|
||||
|
||||
let toks = [
|
||||
Xirf::Open(QN_SYM_DEP, S1, Depth(0)),
|
||||
Xirf::Attr(Attr(QN_NAME, name, (S2, S3))),
|
||||
// <preproc:sym-ref
|
||||
Xirf::Open(QN_SYM_REF, S2, Depth(1)),
|
||||
Xirf::Attr(Attr(QN_NAME, dep1, (S3, S4))),
|
||||
Xirf::Close(None, S4, Depth(1)),
|
||||
// />
|
||||
// <preproc:sym-ref
|
||||
Xirf::Open(QN_SYM_REF, S3, Depth(1)),
|
||||
Xirf::Attr(Attr(QN_NAME, dep2, (S4, S5))),
|
||||
Xirf::Close(None, S4, Depth(1)),
|
||||
// />
|
||||
Xirf::Close(Some(QN_SYM_DEP), S5, Depth(0)),
|
||||
]
|
||||
.into_iter();
|
||||
|
||||
assert_eq!(
|
||||
Ok(vec![
|
||||
Parsed::Incomplete, // <preproc:sym-ref
|
||||
Parsed::Object(XmloEvent::SymDepStart(name, S1)), // @name
|
||||
Parsed::Incomplete, // <preproc:sym-ref
|
||||
Parsed::Object(XmloEvent::Symbol(dep1, S4)), // @name
|
||||
Parsed::Incomplete, // />
|
||||
Parsed::Incomplete, // <preproc:sym-ref
|
||||
Parsed::Object(XmloEvent::Symbol(dep2, S5)), // @name
|
||||
Parsed::Incomplete, // />
|
||||
Parsed::Incomplete, // </preproc:sym-dep>
|
||||
]),
|
||||
SymDepsState::parse(toks).collect()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sym_dep_missing_name() {
|
||||
let toks = [
|
||||
Xirf::Open(QN_SYM_DEP, S1, Depth(0)),
|
||||
// missing @name, causes error
|
||||
Xirf::Open(QN_SYM_REF, S2, Depth(1)),
|
||||
]
|
||||
.into_iter();
|
||||
|
||||
assert_eq!(
|
||||
Err(ParseError::StateError(XmloError::UnassociatedSymDep(S1))),
|
||||
SymDepsState::parse(toks)
|
||||
.collect::<Result<Vec<Parsed<<SymDepsState as ParseState>::Object>>, _>>(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sym_ref_missing_name() {
|
||||
let name = "depsym".into();
|
||||
|
||||
let toks = [
|
||||
Xirf::Open(QN_SYM_DEP, S1, Depth(0)),
|
||||
Xirf::Attr(Attr(QN_NAME, name, (S2, S3))),
|
||||
Xirf::Open(QN_SYM_REF, S2, Depth(1)),
|
||||
// missing @name, causes error
|
||||
Xirf::Close(None, S3, Depth(1)),
|
||||
]
|
||||
.into_iter();
|
||||
|
||||
assert_eq!(
|
||||
Err(ParseError::StateError(XmloError::MalformedSymRef(name, S2))),
|
||||
SymDepsState::parse(toks)
|
||||
.collect::<Result<Vec<Parsed<<SymDepsState as ParseState>::Object>>, _>>(),
|
||||
);
|
||||
}
|
||||
|
||||
/// Very lightly test the default parser composition.
|
||||
///
|
||||
/// This test should do just enough to verify that parser state stitching has
|
||||
/// occurred.
|
||||
#[test]
|
||||
fn xmlo_composite_parsers_header() {
|
||||
let sym_name = "sym".into();
|
||||
let symdep_name = "symdep".into();
|
||||
|
||||
let toks_header = [
|
||||
Xirf::Open(QN_PACKAGE, S1, Depth(0)),
|
||||
// <preproc:symtable>
|
||||
Xirf::Open(QN_SYMTABLE, S2, Depth(1)),
|
||||
// <preproc:sym
|
||||
Xirf::Open(QN_SYM, S3, Depth(2)),
|
||||
Xirf::Attr(Attr(QN_NAME, sym_name, (S2, S3))),
|
||||
Xirf::Close(None, S4, Depth(2)),
|
||||
// />
|
||||
Xirf::Close(Some(QN_SYMTABLE), S4, Depth(1)),
|
||||
// </preproc:symtable>
|
||||
// <preproc:sym-deps>
|
||||
Xirf::Open(QN_SYM_DEPS, S2, Depth(1)),
|
||||
// <preproc:sym-dep
|
||||
Xirf::Open(QN_SYM_DEP, S3, Depth(3)),
|
||||
Xirf::Attr(Attr(QN_NAME, symdep_name, (S2, S3))),
|
||||
Xirf::Close(Some(QN_SYM_DEP), S4, Depth(3)),
|
||||
// </preproc:sym-dep>
|
||||
Xirf::Close(Some(QN_SYM_DEPS), S3, Depth(1)),
|
||||
// </preproc:sym-deps>
|
||||
// No closing root node:
|
||||
// ensure that we can just end at the header without parsing further.
|
||||
]
|
||||
.into_iter();
|
||||
|
||||
let sut = Sut::parse(toks_header);
|
||||
|
||||
assert_eq!(
|
||||
Ok(vec![
|
||||
Parsed::Object(XmloEvent::SymDecl(
|
||||
sym_name,
|
||||
Default::default(),
|
||||
S3
|
||||
)),
|
||||
Parsed::Object(XmloEvent::SymDepStart(symdep_name, S3)),
|
||||
]),
|
||||
sut.filter(|parsed| match parsed {
|
||||
Ok(Parsed::Incomplete) => false,
|
||||
_ => true,
|
||||
})
|
||||
.collect(),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -81,6 +81,24 @@ pub trait TokenStream<T: Token> = Iterator<Item = T>;
|
|||
/// consider using [`TokenStream`].
|
||||
pub trait TokenResultStream<T: Token, E: Error> = Iterator<Item = Result<T, E>>;
|
||||
|
||||
/// A [`ParserState`] capable of being automatically stitched together with
|
||||
/// a parent [`ParserState`] `SP` to create a composite parser.
|
||||
///
|
||||
/// Conceptually,
|
||||
/// this can be visualized as combining the state machines of multiple
|
||||
/// parsers into one larger state machine.
|
||||
///
|
||||
/// The term _state stitching_ refers to a particular pattern able to be
|
||||
/// performed automatically by this parsing framework;
|
||||
/// it is not necessary for parser composition,
|
||||
/// provided that you perform the necessary wiring yourself in absence
|
||||
/// of state stitching.
|
||||
pub trait StitchableParseState<SP: ParseState> = ParseState
|
||||
where
|
||||
SP: ParseState<Token = <Self as ParseState>::Token>,
|
||||
<Self as ParseState>::Object: Into<<SP as ParseState>::Object>,
|
||||
<Self as ParseState>::Error: Into<<SP as ParseState>::Error>;
|
||||
|
||||
/// A deterministic parsing automaton.
|
||||
///
|
||||
/// These states are utilized by a [`Parser`].
|
||||
|
@ -177,13 +195,11 @@ pub trait ParseState: Default + PartialEq + Eq + Debug {
|
|||
fn delegate<C, SP>(
|
||||
self,
|
||||
context: C,
|
||||
tok: Self::Token,
|
||||
tok: <Self as ParseState>::Token,
|
||||
into: impl FnOnce(C, Self) -> SP,
|
||||
) -> TransitionResult<SP>
|
||||
where
|
||||
SP: ParseState<Token = Self::Token>,
|
||||
Self::Object: Into<<SP as ParseState>::Object>,
|
||||
Self::Error: Into<<SP as ParseState>::Error>,
|
||||
Self: StitchableParseState<SP>,
|
||||
{
|
||||
use ParseStatus::{Dead, Incomplete, Object as Obj};
|
||||
|
||||
|
@ -210,14 +226,16 @@ pub trait ParseState: Default + PartialEq + Eq + Debug {
|
|||
fn delegate_lookahead<C, SP>(
|
||||
self,
|
||||
context: C,
|
||||
tok: Self::Token,
|
||||
tok: <Self as ParseState>::Token,
|
||||
into: impl FnOnce(C, Self) -> SP,
|
||||
lookahead: impl FnOnce(C, Self, Self::Token) -> TransitionResult<SP>,
|
||||
lookahead: impl FnOnce(
|
||||
C,
|
||||
Self,
|
||||
<Self as ParseState>::Token,
|
||||
) -> TransitionResult<SP>,
|
||||
) -> TransitionResult<SP>
|
||||
where
|
||||
SP: ParseState<Token = Self::Token>,
|
||||
Self::Object: Into<<SP as ParseState>::Object>,
|
||||
Self::Error: Into<<SP as ParseState>::Error>,
|
||||
Self: StitchableParseState<SP>,
|
||||
{
|
||||
use ParseStatus::{Dead, Incomplete, Object as Obj};
|
||||
|
||||
|
|
|
@ -479,6 +479,9 @@ pub mod st {
|
|||
L_SRC: cid "src",
|
||||
L_STATIC: cid "static",
|
||||
L_SYM: cid "sym",
|
||||
L_SYM_DEPS: cid "sym-deps",
|
||||
L_SYM_DEP: cid "sym-dep",
|
||||
L_SYM_REF: cid "sym-ref",
|
||||
L_SYMTABLE: cid "symtable",
|
||||
L_TITLE: cid "title",
|
||||
L_TPL: cid "tpl",
|
||||
|
|
Loading…
Reference in New Issue