Mike Gerwitz

Activist for User Freedom

aboutsummaryrefslogtreecommitdiffstats
path: root/tamer
diff options
context:
space:
mode:
authorMike Gerwitz <mike.gerwitz@ryansg.com>2022-03-30 15:03:50 -0400
committerMike Gerwitz <mike.gerwitz@ryansg.com>2022-03-30 15:05:55 -0400
commitfb3da09fa4838770924f158f0ce5a6994cae7307 (patch)
tree595a7dab859fbe1e8510729d2351f2ced08bce40 /tamer
parent3f8e397e57acd72483a82216698fee2fc1601779 (diff)
downloadtame-fb3da09fa4838770924f158f0ce5a6994cae7307.tar.gz
tame-fb3da09fa4838770924f158f0ce5a6994cae7307.tar.bz2
tame-fb3da09fa4838770924f158f0ce5a6994cae7307.zip
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-10863
Diffstat (limited to 'tamer')
-rw-r--r--tamer/src/obj/xmlo/asg_builder.rs12
-rw-r--r--tamer/src/obj/xmlo/error.rs17
-rw-r--r--tamer/src/obj/xmlo/reader.rs122
-rw-r--r--tamer/src/obj/xmlo/reader/quickxml.rs17
-rw-r--r--tamer/src/obj/xmlo/reader/quickxml/test.rs48
-rw-r--r--tamer/src/obj/xmlo/reader/test.rs197
-rw-r--r--tamer/src/parse.rs36
-rw-r--r--tamer/src/sym/prefill.rs3
8 files changed, 308 insertions, 144 deletions
diff --git a/tamer/src/obj/xmlo/asg_builder.rs b/tamer/src/obj/xmlo/asg_builder.rs
index 3b652a1..092274d 100644
--- a/tamer/src/obj/xmlo/asg_builder.rs
+++ b/tamer/src/obj/xmlo/asg_builder.rs
@@ -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
diff --git a/tamer/src/obj/xmlo/error.rs b/tamer/src/obj/xmlo/error.rs
index 1e916fa..da7ca31 100644
--- a/tamer/src/obj/xmlo/error.rs
+++ b/tamer/src/obj/xmlo/error.rs
@@ -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,
diff --git a/tamer/src/obj/xmlo/reader.rs b/tamer/src/obj/xmlo/reader.rs
index 3e9b584..df60f21 100644
--- a/tamer/src/obj/xmlo/reader.rs
+++ b/tamer/src/obj/xmlo/reader.rs
@@ -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;
diff --git a/tamer/src/obj/xmlo/reader/quickxml.rs b/tamer/src/obj/xmlo/reader/quickxml.rs
index b9bf905..3e37898 100644
--- a/tamer/src/obj/xmlo/reader/quickxml.rs
+++ b/tamer/src/obj/xmlo/reader/quickxml.rs
@@ -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(),
- )))
+ )
}
}
diff --git a/tamer/src/obj/xmlo/reader/quickxml/test.rs b/tamer/src/obj/xmlo/reader/quickxml/test.rs
index 145bb44..9cd825c 100644
--- a/tamer/src/obj/xmlo/reader/quickxml/test.rs
+++ b/tamer/src/obj/xmlo/reader/quickxml/test.rs
@@ -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")))
diff --git a/tamer/src/obj/xmlo/reader/test.rs b/tamer/src/obj/xmlo/reader/test.rs
index e2269ec..68b2a0a 100644
--- a/tamer/src/obj/xmlo/reader/test.rs
+++ b/tamer/src/obj/xmlo/reader/test.rs
@@ -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;
@@ -136,70 +137,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 = [
Xirf::Open(QN_SYM, S1, Depth(0)),
@@ -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(),
+ );
+}
diff --git a/tamer/src/parse.rs b/tamer/src/parse.rs
index 6d2fb8f..a683a5a 100644
--- a/tamer/src/parse.rs
+++ b/tamer/src/parse.rs
@@ -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};
diff --git a/tamer/src/sym/prefill.rs b/tamer/src/sym/prefill.rs
index 852556d..d05171b 100644
--- a/tamer/src/sym/prefill.rs
+++ b/tamer/src/sym/prefill.rs
@@ -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",