tamer: asg: Support abstract independent clauses (for desc)
The number of files that had to be changed here emphasizes the need for a better abstraction for indirection in the future. I just don't have the time to do so yet. This introduces the same type of indirection that was needed for abstract bindings: we need to be able to distinguish between a metavariable identifier's lexical value as a _string_ vs. a _reference_; the former is how it was interpreted previously, which meant that interpolation would not cause an edge to be created to the metavariable, which in turn means that the metavariable was not referenced. This in turn blocked a change to require that all metavariables be referenced. I'm climbing back up my stash stack. DEV-13163main
parent
666b3d312f
commit
0b84772853
|
@ -859,6 +859,32 @@ sum_ir! {
|
|||
),
|
||||
},
|
||||
|
||||
/// Describe the active object using a reference to a
|
||||
/// metavariable that will be expected to contain an independent
|
||||
/// clause.
|
||||
///
|
||||
/// This is identical to [`Self::DocIndepClause`] in behavior
|
||||
/// except for a level of indirection.
|
||||
/// This reference allows for documentation generation via the
|
||||
/// template system.
|
||||
///
|
||||
/// Implementation Note
|
||||
/// -------------------
|
||||
/// The system could be generalized by eliminating
|
||||
/// [`Self::DocIndepClause`] in favor of this and always
|
||||
/// requiring that a documentation literal be defined using a
|
||||
/// metavariable literal;
|
||||
/// that was not done because it would introduce a
|
||||
/// significant number of additional objects to the graph.
|
||||
DocIndepClauseRef(text: SPair) => {
|
||||
span: text,
|
||||
display: |f| write!(
|
||||
f,
|
||||
"reference to a metavariable describing the active \
|
||||
object as a subject in a sentence",
|
||||
),
|
||||
},
|
||||
|
||||
/// Arbitrary documentation text.
|
||||
///
|
||||
/// TAMER hopes to eventually provide structured documentation,
|
||||
|
|
|
@ -792,14 +792,21 @@ pub fn pkg_expect_ident_obj<O: ObjectRelatable + ObjectRelFrom<Ident>>(
|
|||
pkg_expect_ident_oi(ctx, name).resolve(ctx.asg_ref())
|
||||
}
|
||||
|
||||
pub fn expect_ident_oi<O: ObjectRelatable + ObjectRelFrom<Ident>>(
|
||||
ctx: &<AirAggregate as ParseState>::Context,
|
||||
env: impl ObjectIndexRelTo<Ident>,
|
||||
name: SPair,
|
||||
) -> ObjectIndex<O> {
|
||||
ctx.env_scope_lookup::<Ident>(env, name)
|
||||
.expect("missing requested Ident `{name}`")
|
||||
.definition_narrow::<O>(ctx.asg_ref())
|
||||
.expect("missing `{name}` definition")
|
||||
}
|
||||
|
||||
pub fn expect_ident_obj<O: ObjectRelatable + ObjectRelFrom<Ident>>(
|
||||
ctx: &<AirAggregate as ParseState>::Context,
|
||||
env: impl ObjectIndexRelTo<Ident>,
|
||||
name: SPair,
|
||||
) -> &O {
|
||||
ctx.env_scope_lookup::<Ident>(env, name)
|
||||
.expect("missing requested Ident `{name}`")
|
||||
.definition_narrow::<O>(ctx.asg_ref())
|
||||
.expect("missing `{name}` definition")
|
||||
.resolve(ctx.asg_ref())
|
||||
expect_ident_oi(ctx, env, name).resolve(ctx.asg_ref())
|
||||
}
|
||||
|
|
|
@ -20,7 +20,9 @@
|
|||
mod apply;
|
||||
|
||||
use super::*;
|
||||
use crate::asg::air::test::{as_pkg_body, expect_ident_obj, Sut};
|
||||
use crate::asg::air::test::{
|
||||
as_pkg_body, expect_ident_obj, expect_ident_oi, Sut,
|
||||
};
|
||||
use crate::convert::ExpectInto;
|
||||
use crate::span::dummy::*;
|
||||
use crate::{
|
||||
|
@ -966,3 +968,132 @@ fn expression_referencing_abstract_with_missing_is_abstract() {
|
|||
let expr = expect_ident_obj::<Expr>(&ctx, oi_tpl, spair("expr", S21));
|
||||
assert_eq!(expr.meta_state(), MetaState::Abstract);
|
||||
}
|
||||
|
||||
// Independent clauses representing descriptions can also reference
|
||||
// metavariables,
|
||||
// allowing for documentation generation.
|
||||
// Because such a clause requires expansion,
|
||||
// and because it is owned by the object that it describes,
|
||||
// it must cause the parent object to also be interpreted as abstract.
|
||||
#[test]
|
||||
fn abstract_doc_clause_binds_to_ref() {
|
||||
#[rustfmt::skip]
|
||||
let toks = [
|
||||
TplStart(S1),
|
||||
BindIdent(spair("_tpl_", S2)),
|
||||
|
||||
MetaStart(S3),
|
||||
BindIdent(spair("@param@", S4)), // <-.
|
||||
MetaEnd(S5), // |
|
||||
// |
|
||||
ExprStart(ExprOp::Sum, S7), // |
|
||||
BindIdent(spair("expr", S8)), // |
|
||||
// |
|
||||
// This reference causes the parent // |
|
||||
// expression to become abstract since // |
|
||||
// it requires expansion. // |
|
||||
DocIndepClauseRef(spair("@param@", S9)), // --'
|
||||
ExprEnd(S11),
|
||||
TplEnd(S12),
|
||||
];
|
||||
|
||||
let ctx = air_ctx_from_pkg_body_toks(toks);
|
||||
let asg = ctx.asg_ref();
|
||||
|
||||
let oi_tpl = pkg_expect_ident_oi::<Tpl>(&ctx, spair("_tpl_", S20));
|
||||
|
||||
// Even though the _calculation_ portion is concrete,
|
||||
// the Doc object causes this expression to be abstract.
|
||||
let oi_expr = expect_ident_oi::<Expr>(&ctx, oi_tpl, spair("expr", S21));
|
||||
let expr = oi_expr.resolve(asg);
|
||||
assert_eq!(expr.meta_state(), MetaState::Abstract);
|
||||
|
||||
let oi_doc = oi_expr
|
||||
.edges_filtered::<Doc>(asg)
|
||||
.next()
|
||||
.expect("missing Doc object on Expr");
|
||||
let doc = oi_doc.resolve(asg);
|
||||
|
||||
assert_eq!(&Doc::AbstractIndepClause(S9), doc);
|
||||
|
||||
let oi_ident = oi_doc
|
||||
.edges_filtered::<Ident>(asg)
|
||||
.next()
|
||||
.expect("missing abstract doc ident");
|
||||
assert_eq!(S4, oi_ident.resolve(asg).span());
|
||||
}
|
||||
|
||||
// Independent clauses representing descriptions can also reference
|
||||
// metavariables,
|
||||
// allowing for documentation generation.
|
||||
// Because such a clause requires expansion,
|
||||
// and because it is owned by the object that it describes,
|
||||
// it must cause the parent object to also be interpreted as abstract.
|
||||
//
|
||||
// It is not expected that the user will encounter this error in practice
|
||||
// when using the source XML language,
|
||||
// because interpolation ensures that documentation is either a literal
|
||||
// or a proper metavariable reference.
|
||||
// With that said,
|
||||
// the intent is to have other IRs lower into AIR in the future.
|
||||
#[test]
|
||||
fn abstract_doc_clause_cannot_bind_to_non_meta_ref() {
|
||||
#[rustfmt::skip]
|
||||
let toks = [
|
||||
TplStart(S1),
|
||||
BindIdent(spair("_tpl_", S2)),
|
||||
|
||||
ExprStart(ExprOp::Sum, S3),
|
||||
BindIdent(spair("notmeta", S4)), // <-.
|
||||
ExprEnd(S5), // |
|
||||
// |
|
||||
ExprStart(ExprOp::Sum, S7), // |
|
||||
BindIdent(spair("expr", S8)), // |
|
||||
// |
|
||||
// This references another expression. // |
|
||||
DocIndepClauseRef(spair("notmeta", S9)), // --'
|
||||
ExprEnd(S11),
|
||||
TplEnd(S12),
|
||||
];
|
||||
|
||||
let mut sut = parse_as_pkg_body(toks);
|
||||
|
||||
assert_eq!(
|
||||
#[rustfmt::skip]
|
||||
vec![
|
||||
Ok(Parsed::Incomplete), // PkgStart
|
||||
Ok(Parsed::Incomplete), // TplStart
|
||||
Ok(Parsed::Incomplete), // BindIdent
|
||||
|
||||
Ok(Parsed::Incomplete), // ExprStart
|
||||
Ok(Parsed::Incomplete), // BindIdent
|
||||
Ok(Parsed::Incomplete), // ExprEnd
|
||||
|
||||
Ok(Parsed::Incomplete), // ExprStart
|
||||
Ok(Parsed::Incomplete), // BindIdent
|
||||
|
||||
Err(ParseError::StateError(
|
||||
AsgError::InvalidDocRef(S9, S4)
|
||||
)),
|
||||
|
||||
// RECOVERY: We build the expression,
|
||||
// omitting the documentation string.
|
||||
Ok(Parsed::Incomplete), // ExprEnd
|
||||
Ok(Parsed::Incomplete), // TplEnd
|
||||
Ok(Parsed::Incomplete), // PkgEnd
|
||||
],
|
||||
sut.by_ref().collect::<Vec<_>>(),
|
||||
);
|
||||
|
||||
let ctx = sut.finalize().unwrap().into_private_context();
|
||||
let asg = ctx.asg_ref();
|
||||
|
||||
// Let's make sure that the template created after recovery succeeded.
|
||||
let oi_tpl = pkg_expect_ident_oi::<Tpl>(&ctx, spair("_tpl_", S20));
|
||||
|
||||
// We should have kept the expression despite the error.
|
||||
assert!(oi_tpl
|
||||
.edges_filtered::<Ident>(asg)
|
||||
.filter_map(|oi_ident| oi_ident.definition_narrow::<Expr>(asg))
|
||||
.any(|oi| oi.resolve(asg).span() == S7.merge(S11).unwrap()));
|
||||
}
|
||||
|
|
|
@ -163,6 +163,14 @@ pub enum AsgError {
|
|||
/// but it cannot be conveyed as documentation.
|
||||
InvalidDocContextMeta(ErrorOccurrenceSpan, ErrorContextSpan),
|
||||
|
||||
/// Abstract documentation references an identifier that is not bound to
|
||||
/// a metavariable.
|
||||
///
|
||||
/// Documentation was expected to be derived from the lexical value of a
|
||||
/// metavariable during expansion,
|
||||
/// but the provided identifier does not reference a metavariable.
|
||||
InvalidDocRef(ErrorOccurrenceSpan, ErrorTargetSpan),
|
||||
|
||||
/// A circular dependency was found where it is not permitted.
|
||||
///
|
||||
/// A cycle almost always means that computing the value of an object
|
||||
|
@ -204,6 +212,12 @@ type FirstOccurrenceSpan = Span;
|
|||
/// this may refer to the container of the object that caused an error.
|
||||
type ErrorContextSpan = Span;
|
||||
|
||||
/// The span representing the target of a reference.
|
||||
///
|
||||
/// It is expected that a corresponding [`ErrorOccurrenceSpan`] reference
|
||||
/// the _source_ of the reference.
|
||||
type ErrorTargetSpan = Span;
|
||||
|
||||
impl Display for AsgError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use AsgError::*;
|
||||
|
@ -285,6 +299,11 @@ impl Display for AsgError {
|
|||
(template parameters)"
|
||||
),
|
||||
|
||||
InvalidDocRef(_, _) => write!(
|
||||
f,
|
||||
"expected metavariable reference for abstract documentation",
|
||||
),
|
||||
|
||||
UnsupportedCycle(cycle) => {
|
||||
write!(f, "circular dependency: {cycle}")
|
||||
}
|
||||
|
@ -489,6 +508,16 @@ impl Diagnostic for AsgError {
|
|||
span.help("did you mean to enclose this in a `<text>` node?"),
|
||||
],
|
||||
|
||||
InvalidDocRef(ref_span, target_span) => vec![
|
||||
ref_span.error(
|
||||
"expected metavariable reference \
|
||||
(such as a template parameter)",
|
||||
),
|
||||
target_span
|
||||
.note("documentation is referencing this identifier"),
|
||||
target_span.help("this identifier is not a metavariable"),
|
||||
],
|
||||
|
||||
UnsupportedCycle(cycle) => {
|
||||
// The cycle description clearly describes the cycle,
|
||||
// but in neutral terms,
|
||||
|
|
|
@ -977,7 +977,7 @@ impl<O: ObjectKind> ObjectIndex<O> {
|
|||
{
|
||||
self.edges_filtered::<Doc>(asg)
|
||||
.map(ObjectIndex::cresolve(asg))
|
||||
.find_map(Doc::indep_clause)
|
||||
.find_map(Doc::concrete_indep_clause)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -22,9 +22,10 @@
|
|||
//! TODO: Document TAME's stance on documentation and literate programming,
|
||||
//! much of which hasn't been able to be realized over the years.
|
||||
|
||||
use super::prelude::*;
|
||||
use super::{ident::IdentDefinition, prelude::*, Ident, Meta};
|
||||
use crate::{
|
||||
parse::{util::SPair, Token},
|
||||
asg::graph::ProposedRel,
|
||||
parse::{prelude::Annotate, util::SPair, Token},
|
||||
span::Span,
|
||||
};
|
||||
use std::fmt::Display;
|
||||
|
@ -49,6 +50,17 @@ pub enum Doc {
|
|||
/// The intent is for this text to be mixed with sibling objects,
|
||||
/// in a style similar to that of literate programming.
|
||||
Text(SPair),
|
||||
|
||||
/// An [`IndepClause`](Self::IndepClause)whose text will not be known
|
||||
/// until expansion.
|
||||
///
|
||||
/// This object is expected to contain an edge to an identifier
|
||||
/// representing a metavariable whose lexical value will serve as the
|
||||
/// documentation text.
|
||||
///
|
||||
/// The associated span is the location at which the documentation was
|
||||
/// defined.
|
||||
AbstractIndepClause(Span),
|
||||
}
|
||||
|
||||
impl Display for Doc {
|
||||
|
@ -64,30 +76,115 @@ impl Doc {
|
|||
Self::IndepClause(clause)
|
||||
}
|
||||
|
||||
/// Reference an identifier bound to a metavariable whose lexical value
|
||||
/// will represent an independent clause as in
|
||||
/// [`Self::new_indep_clause`].
|
||||
pub fn new_indep_clause_ref(ref_span: Span) -> Self {
|
||||
Self::AbstractIndepClause(ref_span)
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
pub fn concrete_indep_clause(&self) -> Option<SPair> {
|
||||
match self {
|
||||
Self::IndepClause(spair) => Some(*spair),
|
||||
Self::Text(_) => None,
|
||||
|
||||
// An independent clause _will_ be available at some point,
|
||||
// but is not yet.
|
||||
Self::AbstractIndepClause(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn span(&self) -> Span {
|
||||
match self {
|
||||
Self::IndepClause(spair) | Self::Text(spair) => spair.span(),
|
||||
Self::AbstractIndepClause(span) => *span,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object_rel! {
|
||||
/// Templates may expand into nearly any context,
|
||||
/// and must therefore be able to contain just about anything.
|
||||
/// Documentation strings cannot contain children,
|
||||
/// but they can be abstract.
|
||||
Doc -> {
|
||||
// empty
|
||||
// References to identifiers representing metavariables are
|
||||
// permitted for documentation generation.
|
||||
cross Ident {
|
||||
fn pre_add_edge(
|
||||
asg: &mut Asg,
|
||||
rel: ProposedRel<Self, Ident>,
|
||||
commit: impl FnOnce(&mut Asg),
|
||||
) -> Result<(), AsgError> {
|
||||
match rel.to_oi.definition(asg) {
|
||||
// Missing; we'll have to check once we support
|
||||
// notification on definition.
|
||||
None => Ok(commit(asg)),
|
||||
|
||||
// Only metavariable references are permitted.
|
||||
Some(IdentDefinition::Meta(_)) => {
|
||||
Ok(commit(asg))
|
||||
},
|
||||
|
||||
Some(
|
||||
IdentDefinition::Expr(_)
|
||||
| IdentDefinition::Tpl(_)
|
||||
) => Err(AsgError::InvalidDocRef(
|
||||
// This is a cross edge and so should always be
|
||||
// available
|
||||
rel.ref_span.unwrap_or(rel.from_oi.span()),
|
||||
rel.to_oi.resolve(asg).span(),
|
||||
))
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// TODO: This is not actually used.
|
||||
// There seems to be a bug in the `min_specialization` feature
|
||||
// that results in too narrow of a type inference on
|
||||
// `ObjectIndexRelTo<OB> for ObjectIndexTo<OB>`,
|
||||
// expecting `ProposedRel<Doc, Ident>` instead of
|
||||
// `<ProposedRel<Doc, OB>` when there is only one `AsgRelMut`
|
||||
// impl defined
|
||||
// (when there's only `Ident` above).
|
||||
// Remove this rel to see if it's still a problem,
|
||||
// noting that the nightly version of Rust must be manually
|
||||
// bumped.
|
||||
cross Meta {
|
||||
fn pre_add_edge(
|
||||
_asg: &mut Asg,
|
||||
rel: ProposedRel<Self, Meta>,
|
||||
_commit: impl FnOnce(&mut Asg),
|
||||
) -> Result<(), AsgError> {
|
||||
diagnostic_panic!(
|
||||
vec![
|
||||
rel.from_oi.span()
|
||||
.internal_error("cannot create edge from here..."),
|
||||
rel.to_oi.span()
|
||||
.internal_error("...to here")
|
||||
],
|
||||
"unsupported edge (see object/doc.rs for more information)"
|
||||
)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectIndex<Doc> {
|
||||
/// The identifier that is expected to resolve to a metavariable whose
|
||||
/// lexical value will become the documentation string.
|
||||
///
|
||||
/// This is applicable to [`Doc::AbstractIndepClause`].
|
||||
/// If no such reference exists,
|
||||
/// [`None`] will be returned.
|
||||
pub fn abstract_indep_clause_ref(
|
||||
&self,
|
||||
asg: &Asg,
|
||||
) -> Option<ObjectIndex<Ident>> {
|
||||
self.edges_filtered::<Ident>(asg).next()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -470,12 +470,12 @@ object_rel! {
|
|||
),
|
||||
|
||||
None => {
|
||||
rel.from_oi.map_obj_inner(asg, |meta: MetaState| {
|
||||
rel.from_oi.map_obj_inner(asg, |metast: MetaState| {
|
||||
// This is a cross edge and so this span must be
|
||||
// available, but the types provided don't
|
||||
// guarantee that.
|
||||
let span = rel.ref_span.unwrap_or(rel.to_oi.span());
|
||||
meta.found_missing(span)
|
||||
metast.found_missing(span)
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -514,6 +514,20 @@ object_rel! {
|
|||
match doc {
|
||||
Doc::IndepClause(_) => Ok(commit(asg)),
|
||||
|
||||
// Even though this expression's _calculation_ may not
|
||||
// be abstract,
|
||||
// it does own this documentation object and
|
||||
// therefore the only route to its expansion is
|
||||
// through this expression.
|
||||
Doc::AbstractIndepClause(_) => {
|
||||
rel.from_oi.map_obj_inner(
|
||||
asg,
|
||||
|metast: MetaState| metast.found_abstract(),
|
||||
);
|
||||
|
||||
Ok(commit(asg))
|
||||
}
|
||||
|
||||
// This maintains compatibility with the XLST-based
|
||||
// system.
|
||||
// TODO: Inline documentation was prohibited within
|
||||
|
|
|
@ -191,7 +191,8 @@ object_rel! {
|
|||
let doc = rel.to_oi.resolve(asg);
|
||||
|
||||
match doc {
|
||||
Doc::IndepClause(_) => Ok(commit(asg)),
|
||||
Doc::IndepClause(_)
|
||||
| Doc::AbstractIndepClause(_) => Ok(commit(asg)),
|
||||
|
||||
// It doesn't make sense to allow inline documentation
|
||||
// here,
|
||||
|
|
|
@ -1117,6 +1117,8 @@ pub trait ObjectIndexTreeRelTo<OB: ObjectRelatable>:
|
|||
/// simple sentence or as part of a compound sentence.
|
||||
/// There should only be one such clause for any given object,
|
||||
/// but that is not enforced here.
|
||||
///
|
||||
/// See also [`Self::add_desc_short`] for abstract descriptions.
|
||||
fn add_desc_short(
|
||||
self,
|
||||
asg: &mut Asg,
|
||||
|
@ -1130,6 +1132,25 @@ pub trait ObjectIndexTreeRelTo<OB: ObjectRelatable>:
|
|||
Ok(self)
|
||||
}
|
||||
|
||||
/// Reference an identifier bound to a metavariable whose lexical value
|
||||
/// will represent a short independent clause.
|
||||
///
|
||||
/// See also [`Self::add_desc_short`] for concrete descriptions.
|
||||
fn add_desc_short_ref(
|
||||
self,
|
||||
asg: &mut Asg,
|
||||
oi_ident: ObjectIndex<Ident>,
|
||||
ref_span: Span,
|
||||
) -> Result<Self, AsgError>
|
||||
where
|
||||
Self: ObjectIndexTreeRelTo<Doc>,
|
||||
{
|
||||
let oi_doc = asg.create(Doc::new_indep_clause_ref(ref_span));
|
||||
oi_doc.add_cross_edge_to(asg, oi_ident, ref_span)?;
|
||||
asg.add_tree_edge(self, oi_doc)?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Arbitrary text serving as documentation in a literate style,
|
||||
/// to be expanded into the application site.
|
||||
fn append_doc_text(
|
||||
|
|
|
@ -613,10 +613,32 @@ impl<'a> TreeContext<'a> {
|
|||
Some(attr_desc(*desc))
|
||||
}
|
||||
|
||||
// @desc is still abstract and contains a metavariable reference;
|
||||
// just output the name of the metavariable
|
||||
(
|
||||
Object::Expr(..) | Object::Tpl(..),
|
||||
Doc::AbstractIndepClause(_),
|
||||
) => {
|
||||
let name = oi_doc
|
||||
.abstract_indep_clause_ref(self.asg)
|
||||
.and_then(|oi_ident| oi_ident.resolve(self.asg).name())
|
||||
.diagnostic_expect(
|
||||
|| {
|
||||
vec![doc.span().internal_error(
|
||||
"missing name for metavariable reference",
|
||||
)]
|
||||
},
|
||||
"unable to resolve doc metavariable reference",
|
||||
);
|
||||
|
||||
Some(attr_desc(name))
|
||||
}
|
||||
|
||||
// template/param/@desc
|
||||
(Object::Meta(_), Doc::IndepClause(_desc))
|
||||
if self.tpl_apply.is_none() =>
|
||||
{
|
||||
(
|
||||
Object::Meta(_),
|
||||
Doc::IndepClause(_) | Doc::AbstractIndepClause(_),
|
||||
) if self.tpl_apply.is_none() => {
|
||||
// This is already covered in `emit_tpl_param`
|
||||
None
|
||||
}
|
||||
|
@ -630,12 +652,12 @@ impl<'a> TreeContext<'a> {
|
|||
None
|
||||
}
|
||||
|
||||
_ => {
|
||||
(_, doc) => {
|
||||
diagnostic_todo!(
|
||||
vec![oi_doc.internal_error(
|
||||
"this documentation is not supported in XIRF output"
|
||||
)],
|
||||
"unsupported documentation",
|
||||
"unsupported documentation: {doc:?}",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -160,6 +160,12 @@ pub enum Nir {
|
|||
/// Describe the [`NirEntity`] atop of the stack.
|
||||
Desc(SPair),
|
||||
|
||||
/// Describe the [`NirEntity`] atop of the stack using a lexical
|
||||
/// reference to a metavariable.
|
||||
///
|
||||
/// This allows for documentation generation.
|
||||
DescRef(SPair),
|
||||
|
||||
/// A string literal.
|
||||
///
|
||||
/// The meaning of this string depends on context.
|
||||
|
@ -223,6 +229,10 @@ impl Nir {
|
|||
// expansion.
|
||||
BindIdentAbstract(_) => None,
|
||||
|
||||
// Lexical references for descriptions do not become concrete
|
||||
// until expansion.
|
||||
DescRef(_) => None,
|
||||
|
||||
Noop(_) => None,
|
||||
}
|
||||
}
|
||||
|
@ -257,6 +267,7 @@ impl Map<SymbolId> for Nir {
|
|||
RefSubject(spair) => RefSubject(spair.map(f)),
|
||||
Ref(spair) => Ref(spair.map(f)),
|
||||
Desc(spair) => Desc(spair.map(f)),
|
||||
DescRef(spair) => DescRef(spair.map(f)),
|
||||
Text(spair) => Text(spair.map(f)),
|
||||
Import(spair) => Import(spair.map(f)),
|
||||
|
||||
|
@ -386,6 +397,7 @@ impl Token for Nir {
|
|||
| RefSubject(spair)
|
||||
| Ref(spair)
|
||||
| Desc(spair)
|
||||
| DescRef(spair)
|
||||
| Text(spair)
|
||||
| Import(spair) => spair.span(),
|
||||
|
||||
|
@ -439,6 +451,12 @@ impl Display for Nir {
|
|||
// TODO: TtQuote doesn't yet escape quotes at the time of writing!
|
||||
Desc(spair) => write!(f, "description {}", TtQuote::wrap(spair)),
|
||||
|
||||
DescRef(spair) => write!(
|
||||
f,
|
||||
"abstract description referencing identifier {}",
|
||||
TtQuote::wrap(spair)
|
||||
),
|
||||
|
||||
// TODO: Not yet safe to output arbitrary text;
|
||||
// need to determine how to handle newlines and other types of
|
||||
// output.
|
||||
|
|
|
@ -294,6 +294,9 @@ impl ParseState for NirToAir {
|
|||
(st @ (Ready | Meta(_)), Desc(clause)) => {
|
||||
Transition(st).ok(Air::DocIndepClause(clause))
|
||||
}
|
||||
(st @ (Ready | Meta(_)), DescRef(spair)) => {
|
||||
Transition(st).ok(Air::DocIndepClauseRef(spair))
|
||||
}
|
||||
|
||||
(Ready, Import(namespec)) => {
|
||||
Transition(Ready).ok(Air::PkgImport(namespec))
|
||||
|
|
|
@ -471,6 +471,13 @@ impl ParseState for InterpState {
|
|||
// since its name will not be known until expansion-time.
|
||||
Nir::BindIdent(x) => Nir::BindIdentAbstract(x),
|
||||
|
||||
// Descriptions must also be converted into an explicit
|
||||
// reference,
|
||||
// otherwise the new metavariable reference will be
|
||||
// interpreted as the documentation string rather
|
||||
// than a reference to one.
|
||||
Nir::Desc(x) => Nir::DescRef(x),
|
||||
|
||||
// All other tokens only have their symbols replaced by
|
||||
// the above.
|
||||
x => x,
|
||||
|
|
|
@ -195,6 +195,50 @@ fn concrete_bind_ident_desugars_into_abstract_bind_after_interpolation() {
|
|||
);
|
||||
}
|
||||
|
||||
// Similar to the distinction of an abstract bind above,
|
||||
// we must also make a distinction of a reference to a documentation
|
||||
// string.
|
||||
// The reason for this is that the reference would otherwise be taken
|
||||
// (lexically) to be the value of the documentation string;
|
||||
// we require that it represent indirection,
|
||||
// and must indicate to the system in some way that the lexeme is in
|
||||
// fact an identifier and not a string.
|
||||
#[test]
|
||||
fn doc_desc_desugars_into_desc_ref_after_interpolation() {
|
||||
let given_val = "{@iamref@}";
|
||||
// |[------]|
|
||||
// |1 8|
|
||||
// | B |
|
||||
// [--------]
|
||||
// 0 9
|
||||
// A
|
||||
|
||||
// Non-zero span offset ensures that derived spans properly consider
|
||||
// parent offset.
|
||||
let a = DC.span(10, 10);
|
||||
let b = DC.span(11, 8);
|
||||
|
||||
let given_sym = Nir::Desc(SPair(given_val.into(), a));
|
||||
let toks = vec![given_sym];
|
||||
|
||||
let mut sut = Sut::parse(toks.into_iter());
|
||||
|
||||
let expect_name = expect_expanded_header(&mut sut, a);
|
||||
|
||||
assert_eq!(
|
||||
Ok(vec![
|
||||
// The interpolation occurs the same as above.
|
||||
Object(Nir::Ref(SPair("@iamref@".into(), b))),
|
||||
Object(Nir::Close(NirEntity::TplParam, a)),
|
||||
// We translate the original `Desc` into a `DescRef`,
|
||||
// indicating that the lexical value is to be interpreted as
|
||||
// an identifier reference rather than a documentation string.
|
||||
Object(Nir::DescRef(SPair(expect_name, a))),
|
||||
]),
|
||||
sut.collect(),
|
||||
);
|
||||
}
|
||||
|
||||
// When ending with an interpolated variable,
|
||||
// the parser should recognize that we've returned to the outer literal
|
||||
// context and permit successful termination of the specification string.
|
||||
|
|
Loading…
Reference in New Issue