tamer: asg: Basic `Doc::Text` support
This supports arbitrary documentation as sibling text (mixed content, in XML terms). The motivation behind this change is to permit existing system tests to succeed when `Todo | TodoAttr` are both rejected, rather than having to ignore this. TAME has always had a philosophy of literate documentation, however it was never fully realized. This just maintains the status quo; the text is unstructured, and maybe will be parsed in the future. Unfortunately, this does _not_ include the output in the `xmli` file or the system tests. The reason has nothing to do with TAMER---`xmllint` does not format the output when there is mixed content, it seems, and I need to move on for now; I'll consider my options in the future. But, it's available on the graph and ready to go. DEV-13708main
parent
647e0ccbbd
commit
e88800af42
|
@ -133,7 +133,8 @@ impl ParseState for AirAggregate {
|
|||
ctx: &mut Self::Context,
|
||||
) -> crate::parse::TransitionResult<Self> {
|
||||
use ir::{
|
||||
AirBind::*, AirIdent::*, AirPkg::*, AirSubsets::*, AirTodo::*,
|
||||
AirBind::*, AirDoc::*, AirIdent::*, AirPkg::*, AirSubsets::*,
|
||||
AirTodo::*,
|
||||
};
|
||||
use AirAggregate::*;
|
||||
|
||||
|
@ -168,18 +169,23 @@ impl ParseState for AirAggregate {
|
|||
Transition(st).err(AsgError::InvalidBindContext(id))
|
||||
}
|
||||
|
||||
(Toplevel(oi_pkg), tok @ AirDoc(..)) => {
|
||||
(Toplevel(oi_pkg), tok @ AirDoc(DocIndepClause(..))) => {
|
||||
diagnostic_todo!(
|
||||
vec![
|
||||
oi_pkg.note("for this package"),
|
||||
tok.internal_error(
|
||||
"this package-level documentation is not yet supported"
|
||||
"this package description is not yet supported"
|
||||
)
|
||||
],
|
||||
"package-level documentation is not yet supported by TAMER",
|
||||
"package-level short description is not yet supported by TAMER",
|
||||
)
|
||||
}
|
||||
|
||||
(Toplevel(oi_pkg), AirDoc(DocText(text))) => {
|
||||
oi_pkg.append_doc_text(ctx.asg_mut(), text);
|
||||
Transition(Toplevel(oi_pkg)).incomplete()
|
||||
}
|
||||
|
||||
// Package import
|
||||
(Toplevel(oi_pkg), AirBind(RefIdent(pathspec))) => {
|
||||
oi_pkg.import(ctx.asg_mut(), pathspec);
|
||||
|
|
|
@ -148,6 +148,11 @@ impl ParseState for AirExprAggregate {
|
|||
Transition(BuildingExpr(es, oi)).incomplete()
|
||||
}
|
||||
|
||||
(BuildingExpr(es, oi), AirDoc(DocText(text))) => Transition(
|
||||
BuildingExpr(es, oi),
|
||||
)
|
||||
.err(AsgError::InvalidDocContextExpr(oi.span(), text.span())),
|
||||
|
||||
(st @ Ready(..), AirExpr(ExprEnd(span))) => {
|
||||
Transition(st).err(AsgError::UnbalancedExpr(span))
|
||||
}
|
||||
|
|
|
@ -798,6 +798,21 @@ sum_ir! {
|
|||
in a sentence",
|
||||
),
|
||||
},
|
||||
|
||||
/// Arbitrary documentation text.
|
||||
///
|
||||
/// TAMER hopes to eventually provide structured documentation,
|
||||
/// but until then,
|
||||
/// this is just some arbitrary block of text.
|
||||
/// Historically,
|
||||
/// the convention was LaTeX,
|
||||
/// but the approach has fallen out of favor;
|
||||
/// TAMER should provide its own documentation format that
|
||||
/// it can reason about.
|
||||
DocText(text: SPair) => {
|
||||
span: text,
|
||||
display: |f| write!(f, "documentation text"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
|
||||
use super::{super::Ident, *};
|
||||
use crate::{
|
||||
asg::{IdentKind, Source},
|
||||
asg::{graph::object::ObjectRel, IdentKind, Source},
|
||||
parse::{ParseError, Parsed, Parser},
|
||||
span::dummy::*,
|
||||
};
|
||||
|
@ -340,6 +340,41 @@ fn pkg_import() {
|
|||
assert_eq!(pathspec, import.pathspec());
|
||||
}
|
||||
|
||||
// Documentation can be mixed in with objects in a literate style.
|
||||
#[test]
|
||||
fn pkg_doc() {
|
||||
let doc_a = SPair("first".into(), S2);
|
||||
let id_import = SPair("import".into(), S3);
|
||||
let doc_b = SPair("first".into(), S4);
|
||||
|
||||
#[rustfmt::skip]
|
||||
let toks = vec![
|
||||
Air::DocText(doc_a),
|
||||
|
||||
// Some object to place in-between the two
|
||||
// documentation blocks.
|
||||
Air::RefIdent(id_import),
|
||||
|
||||
Air::DocText(doc_b),
|
||||
];
|
||||
|
||||
let asg = asg_from_toks(toks);
|
||||
|
||||
let oi_pkg = asg
|
||||
.root(S1)
|
||||
.edges_filtered::<Pkg>(&asg)
|
||||
.next()
|
||||
.expect("cannot find package from root");
|
||||
|
||||
assert_eq!(
|
||||
vec![S4, S3, S2], // (edges reversed by Petgraph)
|
||||
oi_pkg
|
||||
.edges(&asg)
|
||||
.map(|rel| rel.widen().resolve(&asg).span())
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
}
|
||||
|
||||
/// Parse using [`Sut`] when the test does not care about the outer package.
|
||||
pub fn parse_as_pkg_body<I: IntoIterator<Item = Air>>(
|
||||
toks: I,
|
||||
|
|
|
@ -174,7 +174,7 @@ impl ParseState for AirTplAggregate {
|
|||
tok: Self::Token,
|
||||
ctx: &mut Self::Context,
|
||||
) -> TransitionResult<Self::Super> {
|
||||
use super::ir::{AirBind::*, AirTpl::*};
|
||||
use super::ir::{AirBind::*, AirDoc::*, AirTpl::*};
|
||||
use AirBindableTpl::*;
|
||||
use AirTplAggregate::*;
|
||||
|
||||
|
@ -206,19 +206,23 @@ impl ParseState for AirTplAggregate {
|
|||
Transition(Toplevel(tpl)).incomplete()
|
||||
}
|
||||
|
||||
(Toplevel(tpl), tok @ AirDoc(..)) => {
|
||||
(Toplevel(tpl), tok @ AirDoc(DocIndepClause(_))) => {
|
||||
diagnostic_todo!(
|
||||
vec![
|
||||
tpl.oi().note("in this template"),
|
||||
tok.internal_error(
|
||||
"this template-level documentation is not \
|
||||
yet supported"
|
||||
"this template description is not yet supported"
|
||||
)
|
||||
],
|
||||
"template-level documentation is not yet supported by TAMER",
|
||||
"template description is not yet supported by TAMER",
|
||||
)
|
||||
}
|
||||
|
||||
(Toplevel(tpl), AirDoc(DocText(text))) => {
|
||||
tpl.oi().append_doc_text(ctx.asg_mut(), text);
|
||||
Transition(Toplevel(tpl)).incomplete()
|
||||
}
|
||||
|
||||
(Toplevel(tpl), AirTpl(TplMetaStart(span))) => {
|
||||
let oi_meta = ctx.asg_mut().create(Meta::new_required(span));
|
||||
Transition(TplMeta(tpl, oi_meta)).incomplete()
|
||||
|
|
|
@ -118,6 +118,17 @@ pub enum AsgError {
|
|||
/// Attempted to expand a template into a context that does not support
|
||||
/// expansion.
|
||||
InvalidExpansionContext(Span),
|
||||
|
||||
/// Documentation text is not valid in an expression context.
|
||||
///
|
||||
/// This historical limitation existed because the author was unsure how
|
||||
/// to go about rendering an equation with literate documentation
|
||||
/// interspersed.
|
||||
/// The plan is to lift this limitation in the future.
|
||||
///
|
||||
/// The spans represent the expression and the documentation text
|
||||
/// respectively.
|
||||
InvalidDocContextExpr(Span, Span),
|
||||
}
|
||||
|
||||
impl Display for AsgError {
|
||||
|
@ -157,6 +168,9 @@ impl Display for AsgError {
|
|||
InvalidExpansionContext(_) => {
|
||||
write!(f, "invalid template expansion context",)
|
||||
}
|
||||
InvalidDocContextExpr(_, _) => {
|
||||
write!(f, "document text is not permitted within expressions")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -267,6 +281,15 @@ impl Diagnostic for AsgError {
|
|||
InvalidExpansionContext(span) => {
|
||||
vec![span.error("cannot expand a template here")]
|
||||
}
|
||||
|
||||
InvalidDocContextExpr(expr_span, span) => vec![
|
||||
expr_span.note("in this expression"),
|
||||
span.error("documentation text is not permitted here"),
|
||||
span.help(
|
||||
"this is a historical limitation that will \
|
||||
likely be lifted in the future",
|
||||
),
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,6 +38,17 @@ use std::fmt::Display;
|
|||
pub enum Doc {
|
||||
/// An (ideally) concise independent clause describing an object.
|
||||
IndepClause(SPair),
|
||||
|
||||
/// Arbitrary text serving as documentation for sibling objects in a
|
||||
/// literate style.
|
||||
///
|
||||
/// TAMER does not presently ascribe any semantic meaning to this text,
|
||||
/// and it may even be entirely whitespace.
|
||||
/// There are plans to improve upon this in the future.
|
||||
///
|
||||
/// The intent is for this text to be mixed with sibling objects,
|
||||
/// in a style similar to that of literate programming.
|
||||
Text(SPair),
|
||||
}
|
||||
|
||||
impl Display for Doc {
|
||||
|
@ -53,15 +64,22 @@ impl Doc {
|
|||
Self::IndepClause(clause)
|
||||
}
|
||||
|
||||
/// Arbitrary text serving as documentation for sibling objects in a
|
||||
/// literate style.
|
||||
pub fn new_text(text: SPair) -> Self {
|
||||
Self::Text(text)
|
||||
}
|
||||
|
||||
pub fn indep_clause(&self) -> Option<SPair> {
|
||||
match self {
|
||||
Self::IndepClause(spair) => Some(*spair),
|
||||
Self::Text(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn span(&self) -> Span {
|
||||
match self {
|
||||
Self::IndepClause(spair) => spair.span(),
|
||||
Self::IndepClause(spair) | Self::Text(spair) => spair.span(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
//! Package object on the ASG.
|
||||
|
||||
use super::{prelude::*, Ident, Tpl};
|
||||
use super::{prelude::*, Doc, Ident, Tpl};
|
||||
use crate::{
|
||||
f::Functor,
|
||||
parse::{util::SPair, Token},
|
||||
|
@ -94,6 +94,9 @@ object_rel! {
|
|||
|
||||
// Anonymous templates are used for expansion.
|
||||
tree Tpl,
|
||||
|
||||
// Arbitrary blocks of text serving as documentation.
|
||||
tree Doc,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -112,4 +115,10 @@ impl ObjectIndex<Pkg> {
|
|||
let oi_import = asg.create(Pkg::new_imported(pathspec));
|
||||
self.add_edge_to(asg, oi_import, Some(pathspec.span()))
|
||||
}
|
||||
|
||||
/// Arbitrary text serving as documentation in a literate style.
|
||||
pub fn append_doc_text(&self, asg: &mut Asg, text: SPair) -> Self {
|
||||
let oi_doc = asg.create(Doc::new_text(text));
|
||||
self.add_edge_to(asg, oi_doc, None)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,8 +21,8 @@
|
|||
|
||||
use std::fmt::Display;
|
||||
|
||||
use super::{prelude::*, Expr, Ident};
|
||||
use crate::{f::Functor, span::Span};
|
||||
use super::{prelude::*, Doc, Expr, Ident};
|
||||
use crate::{f::Functor, parse::util::SPair, span::Span};
|
||||
|
||||
/// Template with associated name.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
|
@ -68,6 +68,10 @@ object_rel! {
|
|||
|
||||
// Template application.
|
||||
tree Tpl,
|
||||
|
||||
// Arbitrary documentation to be expanded into the application
|
||||
// site.
|
||||
tree Doc,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -115,4 +119,11 @@ impl ObjectIndex<Tpl> {
|
|||
) -> Self {
|
||||
self.add_edge_from(asg, oi_target_parent, None)
|
||||
}
|
||||
|
||||
/// Arbitrary text serving as documentation in a literate style,
|
||||
/// to be expanded into the application site.
|
||||
pub fn append_doc_text(&self, asg: &mut Asg, text: SPair) -> Self {
|
||||
let oi_doc = asg.create(Doc::new_text(text));
|
||||
self.add_edge_to(asg, oi_doc, None)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -211,7 +211,7 @@ impl<'a> TreeContext<'a> {
|
|||
}
|
||||
|
||||
Object::Doc((doc, oi_doc)) => {
|
||||
self.emit_doc(doc, *oi_doc, paired_rel.source())
|
||||
self.emit_doc(doc, *oi_doc, paired_rel.source(), depth)
|
||||
}
|
||||
|
||||
Object::Root(..) => diagnostic_unreachable!(
|
||||
|
@ -487,10 +487,22 @@ impl<'a> TreeContext<'a> {
|
|||
doc: &Doc,
|
||||
oi_doc: ObjectIndex<Doc>,
|
||||
src: &Object<OiPairObjectInner>,
|
||||
_depth: Depth,
|
||||
) -> Option<Xirf> {
|
||||
match src {
|
||||
match (src, doc) {
|
||||
// TODO: Non-stmt exprs should use `@label` instead.
|
||||
Object::Expr(..) => doc.indep_clause().map(attr_desc),
|
||||
(Object::Expr(..), Doc::IndepClause(desc)) => {
|
||||
Some(attr_desc(*desc))
|
||||
}
|
||||
|
||||
(_, Doc::Text(_text)) => {
|
||||
// TODO: This isn't utilized by the XSLT parser and
|
||||
// `xmllint` for system tests does not format with mixed
|
||||
// data present,
|
||||
// so let's just omit for now.
|
||||
// Some(Xirf::text(Text(text.symbol(), text.span()), depth))
|
||||
None
|
||||
}
|
||||
|
||||
_ => {
|
||||
diagnostic_todo!(
|
||||
|
|
|
@ -49,6 +49,9 @@ pub enum NirToAir {
|
|||
/// A predicate has been partially applied to its subject,
|
||||
/// but we do not yet know its function or comparison value.
|
||||
PredPartial(Span, SPair),
|
||||
|
||||
/// Processing a metavariable.
|
||||
Meta(Span),
|
||||
}
|
||||
|
||||
impl Display for NirToAir {
|
||||
|
@ -65,6 +68,7 @@ impl Display for NirToAir {
|
|||
"waiting to determine type of predicate for identifier {}",
|
||||
TtQuote::wrap(name),
|
||||
),
|
||||
Meta(_) => write!(f, "parsing metavariable definition"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -214,14 +218,23 @@ impl ParseState for NirToAir {
|
|||
}
|
||||
|
||||
(Ready, Open(TplParam, span)) => {
|
||||
Transition(Ready).ok(Air::TplMetaStart(span))
|
||||
Transition(Meta(span)).ok(Air::TplMetaStart(span))
|
||||
}
|
||||
(Ready, Close(TplParam, span)) => {
|
||||
(Meta(mspan), Text(lexeme)) => {
|
||||
Transition(Meta(mspan)).ok(Air::TplLexeme(lexeme))
|
||||
}
|
||||
(Ready | Meta(_), Close(TplParam, span)) => {
|
||||
Transition(Ready).ok(Air::TplMetaEnd(span))
|
||||
}
|
||||
(Ready, Text(lexeme)) => {
|
||||
Transition(Ready).ok(Air::TplLexeme(lexeme))
|
||||
}
|
||||
// Some of these will be permitted in the future.
|
||||
(
|
||||
Meta(mspan),
|
||||
tok @ (Open(..) | Close(..) | Ref(..) | RefSubject(..)
|
||||
| Desc(..)),
|
||||
) => Transition(Meta(mspan))
|
||||
.err(NirToAirError::UnexpectedMetaToken(mspan, tok)),
|
||||
|
||||
(Ready, Text(text)) => Transition(Ready).ok(Air::DocText(text)),
|
||||
|
||||
(
|
||||
Ready,
|
||||
|
@ -231,8 +244,8 @@ impl ParseState for NirToAir {
|
|||
),
|
||||
) => Transition(Ready).ok(Air::ExprEnd(span)),
|
||||
|
||||
(Ready, BindIdent(spair)) => {
|
||||
Transition(Ready).ok(Air::BindIdent(spair))
|
||||
(st @ (Ready | Meta(_)), BindIdent(spair)) => {
|
||||
Transition(st).ok(Air::BindIdent(spair))
|
||||
}
|
||||
(Ready, Ref(spair) | RefSubject(spair)) => {
|
||||
Transition(Ready).ok(Air::RefIdent(spair))
|
||||
|
@ -242,7 +255,7 @@ impl ParseState for NirToAir {
|
|||
Transition(Ready).ok(Air::DocIndepClause(clause))
|
||||
}
|
||||
|
||||
(Ready, Todo(..) | TodoAttr(..)) => {
|
||||
(_, Todo(..) | TodoAttr(..)) => {
|
||||
Transition(Ready).ok(Air::Todo(UNKNOWN_SPAN))
|
||||
}
|
||||
|
||||
|
@ -267,6 +280,10 @@ pub enum NirToAirError {
|
|||
|
||||
/// Match body is not yet supported.
|
||||
TodoMatchBody(Span, Span),
|
||||
|
||||
/// The provided [`Nir`] token of input was unexpected for the body of a
|
||||
/// metavariable that was opened at the provided [`Span`].
|
||||
UnexpectedMetaToken(Span, Nir),
|
||||
}
|
||||
|
||||
impl Display for NirToAirError {
|
||||
|
@ -281,6 +298,13 @@ impl Display for NirToAirError {
|
|||
TodoMatchBody(_, _) => {
|
||||
write!(f, "match body is not yet supported by TAMER")
|
||||
}
|
||||
|
||||
UnexpectedMetaToken(_, tok) => {
|
||||
write!(
|
||||
f,
|
||||
"expected lexical token for metavariable, found {tok}"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -307,6 +331,14 @@ impl Diagnostic for NirToAirError {
|
|||
"tokens in match body are not yet supported by TAMER",
|
||||
),
|
||||
],
|
||||
|
||||
// The user should have been preempted by the parent parser
|
||||
// (e.g. XML->Nir),
|
||||
// and so shouldn't see this.
|
||||
UnexpectedMetaToken(mspan, given) => vec![
|
||||
mspan.note("while parsing the body of this metavariable"),
|
||||
given.span().error("expected a lexical token here"),
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -370,3 +370,28 @@ fn match_no_args_err() {
|
|||
Sut::parse(toks.into_iter()).collect::<Vec<Result<_, _>>>(),
|
||||
);
|
||||
}
|
||||
|
||||
// Sibling text (e.g. mixed content in XML) becomes arbitrary
|
||||
// documentation,
|
||||
// intended to be written in a literate style.
|
||||
#[test]
|
||||
fn text_as_arbitrary_doc() {
|
||||
let text = SPair("foo bar baz".into(), S2);
|
||||
|
||||
#[rustfmt::skip]
|
||||
let toks = vec![
|
||||
Open(Package, S1),
|
||||
Text(text),
|
||||
Close(Package, S3),
|
||||
];
|
||||
|
||||
assert_eq!(
|
||||
#[rustfmt::skip]
|
||||
Ok(vec![
|
||||
O(Air::PkgStart(S1)),
|
||||
O(Air::DocText(text)),
|
||||
O(Air::PkgEnd(S3)),
|
||||
]),
|
||||
Sut::parse(toks.into_iter()).collect(),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -172,7 +172,7 @@ ele_parse! {
|
|||
// program;
|
||||
// see [`NirParseState`] for more information.
|
||||
[super] {
|
||||
[text](_sym, span) => Todo(span),
|
||||
[text](sym, span) => Nir::Text(SPair(sym, span)),
|
||||
TplKw
|
||||
};
|
||||
|
||||
|
@ -263,7 +263,8 @@ ele_parse! {
|
|||
@ {
|
||||
QN_PACKAGE => Ref,
|
||||
QN_EXPORT => TodoAttr,
|
||||
} => Todo(ospan.into()),
|
||||
} => Noop(ospan.into()),
|
||||
// ^ we only care about the `Ref`
|
||||
};
|
||||
|
||||
/// A statement that is accepted within the body of a package.
|
||||
|
|
Loading…
Reference in New Issue