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-13597main
parent
055ff4a9d9
commit
39ebb74583
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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",
|
||||
)],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>),
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)]
|
||||
|
|
Loading…
Reference in New Issue