tame/tamer/src/obj/xmlo/reader.rs

879 lines
29 KiB
Rust
Raw Blame History

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

// XIR-based xmlo object file reader
//
// Copyright (C) 2014-2023 Ryan Specialty, LLC.
//
// This file is part of TAME.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use std::fmt::Display;
use super::{SymAttrs, XmloError};
use crate::{
fmt::{DisplayWrapper, TtQuote},
num::{Dim, Dtype},
obj::xmlo::SymType,
parse::{
self, util::SPair, NoContext, ParseState, Token, Transition,
TransitionResult, Transitionable,
},
span::Span,
sym::{st::raw, GlobalSymbolIntern, GlobalSymbolResolve, SymbolId},
xir::{
attr::{Attr, AttrSpan},
flat::{Text, XirfToken as Xirf},
st::qname::*,
EleSpan, QName,
},
};
/// `xmlo` reader events.
///
/// All data are parsed rather than being returned as [`u8`] slices,
/// which avoids having to deal with awkward borrows or data copying since
/// these data will likely be persisted in memory anyway.
///
/// To avoid extra data copying,
/// we should instead prefer not to put data into object files that won't
/// 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.
PkgRootPath(SPair),
/// Indicates that the package is a program.
PkgProgramFlag(Span),
/// Name of package eligibility classification.
PkgEligClassYields(SPair),
/// Symbol declaration.
///
/// This represents an entry in the symbol table,
/// which includes a symbol along with its variable metadata as
/// [`SymAttrs`].
SymDecl(SPair, SymAttrs),
/// Begin adjacency list for a given symbol and interpret subsequent
/// symbols as edges (dependencies).
SymDepStart(SPair),
/// A symbol reference whose interpretation is dependent on the current
/// state.
///
/// The span currently references the object file itself,
/// but in the future this will resolve to a span stored within the
/// 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,
/// and is returned here as an owned value.
/// Given that fragments can be quite large,
/// a caller not interested in these data should choose to skip
/// fragments entirely rather than simply ignoring fragment events.
Fragment(SPair, SymbolId),
/// End-of-header.
///
/// The header of an `xmlo` file is defined as the symbol table;
/// dependency list; and fragments.
/// This event is emitted at the closing `preproc:fragment` node.
Eoh(Span),
}
impl parse::Object for XmloToken {}
impl Token for XmloToken {
fn ir_name() -> &'static str {
"xmlo"
}
fn span(&self) -> Span {
use XmloToken::*;
match self {
// Note that even the spans for the package metadata are
// important since these initial tokens seed
// `Parser::last_span`,
// which is used for early error messages.
PkgStart(span)
| PkgName(SPair(_, span))
| PkgRootPath(SPair(_, span))
| PkgProgramFlag(span)
| PkgEligClassYields(SPair(_, span))
| SymDecl(SPair(_, span), _)
| SymDepStart(SPair(_, span))
| Symbol(SPair(_, span))
| SymDepEnd(span)
| Fragment(SPair(_, span), _)
| Eoh(span) => *span,
}
}
}
impl Display for XmloToken {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
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))
}
PkgProgramFlag(_) => write!(f, "package program flag"),
PkgEligClassYields(sym) => {
write!(
f,
"package eligibility classification {}",
TtQuote::wrap(sym)
)
}
SymDecl(sym, ..) => {
write!(f, "symbol {} declaration", TtQuote::wrap(sym))
}
SymDepStart(sym) => {
write!(
f,
"beginning of symbol {} dependency list",
TtQuote::wrap(sym)
)
}
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))
}
Eoh(..) => write!(f, "end of header"),
}
}
}
#[derive(Debug, Default, PartialEq, Eq)]
pub enum XmloReader {
/// Parser has not yet processed any input.
#[default]
Ready,
/// Processing `package` attributes.
Package(Span),
/// Expecting a symbol declaration or closing `preproc:symtable`.
Symtable(Span, SymtableState),
/// Symbol dependencies are expected next.
SymDepsExpected,
/// Expecting symbol dependency list or closing `preproc:sym-deps`.
SymDeps(Span, SymDepsState),
/// Compiled text fragments are expected next.
FragmentsExpected,
/// Expecting text fragment or closing `preproc:fragments`.
Fragments(Span, FragmentsState),
/// End of header parsing.
Eoh,
/// `xmlo` file has been fully read.
Done,
}
impl ParseState for XmloReader {
type Token = Xirf<Text>;
type Object = XmloToken;
type Error = XmloError;
fn parse_token(
self,
tok: Self::Token,
ctx: NoContext,
) -> TransitionResult<Self> {
use XmloReader::*;
match (self, tok) {
(Ready, Xirf::Open(QN_LV_PACKAGE | QN_PACKAGE, ospan, ..)) => {
let span = ospan.tag_span();
Transition(Package(span)).ok(XmloToken::PkgStart(span))
}
(Ready, tok) => {
Transition(Ready).err(XmloError::UnexpectedRoot(tok))
}
(Package(span), Xirf::Attr(Attr(name, value, aspan))) => {
// TODO: These spans do not encompass the entire token for errors,
// which can result in confusing output depending on the context;
// we ought to retain _both_ token- and value-spans.
Transition(Package(span)).ok(match name {
QN_NAME => XmloToken::PkgName(SPair(
canonical_slash(value),
aspan.value_span(),
)),
QN_UUROOTPATH => {
XmloToken::PkgRootPath(SPair(value, aspan.value_span()))
}
QN_PROGRAM => XmloToken::PkgProgramFlag(aspan.key_span()),
QN_P_ELIG_CLASS_YIELDS => XmloToken::PkgEligClassYields(
SPair(value, aspan.value_span()),
),
// Ignore unknown attributes for now to maintain BC,
// since no strict xmlo schema has been defined.
_ => return Transition(Package(span)).incomplete(),
})
}
// Empty package (should we allow this?);
// XIRF guarantees a matching closing tag.
(Package(_), Xirf::Close(..)) => Transition(Done).incomplete(),
(Package(_), Xirf::Open(QN_P_SYMTABLE, span, ..)) => {
Transition(Symtable(span.tag_span(), SymtableState::default()))
.incomplete()
}
(Symtable(_, ss), Xirf::Close(Some(QN_P_SYMTABLE), ..))
if ss.is_accepting(ctx) =>
{
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(
tok,
ctx,
|ss| Transition(Symtable(span, ss)),
|| unreachable!(), // TODO: currently caught by preceding match
),
(SymDepsExpected, Xirf::Open(QN_P_SYM_DEPS, span, _)) => {
Transition(SymDeps(span.tag_span(), SymDepsState::default()))
.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,
ctx,
|sd| Transition(SymDeps(span, sd)),
|| unreachable!(), // TODO: currently caught by preceding match
),
(FragmentsExpected, Xirf::Open(QN_P_FRAGMENTS, span, _)) => {
Transition(Fragments(
span.tag_span(),
FragmentsState::default(),
))
.incomplete()
}
(
Fragments(_, sf),
Xirf::Close(None | Some(QN_P_FRAGMENTS), span, _),
) if sf.is_accepting(ctx) => {
Transition(Eoh).ok(XmloToken::Eoh(span.tag_span()))
}
(Fragments(span, sf), tok) => sf.delegate(
tok,
ctx,
|sf| Transition(Fragments(span, sf)),
|| unreachable!(), // TODO: currently caught by preceding match
),
(Eoh, Xirf::Close(Some(QN_PACKAGE), ..)) => {
Transition(Done).incomplete()
}
// TODO: For whitespace, which can be stripped by XIRF.
(st, Xirf::Text(..)) => Transition(st).incomplete(),
(st, unknown) => {
Transition(st).err(XmloError::UnexpectedToken(unknown))
}
}
}
fn is_accepting(&self, _: &Self::Context) -> bool {
matches!(self, Self::FragmentsExpected | Self::Eoh | Self::Done)
}
}
/// 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 Display for XmloReader {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
use XmloReader::*;
match self {
Ready => write!(f, "awaiting xmlo input"),
Package(_) => write!(f, "processing package attributes"),
Symtable(_, ss) => Display::fmt(ss, f),
SymDepsExpected => write!(f, "expecting symbol dependency list"),
SymDeps(_, sd) => Display::fmt(sd, f),
FragmentsExpected => write!(f, "expecting start of fragments"),
Fragments(_, sf) => Display::fmt(sf, f),
Eoh => write!(f, "finished parsing header"),
Done => write!(f, "finished parsing xmlo file"),
}
}
}
/// Symbol table parser operating within a delimited context.
///
/// This parser expects a parent [`ParseState`] to indicate when symtable
/// parsing ought to start and end—
/// this parser does not recognize any opening or closing tags.
#[derive(Debug, Default, PartialEq, Eq)]
pub enum SymtableState {
/// Symbol table declaration found;
/// symbols declarations expected.
#[default]
Ready,
/// Processing a symbol.
Sym(Span, Option<SymbolId>, SymAttrs),
/// Awaiting a symbol map name.
SymMapFrom(Span, SymbolId, SymAttrs, Span),
/// Used by functions to declare their parameters.
SymRef(Span, SymbolId, SymAttrs, Span),
}
impl ParseState for SymtableState {
type Token = Xirf<Text>;
type Object = XmloToken;
type Error = XmloError;
fn parse_token(
self,
tok: Self::Token,
_: NoContext,
) -> TransitionResult<Self> {
use SymtableState::*;
match (self, tok) {
(Ready, Xirf::Attr(..)) => Transition(Ready).incomplete(),
(Ready, Xirf::Open(QN_P_SYM, span, _)) => {
Transition(Sym(span.tag_span(), None, SymAttrs::default()))
.incomplete()
}
(Sym(span, None, attrs), Xirf::Close(..)) => {
Transition(Sym(span, None, attrs))
.err(XmloError::UnassociatedSym(span))
}
// Completed symbol.
(Sym(span, Some(name), attrs), Xirf::Close(..)) => {
Transition(Ready)
.ok(XmloToken::SymDecl(SPair(name, span), attrs))
}
// Symbol @name found.
(Sym(_, None, attrs), Xirf::Attr(Attr(QN_NAME, name, aspan))) => {
Transition(Sym(aspan.value_span(), Some(name), attrs))
.incomplete()
}
(
Sym(span_sym, name, mut attrs),
Xirf::Attr(Attr(key, value, AttrSpan(_, span_attrval))),
) => Self::parse_sym_attr(&mut attrs, key, value, span_attrval)
.transition(Sym(span_sym, name, attrs)),
// `preproc:from` supported only for `type="map"`.
// TODO: The compiler really ought to just make this an
// attribute now so we can simplify parsing here.
(
Sym(span_sym, Some(name), attrs),
Xirf::Open(QN_P_FROM, span_from, _),
) if attrs.ty == Some(SymType::Map)
|| attrs.ty == Some(SymType::RetMap) =>
{
Transition(SymMapFrom(
span_sym,
name,
attrs,
span_from.tag_span(),
))
.incomplete()
}
(
SymMapFrom(span_sym, name, mut attrs, span_from),
Xirf::Attr(Attr(QN_NAME, from_name, _)),
) => match attrs.from.replace(from_name) {
Some(_) => Err(XmloError::MapFromMultiple(name, span_from)),
None => Ok(()),
}
.transition(SymMapFrom(span_sym, name, attrs, span_from)),
(SymMapFrom(span_sym, name, attrs, span_from), Xirf::Close(..)) => {
if attrs.from.is_none() {
return Transition(SymMapFrom(
span_sym, name, attrs, span_from,
))
.err(XmloError::MapFromNameMissing(name, span_from));
}
Transition(Sym(span_sym, Some(name), attrs)).incomplete()
}
// TODO: These don't yield any events;
// can they be removed from the compiler?
// The old XmloReader ignored these.
(
Sym(span_sym, Some(name), attrs),
Xirf::Open(QN_P_SYM_REF, span_ref, _),
) => Transition(SymRef(span_sym, name, attrs, span_ref.tag_span()))
.incomplete(),
(SymRef(span_sym, name, attrs, _), Xirf::Close(..)) => {
Transition(Sym(span_sym, Some(name), attrs)).incomplete()
}
(st @ SymRef(..), _) => Transition(st).incomplete(),
// TODO: For whitespace, which can be stripped by XIRF.
(st, Xirf::Text(..)) => Transition(st).incomplete(),
(st, unknown) => {
Transition(st).err(XmloError::UnexpectedToken(unknown))
}
}
}
fn is_accepting(&self, _: &Self::Context) -> bool {
*self == Self::Ready
}
}
impl SymtableState {
/// Parse attributes of a `preproc:symtable/preproc:sym` element,
/// representing attributes of a symbol in the symbol table.
///
/// Note that `@name` is expected to have already been processed by the
/// caller and is not expected to occur a second time.
fn parse_sym_attr(
attrs: &mut SymAttrs,
key: QName,
value: SymbolId,
span: Span,
) -> Result<(), XmloError> {
use raw::L_TRUE;
match key {
QN_DIM => {
attrs.dim.replace(
Self::parse_dim(value)
.ok_or(XmloError::InvalidDim(value, span))?,
);
}
QN_DTYPE => {
attrs.dtype.replace(
Self::parse_dtype(value)
.ok_or(XmloError::InvalidDtype(value, span))?,
);
}
QN_TYPE => {
attrs.ty.replace(
Self::parse_symtype(value)
.ok_or(XmloError::InvalidType(value, span))?,
);
}
QN_SRC => {
attrs.src.replace(value);
}
QN_EXTERN => {
attrs.extern_ = value == L_TRUE;
}
QN_YIELDS => {
attrs.yields.replace(value);
}
QN_PARENT => {
attrs.parent.replace(value);
}
QN_DESC => {
attrs.desc.replace(value);
}
QN_VIRTUAL => {
attrs.virtual_ = value == L_TRUE;
}
QN_ISOVERRIDE => {
attrs.override_ = value == L_TRUE;
}
QN_P_GENERATED => {
attrs.generated = value == L_TRUE;
}
// If we actually hit this,
// we may want to add a proper error to provide more context.
// It is not expected to be hit,
// since it would mean that there is a duplicate attribute and
// this xmlo file is hopefully produced by the compiler.
QN_NAME => panic!(
"preproc:sym/@name already processed \
(duplicate attribute); \
the xmlo file is corrupt"
),
// To maintain BC,
// ignore unknown attrs for now until we are confident that we
// have a comprehensive schema.
// TODO: Error here.
_ => (),
}
Ok(())
}
/// Parse a numeric `preproc:sym/@dim` attribute.
fn parse_dim(value: SymbolId) -> Option<Dim> {
use raw::*;
match value {
N0 => Some(Dim::Scalar),
N1 => Some(Dim::Vector),
N2 => Some(Dim::Matrix),
_ => None,
}
}
/// Parse a `preproc:sym/@dtype` attribute.
fn parse_dtype(value: SymbolId) -> Option<Dtype> {
use raw::*;
match value {
L_BOOLEAN => Some(Dtype::Boolean),
L_INTEGER => Some(Dtype::Integer),
L_FLOAT => Some(Dtype::Float),
L_EMPTY => Some(Dtype::Empty),
_ => None,
}
}
/// Parse a `preproc:sym/@type` attribute.
fn parse_symtype(value: SymbolId) -> Option<SymType> {
use raw::*;
match value {
L_CGEN => Some(SymType::Cgen),
L_CLASS => Some(SymType::Class),
L_CONST => Some(SymType::Const),
L_FUNC => Some(SymType::Func),
L_GEN => Some(SymType::Gen),
L_LPARAM => Some(SymType::Lparam),
L_PARAM => Some(SymType::Param),
L_RATE => Some(SymType::Rate),
L_TPL => Some(SymType::Tpl),
L_TYPE => Some(SymType::Type),
L_RETMAP_HEAD => Some(SymType::RetMapHead),
L_RETMAP => Some(SymType::RetMap),
L_RETMAP_TAIL => Some(SymType::RetMapTail),
L_MAP_HEAD => Some(SymType::MapHead),
L_MAP => Some(SymType::Map),
L_MAP_TAIL => Some(SymType::MapTail),
L_META => Some(SymType::Meta),
L_WORKSHEET => Some(SymType::Worksheet),
_ => None,
}
}
}
impl Display for SymtableState {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
use SymtableState::*;
match self {
Ready => write!(f, "expecting symbol declaration"),
Sym(_, Some(sym), _) => write!(f, "parsing symbol `{sym}`"),
Sym(_, None, _) => write!(f, "parsing a symbol"),
SymMapFrom(_, sym, _, _) => {
write!(f, "expecting map name for symbol `{sym}`")
}
SymRef(_, sym, _, _) => {
write!(f, "parsing refs for symbol `{sym}`")
}
}
}
}
/// 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<Text>;
type Object = XmloToken;
type Error = XmloError;
fn parse_token(
self,
tok: Self::Token,
_: NoContext,
) -> TransitionResult<Self> {
use SymDepsState::*;
match (self, tok) {
(Ready, Xirf::Attr(..)) => Transition(Ready).incomplete(),
(Ready, Xirf::Open(QN_P_SYM_DEP, span, _)) => {
Transition(SymUnnamed(span.tag_span())).incomplete()
}
(SymUnnamed(span), Xirf::Attr(Attr(QN_NAME, name, _))) => {
Transition(Sym(span, name))
.ok(XmloToken::SymDepStart(SPair(name, span)))
}
(SymUnnamed(span), _) => Transition(SymUnnamed(span))
.err(XmloError::UnassociatedSymDep(span)),
(Sym(span, name), Xirf::Open(QN_P_SYM_REF, span_ref, _)) => {
Transition(SymRefUnnamed(span, name, span_ref.tag_span()))
.incomplete()
}
(
SymRefUnnamed(span, name, span_ref),
Xirf::Attr(Attr(QN_NAME, ref_name, AttrSpan(_, span_ref_name))),
) => Transition(SymRefDone(span, name, span_ref))
.ok(XmloToken::Symbol(SPair(ref_name, span_ref_name))),
// TODO: For xmlns attributes, which will go away in XIRF.
(SymRefUnnamed(span, name, span_ref), Xirf::Attr(..)) => {
Transition(SymRefUnnamed(span, name, span_ref)).incomplete()
}
(SymRefUnnamed(span, name, span_ref), _) => {
Transition(SymRefUnnamed(span, name, span_ref))
.err(XmloError::MalformedSymRef(name, span_ref))
}
// TODO: For xmlns attributes, which will go away in XIRF.
(SymRefDone(span, name, ref_span), Xirf::Attr(..)) => {
Transition(SymRefDone(span, name, ref_span)).incomplete()
}
(SymRefDone(span, name, _), Xirf::Close(..)) => {
Transition(Sym(span, name)).incomplete()
}
(Sym(..), Xirf::Close(..)) => Transition(Ready).incomplete(),
// TODO: For whitespace, which can be stripped by XIRF.
(st, Xirf::Text(..)) => Transition(st).incomplete(),
(st, unknown) => {
Transition(st).err(XmloError::UnexpectedToken(unknown))
}
}
}
fn is_accepting(&self, _: &Self::Context) -> bool {
*self == Self::Ready
}
}
impl Display for SymDepsState {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
use SymDepsState::*;
match self {
Ready => write!(f, "expecting symbol table entry"),
SymUnnamed(_) => write!(f, "expecting name for symbol table entry"),
Sym(_, sym) => write!(
f,
"expecting dependencies for symbol table entry `{sym}`"
),
SymRefUnnamed(_, sym, _) => write!(
f,
"expecting dependency name for symbol table entry `{sym}`"
),
SymRefDone(_, sym, _) => write!(
f,
"expending end of dependency for symbol table entry `{sym}`"
),
}
}
}
/// Text fragment (compiled code) parser for `preproc:fragments` 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:fragments` tags.
#[derive(Debug, Default, PartialEq, Eq)]
pub enum FragmentsState {
#[default]
Ready,
FragmentUnnamed(Span),
Fragment(SPair),
FragmentDone(SPair),
}
impl ParseState for FragmentsState {
type Token = Xirf<Text>;
type Object = XmloToken;
type Error = XmloError;
fn parse_token(
self,
tok: Self::Token,
_: NoContext,
) -> TransitionResult<Self> {
use FragmentsState::*;
match (self, tok) {
(Ready, Xirf::Attr(..)) => Transition(Ready).incomplete(),
(Ready, Xirf::Open(QN_P_FRAGMENT, span, _)) => {
Transition(FragmentUnnamed(span.tag_span())).incomplete()
}
// TODO: For whitespace, which can be stripped by XIRF.
(Ready, Xirf::Text(..)) => Transition(Ready).incomplete(),
(FragmentUnnamed(span), Xirf::Attr(Attr(QN_ID, id, _))) => {
match id {
// See "compiler bug" comment below.
raw::WS_EMPTY => {
Transition(FragmentUnnamed(span)).incomplete()
}
id => Transition(Fragment(SPair(id, span))).incomplete(),
}
}
(FragmentUnnamed(span), Xirf::Attr(Attr(key, ..)))
if key != QN_ID =>
{
Transition(FragmentUnnamed(span)).incomplete()
}
// Compiler bug: `<preproc:fragment id="" />` in `rater/core/base`.
(FragmentUnnamed(_span), Xirf::Close(None, ..)) => {
Transition(Ready).incomplete()
}
(FragmentUnnamed(span), _) => Transition(FragmentUnnamed(span))
.err(XmloError::UnassociatedFragment(span)),
(Fragment(name), Xirf::Text(Text(text, _), _)) => {
Transition(FragmentDone(name))
.ok(XmloToken::Fragment(name, text))
}
// TODO: Also a compiler bug, for some generated classes.
// This needs fixing in the compiler.
(Fragment(_), Xirf::Close(..)) => {
//eprintln!("warning: empty fragment text for {id} at {span}");
Transition(Ready).incomplete()
}
(FragmentDone(..), Xirf::Close(..)) => {
Transition(Ready).incomplete()
}
(st, unknown) => {
Transition(st).err(XmloError::UnexpectedToken(unknown))
}
}
}
fn is_accepting(&self, _: &Self::Context) -> bool {
*self == Self::Ready
}
}
impl Display for FragmentsState {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
use FragmentsState::*;
match self {
Ready => write!(f, "expecting fragment"),
FragmentUnnamed(_) => {
write!(f, "expecting fragment association id")
}
Fragment(name) => {
write!(
f,
"expecting fragment text for symbol {}",
TtQuote::wrap(name)
)
}
FragmentDone(name) => {
write!(
f,
"expecting end of fragment for symbol {}",
TtQuote::wrap(name)
)
}
}
}
}
#[cfg(test)]
mod test;