tamer: asg: Expression identifier references

This adds support for identifier references, adding `Ident` as a valid edge
type for `Expr`.

There is nothing in the system yet to enforce ontology through levels of
indirection; that will come later on.

I'm testing these changes with a very minimal NIR parse, which I'll commit
shortly.

DEV-13597
main
Mike Gerwitz 2023-01-23 15:49:25 -05:00
parent 055ff4a9d9
commit 39ebb74583
6 changed files with 163 additions and 9 deletions

View File

@ -101,6 +101,18 @@ pub enum Air {
/// Binding an identifier will declare it.
ExprIdent(SPair),
/// Reference another expression identified by the given [`SPair`].
///
/// Values can be referenced before they are declared or defined,
/// so the provided identifier need not yet exist.
/// However,
/// the identifier must eventually be bound to an [`Expr`].
///
/// Since all values in TAME are referentially tansparent,
/// the system has flexibility in determining what it should do with a
/// reference.
ExprRef(SPair),
/// Declare a resolved identifier.
IdentDecl(SPair, IdentKind, Source),
@ -147,6 +159,7 @@ impl Token for Air {
ExprOpen(_, span) | ExprClose(span) => *span,
ExprIdent(spair)
| ExprRef(spair)
| IdentDecl(spair, _, _)
| IdentExternDecl(spair, _, _)
| IdentDep(spair, _)
@ -173,6 +186,14 @@ impl Display for Air {
write!(f, "identify expression as {}", TtQuote::wrap(id))
}
ExprRef(id) => {
write!(
f,
"reference to the expression identified by {}",
TtQuote::wrap(id)
)
}
IdentDecl(spair, _, _) => {
write!(f, "declaration of identifier {}", TtQuote::wrap(spair))
}
@ -479,10 +500,6 @@ impl ParseState for AirAggregate {
}
}
(st @ Empty(_), ExprIdent(ident)) => {
Transition(st).err(AsgError::InvalidExprBindContext(ident))
}
(BuildingExpr(es, oi), ExprIdent(id)) => {
let identi = asg.lookup_or_missing(id);
@ -495,6 +512,19 @@ impl ParseState for AirAggregate {
}
}
(BuildingExpr(es, oi), ExprRef(ident)) => {
Transition(BuildingExpr(es, oi.ref_expr(asg, ident)))
.incomplete()
}
(st @ Empty(_), ExprIdent(ident)) => {
Transition(st).err(AsgError::InvalidExprBindContext(ident))
}
(st @ Empty(_), ExprRef(ident)) => {
Transition(st).err(AsgError::InvalidExprRefContext(ident))
}
(st @ Empty(_), IdentDecl(name, kind, src)) => {
asg.declare(name, kind, src).map(|_| ()).transition(st)
}

View File

@ -783,6 +783,88 @@ fn expr_still_dangling_on_redefine() {
assert_eq!(expr.span(), S7.merge(S10).unwrap());
}
#[test]
fn expr_ref_to_ident() {
let id_foo = SPair("foo".into(), S2);
let id_bar = SPair("bar".into(), S6);
let toks = vec![
Air::ExprOpen(ExprOp::Sum, S1),
Air::ExprIdent(id_foo),
// Reference to an as-of-yet-undefined id (okay)
Air::ExprRef(id_bar),
Air::ExprClose(S4),
//
// Another expression to reference the first
// (we don't handle cyclic references until a topological sort,
// so no point in referencing ourselves;
// it'd work just fine here.)
Air::ExprOpen(ExprOp::Sum, S5),
Air::ExprIdent(id_bar),
Air::ExprClose(S7),
];
let asg = asg_from_toks(toks);
let oi_foo = asg.expect_ident_oi::<Expr>(id_foo);
let foo_refs = oi_foo
.edges(&asg)
.filter_map(ObjectRel::narrow::<Ident>)
.collect::<Vec<_>>();
// We should have only a single reference (to `id_bar`).
assert_eq!(foo_refs.len(), 1);
let oi_ident_bar = foo_refs[0];
let ident_bar = oi_ident_bar.resolve(&asg);
assert_eq!(ident_bar.span(), id_bar.span());
// The identifier will have originally been `Missing`,
// since it did not exist at the point of reference.
// But it should now properly identify the other expression.
assert_matches!(ident_bar, Ident::Transparent(..));
let oi_expr_bar = asg.expect_ident_oi::<Expr>(id_bar);
assert!(oi_ident_bar.is_bound_to(&asg, oi_expr_bar));
}
#[test]
fn expr_ref_outside_of_expr_context() {
let id_foo = SPair("foo".into(), S2);
let toks = vec![
// This will fail since we're not in an expression context.
Air::ExprRef(id_foo),
// RECOVERY: Simply ignore the above.
Air::ExprOpen(ExprOp::Sum, S1),
Air::ExprIdent(id_foo),
Air::ExprClose(S3),
];
let mut sut = Sut::parse(toks.into_iter());
assert_eq!(
vec![
Err(ParseError::StateError(AsgError::InvalidExprRefContext(
id_foo
))),
// RECOVERY: Proceed as normal
Ok(Parsed::Incomplete), // OpenExpr
Ok(Parsed::Incomplete), // IdentExpr
Ok(Parsed::Incomplete), // CloseExpr
],
sut.by_ref().collect::<Vec<_>>(),
);
let asg = sut.finalize().unwrap().into_context();
// Verify that the identifier was bound just to have some confidence in
// the recovery.
let expr = asg.expect_ident_obj::<Expr>(id_foo);
assert_eq!(expr.span(), S1.merge(S3).unwrap());
}
fn asg_from_toks<I: IntoIterator<Item = Air>>(toks: I) -> Asg
where
I::IntoIter: Debug,

View File

@ -83,6 +83,12 @@ pub enum AsgError {
/// Note that the user may encounter an error from a higher-level IR
/// instead of this one.
InvalidExprBindContext(SPair),
/// Attempted to reference an identifier as part of an expression while
/// not in an expression context.
///
/// Ideally this situation is syntactically invalid in a source IR.
InvalidExprRefContext(SPair),
}
impl Display for AsgError {
@ -102,6 +108,13 @@ impl Display for AsgError {
InvalidExprBindContext(_) => {
write!(f, "invalid expression identifier binding context")
}
InvalidExprRefContext(ident) => {
write!(
f,
"invalid context for expression identifier {}",
TtQuote::wrap(ident)
)
}
}
}
}
@ -115,7 +128,8 @@ impl Error for AsgError {
IdentRedefine(_, _)
| DanglingExpr(_)
| UnbalancedExpr(_)
| InvalidExprBindContext(_) => None,
| InvalidExprBindContext(_)
| InvalidExprRefContext(_) => None,
}
}
}
@ -180,6 +194,11 @@ impl Diagnostic for AsgError {
the expression is closed",
),
],
InvalidExprRefContext(ident) => vec![ident.error(
"cannot reference the value of an expression from outside \
of an expression context",
)],
}
}
}

View File

@ -626,6 +626,7 @@ pub trait ObjectRel {
/// [`Ident`].
///
/// See [`ObjectRel`] for more information.
#[derive(Debug, PartialEq, Eq)]
pub enum IdentRel {
Ident(ObjectIndex<Ident>),
Expr(ObjectIndex<Expr>),
@ -673,6 +674,7 @@ impl From<ObjectIndex<Expr>> for IdentRel {
/// [`Expr`].
///
/// See [`ObjectRel`] for more information.
#[derive(Debug, PartialEq, Eq)]
pub enum ExprRel {
Ident(ObjectIndex<Ident>),
Expr(ObjectIndex<Expr>),

View File

@ -22,7 +22,7 @@
use std::fmt::Display;
use super::{Asg, ObjectIndex};
use crate::{f::Functor, num::Dim, span::Span};
use crate::{f::Functor, num::Dim, parse::util::SPair, span::Span};
/// Expression.
///
@ -194,8 +194,17 @@ impl ObjectIndex<Expr> {
expr: Expr,
) -> ObjectIndex<Expr> {
let oi_subexpr = asg.create(expr);
self.add_edge_to(asg, oi_subexpr);
oi_subexpr.add_edge_from(asg, self)
}
oi_subexpr
/// Reference the value of the expression identified by `ident` as if it
/// were a subexpression.
///
/// If `ident` does not yet exist on the graph,
/// a missing identifier will take its place to be later resolved once
/// it becomes available.
pub fn ref_expr(self, asg: &mut Asg, ident: SPair) -> Self {
let identi = asg.lookup_or_missing(ident);
self.add_edge_to(asg, identi)
}
}

View File

@ -21,7 +21,7 @@
use super::{
super::{Asg, AsgError, ObjectIndex, ObjectKind},
ObjectRelTo,
ObjectRel, ObjectRelTo, ObjectRelatable,
};
use crate::{
diagnose::{Annotate, Diagnostic},
@ -1020,6 +1020,18 @@ impl ObjectIndex<Ident> {
})
.map(|ident_oi| ident_oi.add_edge_to(asg, definition))
}
/// Whether this identifier is bound to the object represented by `oi`.
///
/// To bind an identifier,
/// see [`Self::bind_definition`].
pub fn is_bound_to<O: ObjectKind + ObjectRelatable>(
&self,
asg: &Asg,
oi: ObjectIndex<O>,
) -> bool {
self.edges(asg).find_map(ObjectRel::narrow) == Some(oi)
}
}
#[cfg(test)]