From 39d093525cb98c1d5be0efa1c339ae38772580d0 Mon Sep 17 00:00:00 2001 From: Mike Gerwitz Date: Mon, 30 Jan 2023 16:51:24 -0500 Subject: [PATCH] tamer: nir, asg: Introduce package to ASG This does not yet create edges from identifiers to the package; just getting this introduced was quite a bit of work, so I want to get this committed. Note that this also includes a change to NIR so that `Close` contains the entity so that we can pattern-match for AIR transformations rather than retaining yet another stack with checks that are already going to be done by AIR. This makes NIR stand less on its own from a self-validation point, but that's okay, given that it's the language that the user entered and, conceptually, they could enter invalid NIR the same as they enter invalid XML (e.g. from a REPL). In _practice_, of course, NIR is lowered from XML and the schema is enforced during that lowering and so the validation does exist as part of that parsing. These concessions speak more to the verbosity of the language (Rust) than anything. DEV-13159 --- tamer/src/asg/air.rs | 95 ++++++++--- tamer/src/asg/air/test.rs | 269 +++++++++++++++++++++++++----- tamer/src/asg/error.rs | 60 +++++-- tamer/src/asg/graph/object.rs | 61 ++++++- tamer/src/asg/graph/object/pkg.rs | 45 +++++ tamer/src/ld/xmle/lower.rs | 14 +- tamer/src/nir.rs | 18 +- tamer/src/nir/air.rs | 10 +- tamer/src/nir/air/test.rs | 15 +- tamer/src/nir/interp.rs | 2 +- tamer/src/nir/interp/test.rs | 22 +-- tamer/src/nir/parse.rs | 7 +- 12 files changed, 510 insertions(+), 108 deletions(-) create mode 100644 tamer/src/asg/graph/object/pkg.rs diff --git a/tamer/src/asg/air.rs b/tamer/src/asg/air.rs index fd3f72e4..69d5cec2 100644 --- a/tamer/src/asg/air.rs +++ b/tamer/src/asg/air.rs @@ -18,10 +18,10 @@ // along with this program. If not, see . use super::{ + graph::object::{Expr, Pkg}, Asg, AsgError, ExprOp, FragmentText, IdentKind, ObjectIndex, Source, }; use crate::{ - asg::Expr, f::Functor, fmt::{DisplayWrapper, TtQuote}, parse::{self, util::SPair, ParseState, Token, Transition, Transitionable}, @@ -69,6 +69,23 @@ pub enum Air { /// the ASG. Todo, + /// Begin a new package of identifiers. + /// + /// Packages are responsible for bundling together identifiers + /// representing subsystems that can be composed with other packages. + /// + /// A source language may place limits on the types of [`Object`]s that + /// may appear within a given package, + /// but we have no such restriction. + /// + /// TODO: The package needs a name, + /// and we'll need to determine how to best represent that relative to + /// the project root and be considerate of symlinks. + PkgOpen(Span), + + /// Complete processing of the current package. + PkgClose(Span), + /// Create a new [`Expr`] on the graph and place it atop of the /// expression stack. /// @@ -156,7 +173,10 @@ impl Token for Air { match self { Todo => UNKNOWN_SPAN, - ExprOpen(_, span) | ExprClose(span) => *span, + PkgOpen(span) + | PkgClose(span) + | ExprOpen(_, span) + | ExprClose(span) => *span, ExprIdent(spair) | ExprRef(spair) @@ -178,6 +198,9 @@ impl Display for Air { match self { Todo => write!(f, "TODO"), + PkgOpen(_) => write!(f, "open package"), + PkgClose(_) => write!(f, "close package"), + ExprOpen(op, _) => write!(f, "open {op} expression"), ExprClose(_) => write!(f, "close expression"), @@ -414,10 +437,13 @@ pub enum AirAggregate { /// Parser is not currently performing any work. Empty(ExprStack), + /// A package is being defined. + PkgDfn(ObjectIndex, ExprStack), + /// Building an expression. /// /// Expressions may be nested arbitrarily deeply. - BuildingExpr(ExprStack, ObjectIndex), + BuildingExpr(ObjectIndex, ExprStack, ObjectIndex), } impl Default for AirAggregate { @@ -432,7 +458,8 @@ impl Display for AirAggregate { match self { Empty(es) => write!(f, "awaiting AIR input for ASG with {es}"), - BuildingExpr(es, _) => { + PkgDfn(_, es) => write!(f, "defining package with {es}"), + BuildingExpr(_, es, _) => { write!(f, "building expression with {es}") } } @@ -460,21 +487,45 @@ impl ParseState for AirAggregate { match (self, tok) { (st, Todo) => Transition(st).incomplete(), - (Empty(es), ExprOpen(op, span)) => { + (Empty(es), PkgOpen(span)) => { + let oi_pkg = asg.create(Pkg::new(span)); + Transition(PkgDfn(oi_pkg, es)).incomplete() + } + + (PkgDfn(oi_pkg, es), PkgOpen(span)) => { + Transition(PkgDfn(oi_pkg, es)) + .err(AsgError::NestedPkgOpen(span, oi_pkg.span())) + } + (BuildingExpr(oi_pkg, es, oi), PkgOpen(span)) => { + Transition(BuildingExpr(oi_pkg, es, oi)) + .err(AsgError::NestedPkgOpen(span, oi_pkg.span())) + } + + (PkgDfn(_, es), PkgClose(_)) => Transition(Empty(es)).incomplete(), + + (st @ (Empty(..) | BuildingExpr(..)), PkgClose(span)) => { + Transition(st).err(AsgError::InvalidPkgCloseContext(span)) + } + + (PkgDfn(oi_pkg, es), ExprOpen(op, span)) => { let oi = asg.create(Expr::new(op, span)); - Transition(BuildingExpr(es.activate(), oi)).incomplete() + Transition(BuildingExpr(oi_pkg, es.activate(), oi)).incomplete() } - (BuildingExpr(es, poi), ExprOpen(op, span)) => { + (BuildingExpr(oi_pkg, es, poi), ExprOpen(op, span)) => { let oi = poi.create_subexpr(asg, Expr::new(op, span)); - Transition(BuildingExpr(es.push(poi), oi)).incomplete() + Transition(BuildingExpr(oi_pkg, es.push(poi), oi)).incomplete() } - (st @ Empty(_), ExprClose(span)) => { + (st @ Empty(..), ExprOpen(_, span)) => { + Transition(st).err(AsgError::InvalidExprContext(span)) + } + + (st @ (Empty(..) | PkgDfn(..)), ExprClose(span)) => { Transition(st).err(AsgError::UnbalancedExpr(span)) } - (BuildingExpr(es, oi), ExprClose(end)) => { + (BuildingExpr(oi_pkg, es, oi), ExprClose(end)) => { let start: Span = oi.into(); let _ = oi.map_obj(asg, |expr| { @@ -483,11 +534,11 @@ impl ParseState for AirAggregate { match es.pop() { (es, Some(poi)) => { - Transition(BuildingExpr(es, poi)).incomplete() + Transition(BuildingExpr(oi_pkg, es, poi)).incomplete() } (es, None) => { let dangling = es.is_dangling(); - let st = Empty(es.done()); + let st = PkgDfn(oi_pkg, es.done()); if dangling { Transition(st).err(AsgError::DanglingExpr( @@ -500,28 +551,32 @@ impl ParseState for AirAggregate { } } - (BuildingExpr(es, oi), ExprIdent(id)) => { + (BuildingExpr(oi_pkg, es, oi), ExprIdent(id)) => { let identi = asg.lookup_or_missing(id); // It is important that we do not mark this expression as // reachable unless we successfully bind the identifier. match identi.bind_definition(asg, oi) { - Ok(_) => Transition(BuildingExpr(es.reachable_by(id), oi)) - .incomplete(), - Err(e) => Transition(BuildingExpr(es, oi)).err(e), + Ok(_) => Transition(BuildingExpr( + oi_pkg, + es.reachable_by(id), + oi, + )) + .incomplete(), + Err(e) => Transition(BuildingExpr(oi_pkg, es, oi)).err(e), } } - (BuildingExpr(es, oi), ExprRef(ident)) => { - Transition(BuildingExpr(es, oi.ref_expr(asg, ident))) + (BuildingExpr(oi_pkg, es, oi), ExprRef(ident)) => { + Transition(BuildingExpr(oi_pkg, es, oi.ref_expr(asg, ident))) .incomplete() } - (st @ Empty(_), ExprIdent(ident)) => { + (st @ (Empty(_) | PkgDfn(_, _)), ExprIdent(ident)) => { Transition(st).err(AsgError::InvalidExprBindContext(ident)) } - (st @ Empty(_), ExprRef(ident)) => { + (st @ (Empty(_) | PkgDfn(_, _)), ExprRef(ident)) => { Transition(st).err(AsgError::InvalidExprRefContext(ident)) } diff --git a/tamer/src/asg/air/test.rs b/tamer/src/asg/air/test.rs index fa1fee37..614c5eda 100644 --- a/tamer/src/asg/air/test.rs +++ b/tamer/src/asg/air/test.rs @@ -23,6 +23,7 @@ use super::super::Ident; use super::*; use crate::asg::graph::object::ObjectRel; +use crate::parse::Parser; use crate::{ parse::{ParseError, Parsed}, span::dummy::*, @@ -245,6 +246,73 @@ fn ident_root_existing() { assert!(asg.is_rooted(ident_node)); } +#[test] +fn empty_pkg() { + let toks = vec![Air::PkgOpen(S1), Air::PkgClose(S2)]; + + let mut sut = Sut::parse(toks.into_iter()); + assert!(sut.all(|x| x.is_ok())); + + // TODO: Packages aren't named via AIR at the time of writing, + // so we can't look anything up. +} + +#[test] +fn close_pkg_without_open() { + let toks = vec![ + Air::PkgClose(S1), + // RECOVERY: Try again. + Air::PkgOpen(S2), + Air::PkgClose(S3), + ]; + + assert_eq!( + vec![ + Err(ParseError::StateError(AsgError::InvalidPkgCloseContext(S1))), + // RECOVERY + Ok(Parsed::Incomplete), // PkgOpen + Ok(Parsed::Incomplete), // PkgClose + ], + Sut::parse(toks.into_iter()).collect::>(), + ); +} + +#[test] +fn nested_open_pkg() { + let toks = vec![ + Air::PkgOpen(S1), + Air::PkgOpen(S2), + // RECOVERY + Air::PkgClose(S3), + ]; + + assert_eq!( + vec![ + Ok(Parsed::Incomplete), // PkgOpen + Err(ParseError::StateError(AsgError::NestedPkgOpen(S2, S1))), + // RECOVERY + Ok(Parsed::Incomplete), // PkgClose + ], + Sut::parse(toks.into_iter()).collect::>(), + ); +} + +/// Parse using [`Sut`] when the test does not care about the outer package. +fn parse_as_pkg_body>( + toks: I, +) -> Parser + Debug> +where + ::IntoIter: Debug, +{ + use std::iter; + + Sut::parse( + iter::once(Air::PkgOpen(S1)) + .chain(toks.into_iter()) + .chain(iter::once(Air::PkgClose(S1))), + ) +} + #[test] fn expr_empty_ident() { let id = SPair("foo".into(), S2); @@ -255,7 +323,7 @@ fn expr_empty_ident() { Air::ExprClose(S3), ]; - let mut sut = Sut::parse(toks.into_iter()); + let mut sut = parse_as_pkg_body(toks); assert!(sut.all(|x| x.is_ok())); let asg = sut.finalize().unwrap().into_context(); @@ -266,6 +334,94 @@ fn expr_empty_ident() { assert_eq!(expr.span(), S1.merge(S3).unwrap()); } +#[test] +fn expr_without_pkg() { + let toks = vec![ + // No package + // (because we're not parsing with `parse_as_pkg_body` below) + Air::ExprOpen(ExprOp::Sum, S1), + // RECOVERY + Air::PkgOpen(S2), + Air::PkgClose(S3), + ]; + + assert_eq!( + vec![ + Err(ParseError::StateError(AsgError::InvalidExprContext(S1))), + // RECOVERY + Ok(Parsed::Incomplete), // PkgOpen + Ok(Parsed::Incomplete), // PkgClose + ], + Sut::parse(toks.into_iter()).collect::>(), + ); +} + +// Note that this can't happen in e.g. NIR / TAME's source XML. +#[test] +fn close_pkg_mid_expr() { + let id = SPair("foo".into(), S4); + + let toks = vec![ + Air::PkgOpen(S1), + Air::ExprOpen(ExprOp::Sum, S2), + Air::PkgClose(S3), + // RECOVERY: Let's finish the expression first... + Air::ExprIdent(id), + Air::ExprClose(S5), + // ...and then try to close again. + Air::PkgClose(S6), + ]; + + assert_eq!( + vec![ + Ok(Parsed::Incomplete), // PkgOpen + Ok(Parsed::Incomplete), // ExprOpen + Err(ParseError::StateError(AsgError::InvalidPkgCloseContext(S3))), + // RECOVERY: We should be able to close the package if we just + // finish the expression first, + // demonstrating that recovery properly maintains all state. + Ok(Parsed::Incomplete), // ExprIdent + Ok(Parsed::Incomplete), // ExprClose + // Successful close here. + Ok(Parsed::Incomplete), // PkgClose + ], + Sut::parse(toks.into_iter()).collect::>(), + ); +} + +#[test] +fn open_pkg_mid_expr() { + let id = SPair("foo".into(), S4); + + let toks = vec![ + Air::PkgOpen(S1), + Air::ExprOpen(ExprOp::Sum, S2), + Air::PkgOpen(S3), + // RECOVERY: We should still be able to complete successfully. + Air::ExprIdent(id), + Air::ExprClose(S5), + // Closes the _original_ package. + Air::PkgClose(S6), + ]; + + assert_eq!( + vec![ + Ok(Parsed::Incomplete), // PkgOpen + Ok(Parsed::Incomplete), // ExprOpen + Err(ParseError::StateError(AsgError::NestedPkgOpen(S3, S1))), + // RECOVERY: Ignore the open and continue. + // Of course, + // this means that any identifiers would be defined in a + // different package than was likely intended, + // but at least we'll be able to keep processing. + Ok(Parsed::Incomplete), // ExprIdent + Ok(Parsed::Incomplete), // ExprClose + Ok(Parsed::Incomplete), // PkgClose + ], + Sut::parse(toks.into_iter()).collect::>(), + ); +} + #[test] fn expr_non_empty_ident_root() { let id_a = SPair("foo".into(), S2); @@ -283,7 +439,7 @@ fn expr_non_empty_ident_root() { Air::ExprClose(S6), ]; - let mut sut = Sut::parse(toks.into_iter()); + let mut sut = parse_as_pkg_body(toks); assert!(sut.all(|x| x.is_ok())); let asg = sut.finalize().unwrap().into_context(); @@ -315,7 +471,7 @@ fn expr_non_empty_bind_only_after() { Air::ExprClose(S5), ]; - let mut sut = Sut::parse(toks.into_iter()); + let mut sut = parse_as_pkg_body(toks); assert!(sut.all(|x| x.is_ok())); let asg = sut.finalize().unwrap().into_context(); @@ -332,7 +488,7 @@ fn expr_non_empty_bind_only_after() { fn expr_dangling_no_subexpr() { let toks = vec![ Air::ExprOpen(ExprOp::Sum, S1), - // No `IdentExpr`, + // No `ExprIdent`, // so this expression is dangling. Air::ExprClose(S2), ]; @@ -342,10 +498,13 @@ fn expr_dangling_no_subexpr() { assert_eq!( vec![ + Ok(Parsed::Incomplete), // PkgOpen Ok(Parsed::Incomplete), - Err(ParseError::StateError(AsgError::DanglingExpr(full_span))) + Err(ParseError::StateError(AsgError::DanglingExpr(full_span))), + // RECOVERY + Ok(Parsed::Incomplete), // PkgClose ], - Sut::parse(toks.into_iter()).collect::>(), + parse_as_pkg_body(toks).collect::>(), ); } @@ -365,12 +524,15 @@ fn expr_dangling_with_subexpr() { assert_eq!( vec![ + Ok(Parsed::Incomplete), // PkgOpen Ok(Parsed::Incomplete), Ok(Parsed::Incomplete), Ok(Parsed::Incomplete), - Err(ParseError::StateError(AsgError::DanglingExpr(full_span))) + Err(ParseError::StateError(AsgError::DanglingExpr(full_span))), + // RECOVERY + Ok(Parsed::Incomplete), // PkgClose ], - Sut::parse(toks.into_iter()).collect::>(), + parse_as_pkg_body(toks).collect::>(), ); } @@ -398,13 +560,16 @@ fn expr_dangling_with_subexpr_ident() { assert_eq!( vec![ + Ok(Parsed::Incomplete), // PkgOpen Ok(Parsed::Incomplete), Ok(Parsed::Incomplete), Ok(Parsed::Incomplete), Ok(Parsed::Incomplete), - Err(ParseError::StateError(AsgError::DanglingExpr(full_span))) + Err(ParseError::StateError(AsgError::DanglingExpr(full_span))), + // RECOVERY + Ok(Parsed::Incomplete), // PkgClose ], - Sut::parse(toks.into_iter()).collect::>(), + parse_as_pkg_body(toks).collect::>(), ); } @@ -431,13 +596,16 @@ fn expr_reachable_subsequent_dangling() { assert_eq!( vec![ + Ok(Parsed::Incomplete), // PkgOpen Ok(Parsed::Incomplete), Ok(Parsed::Incomplete), Ok(Parsed::Incomplete), Ok(Parsed::Incomplete), - Err(ParseError::StateError(AsgError::DanglingExpr(second_span))) + Err(ParseError::StateError(AsgError::DanglingExpr(second_span))), + // RECOVERY + Ok(Parsed::Incomplete), // PkgClose ], - Sut::parse(toks.into_iter()).collect::>(), + parse_as_pkg_body(toks).collect::>(), ); } @@ -458,17 +626,18 @@ fn recovery_expr_reachable_after_dangling() { // The error span should encompass the entire expression. let err_span = S1.merge(S2).unwrap(); - let mut sut = Sut::parse(toks.into_iter()); + let mut sut = parse_as_pkg_body(toks); assert_eq!( vec![ + Ok(Parsed::Incomplete), // PkgOpen Ok(Parsed::Incomplete), Err(ParseError::StateError(AsgError::DanglingExpr(err_span))), - // Recovery allows us to continue at this point with the next - // expression. + // RECOVERY: continue at this point with the next expression. Ok(Parsed::Incomplete), Ok(Parsed::Incomplete), Ok(Parsed::Incomplete), + Ok(Parsed::Incomplete), // PkgClose ], sut.by_ref().collect::>(), ); @@ -505,17 +674,20 @@ fn expr_close_unbalanced() { Air::ExprClose(S5), ]; - let mut sut = Sut::parse(toks.into_iter()); + let mut sut = parse_as_pkg_body(toks); assert_eq!( vec![ + Ok(Parsed::Incomplete), // PkgOpen Err(ParseError::StateError(AsgError::UnbalancedExpr(S1))), - // Recovery should allow us to continue. - Ok(Parsed::Incomplete), // OpenExpr - Ok(Parsed::Incomplete), // IdentExpr - Ok(Parsed::Incomplete), // CloseExpr + // RECOVERY + Ok(Parsed::Incomplete), // ExprOpen + Ok(Parsed::Incomplete), // ExprIdent + Ok(Parsed::Incomplete), // ExprClose // Another error after a successful expression. Err(ParseError::StateError(AsgError::UnbalancedExpr(S5))), + // RECOVERY + Ok(Parsed::Incomplete), // PkgClose ], sut.by_ref().collect::>(), ); @@ -544,21 +716,24 @@ fn expr_bind_to_empty() { Air::ExprIdent(id_noexpr_b), ]; - let mut sut = Sut::parse(toks.into_iter()); + let mut sut = parse_as_pkg_body(toks); assert_eq!( vec![ + Ok(Parsed::Incomplete), // PkgOpen Err(ParseError::StateError(AsgError::InvalidExprBindContext( id_noexpr_a ))), - // Recovery should allow us to continue. - Ok(Parsed::Incomplete), // OpenExpr - Ok(Parsed::Incomplete), // IdentExpr - Ok(Parsed::Incomplete), // CloseExpr + // RECOVERY + Ok(Parsed::Incomplete), // ExprOpen + Ok(Parsed::Incomplete), // ExprIdent + Ok(Parsed::Incomplete), // ExprClose // Another error after a successful expression. Err(ParseError::StateError(AsgError::InvalidExprBindContext( id_noexpr_b ))), + // RECOVERY + Ok(Parsed::Incomplete), // PkgClose ], sut.by_ref().collect::>(), ); @@ -675,20 +850,22 @@ fn expr_redefine_ident() { Air::ExprClose(S5), ]; - let mut sut = Sut::parse(toks.into_iter()); + let mut sut = parse_as_pkg_body(toks); assert_eq!( vec![ - Ok(Parsed::Incomplete), // OpenExpr - Ok(Parsed::Incomplete), // IdentExpr (first) - Ok(Parsed::Incomplete), // OpenExpr + Ok(Parsed::Incomplete), // PkgOpen + Ok(Parsed::Incomplete), // ExprOpen + Ok(Parsed::Incomplete), // ExprIdent (first) + Ok(Parsed::Incomplete), // ExprOpen Err(ParseError::StateError(AsgError::IdentRedefine( id_first, id_dup.span(), ))), // RECOVERY: Ignore the attempt to redefine and continue. - Ok(Parsed::Incomplete), // CloseExpr - Ok(Parsed::Incomplete), // CloseExpr + Ok(Parsed::Incomplete), // ExprClose + Ok(Parsed::Incomplete), // ExprClose + Ok(Parsed::Incomplete), // PkgClose ], sut.by_ref().collect::>(), ); @@ -733,15 +910,16 @@ fn expr_still_dangling_on_redefine() { Air::ExprClose(S10), ]; - let mut sut = Sut::parse(toks.into_iter()); + let mut sut = parse_as_pkg_body(toks); assert_eq!( vec![ - Ok(Parsed::Incomplete), // OpenExpr - Ok(Parsed::Incomplete), // IdentExpr (first) - Ok(Parsed::Incomplete), // CloseExpr + Ok(Parsed::Incomplete), // PkgOpen + Ok(Parsed::Incomplete), // ExprOpen + Ok(Parsed::Incomplete), // ExprIdent (first) + Ok(Parsed::Incomplete), // ExprClose // Beginning of second expression - Ok(Parsed::Incomplete), // OpenExpr + Ok(Parsed::Incomplete), // ExprOpen Err(ParseError::StateError(AsgError::IdentRedefine( id_first, id_dup.span(), @@ -755,15 +933,16 @@ fn expr_still_dangling_on_redefine() { // RECOVERY: But we'll continue onto one final expression, // which we will fail to define but then subsequently define // successfully. - Ok(Parsed::Incomplete), // OpenExpr + Ok(Parsed::Incomplete), // ExprOpen Err(ParseError::StateError(AsgError::IdentRedefine( id_first, id_dup2.span(), ))), // RECOVERY: Despite the initial failure, // we can now re-attempt to bind with a unique id. - Ok(Parsed::Incomplete), // IdentExpr (second) - Ok(Parsed::Incomplete), // CloseExpr + Ok(Parsed::Incomplete), // ExprIdent (second) + Ok(Parsed::Incomplete), // ExprClose + Ok(Parsed::Incomplete), // PkgClose ], sut.by_ref().collect::>(), ); @@ -842,17 +1021,19 @@ fn expr_ref_outside_of_expr_context() { Air::ExprClose(S3), ]; - let mut sut = Sut::parse(toks.into_iter()); + let mut sut = parse_as_pkg_body(toks); assert_eq!( vec![ + Ok(Parsed::Incomplete), // PkgOpen Err(ParseError::StateError(AsgError::InvalidExprRefContext( id_foo ))), // RECOVERY: Proceed as normal - Ok(Parsed::Incomplete), // OpenExpr - Ok(Parsed::Incomplete), // IdentExpr - Ok(Parsed::Incomplete), // CloseExpr + Ok(Parsed::Incomplete), // ExprOpen + Ok(Parsed::Incomplete), // ExprIdent + Ok(Parsed::Incomplete), // ExprClose + Ok(Parsed::Incomplete), // PkgClose ], sut.by_ref().collect::>(), ); @@ -869,7 +1050,7 @@ fn asg_from_toks>(toks: I) -> Asg where I::IntoIter: Debug, { - let mut sut = Sut::parse(toks.into_iter()); + let mut sut = parse_as_pkg_body(toks); assert!(sut.all(|x| x.is_ok())); sut.finalize().unwrap().into_context() } diff --git a/tamer/src/asg/error.rs b/tamer/src/asg/error.rs index 012494a4..e9df3e4a 100644 --- a/tamer/src/asg/error.rs +++ b/tamer/src/asg/error.rs @@ -51,6 +51,25 @@ pub enum AsgError { /// whereas _declaring_ an identifier provides metadata about it. IdentRedefine(SPair, Span), + /// Attempted to open a package while defining another package. + /// + /// Packages cannot be nested. + /// The first span represents the location of the second package open, + /// and the second span represents the location of the package already + /// being defined. + NestedPkgOpen(Span, Span), + + /// Attempted to close a package when not in a package context. + /// + /// This could mean that a package close was attempted while parsing + /// some other object + /// (e.g. an expression), + /// or that there is no open package to close. + InvalidPkgCloseContext(Span), + + /// Attempted to open an expression in an invalid context. + InvalidExprContext(Span), + /// An expresion is not reachable by any other expression or /// identifier. /// @@ -100,6 +119,11 @@ impl Display for AsgError { IdentRedefine(spair, _) => { write!(f, "cannot redefine {}", TtQuote::wrap(spair)) } + NestedPkgOpen(_, _) => write!(f, "cannot nest packages"), + InvalidPkgCloseContext(_) => { + write!(f, "invalid context for package close",) + } + InvalidExprContext(_) => write!(f, "invalid expression context"), DanglingExpr(_) => write!( f, "dangling expression (anonymous expression has no parent)" @@ -119,20 +143,7 @@ impl Display for AsgError { } } -impl Error for AsgError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - use AsgError::*; - - match self { - IdentTransition(err) => err.source(), - IdentRedefine(_, _) - | DanglingExpr(_) - | UnbalancedExpr(_) - | InvalidExprBindContext(_) - | InvalidExprRefContext(_) => None, - } - } -} +impl Error for AsgError {} impl From for AsgError { fn from(err: TransitionError) -> Self { @@ -169,6 +180,27 @@ impl Diagnostic for AsgError { .help(" defined and its definition cannot be changed."), ], + NestedPkgOpen(second, first) => vec![ + first.note("this package is still being defined"), + second.error("attempted to open another package here"), + second.help( + "close the package to complete its definition before \ + attempting to open another", + ), + ], + + InvalidPkgCloseContext(span) => vec![ + span.error("package close was not expected here"), + span.help( + "a package must be closed at the same level of nesting \ + that it was opened", + ), + ], + + InvalidExprContext(span) => { + vec![span.error("an expression is not allowed here")] + } + DanglingExpr(span) => vec![ span.error( "this expression is unreachable and its value \ diff --git a/tamer/src/asg/graph/object.rs b/tamer/src/asg/graph/object.rs index b3426137..d65d1728 100644 --- a/tamer/src/asg/graph/object.rs +++ b/tamer/src/asg/graph/object.rs @@ -119,9 +119,11 @@ use std::{convert::Infallible, fmt::Display, marker::PhantomData}; pub mod expr; pub mod ident; +pub mod pkg; -use expr::Expr; -use ident::Ident; +pub use expr::Expr; +pub use ident::Ident; +pub use pkg::Pkg; /// An object on the ASG. /// @@ -136,6 +138,9 @@ pub enum Object { /// There should be only one object of this kind. Root, + /// A package of identifiers. + Pkg(Pkg), + /// Identifier (a named object). Ident(Ident), @@ -157,6 +162,7 @@ pub enum Object { /// TODO: `pub(super)` when the graph can be better encapsulated. #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum ObjectRelTy { + Pkg, Ident, Expr, } @@ -165,6 +171,7 @@ impl Display for Object { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { Self::Root => write!(f, "root ASG node"), + Self::Pkg(pkg) => Display::fmt(pkg, f), Self::Ident(ident) => Display::fmt(ident, f), Self::Expr(expr) => Display::fmt(expr, f), } @@ -175,6 +182,7 @@ impl Object { pub fn span(&self) -> Span { match self { Self::Root => UNKNOWN_SPAN, + Self::Pkg(pkg) => pkg.span(), Self::Ident(ident) => ident.span(), Self::Expr(expr) => expr.span(), } @@ -232,6 +240,18 @@ impl Object { } } +impl From<&Object> for Span { + fn from(val: &Object) -> Self { + val.span() + } +} + +impl From for Object { + fn from(pkg: Pkg) -> Self { + Self::Pkg(pkg) + } +} + impl From for Object { fn from(ident: Ident) -> Self { Self::Ident(ident) @@ -244,6 +264,17 @@ impl From for Object { } } +impl From for Pkg { + /// Narrow an object into an [`Ident`], + /// panicing if the object is not of that type. + fn from(val: Object) -> Self { + match val { + Object::Pkg(pkg) => pkg, + _ => val.narrowing_panic("a package"), + } + } +} + impl From for Ident { /// Narrow an object into an [`Ident`], /// panicing if the object is not of that type. @@ -272,6 +303,15 @@ impl AsRef for Object { } } +impl AsRef for Object { + fn as_ref(&self) -> &Pkg { + match self { + Object::Pkg(ref pkg) => pkg, + _ => self.narrowing_panic("a package"), + } + } +} + impl AsRef for Object { fn as_ref(&self) -> &Ident { match self { @@ -343,6 +383,21 @@ impl ObjectIndex { Self(index, span.into(), PhantomData::default()) } + /// The source location from which the request for the associated object + /// was derived. + /// + /// The span _does not necessarily represent_ the span of the target + /// [`Object`]. + /// If the object is being created, + /// then it may, + /// but it otherwise represents the location of whatever is + /// _requesting_ the object. + pub fn span(&self) -> Span { + match self { + Self(_, span, _) => *span, + } + } + /// Add an edge from `self` to `to_oi` on the provided [`Asg`]. /// /// An edge can only be added if ontologically valid; @@ -670,6 +725,7 @@ impl ObjectRelatable for Ident { match ty { ObjectRelTy::Ident => Some(IdentRel::Ident(oi.must_narrow_into())), ObjectRelTy::Expr => Some(IdentRel::Expr(oi.must_narrow_into())), + ObjectRelTy::Pkg => None, } } } @@ -721,6 +777,7 @@ impl ObjectRelatable for Expr { match ty { ObjectRelTy::Ident => Some(ExprRel::Ident(oi.must_narrow_into())), ObjectRelTy::Expr => Some(ExprRel::Expr(oi.must_narrow_into())), + ObjectRelTy::Pkg => None, } } } diff --git a/tamer/src/asg/graph/object/pkg.rs b/tamer/src/asg/graph/object/pkg.rs new file mode 100644 index 00000000..bf64a755 --- /dev/null +++ b/tamer/src/asg/graph/object/pkg.rs @@ -0,0 +1,45 @@ +// Packages represented on ASG +// +// 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 . + +//! Package object on the ASG. + +use std::fmt::Display; + +use crate::span::Span; + +#[derive(Debug, PartialEq, Eq)] +pub struct Pkg(Span); + +impl Pkg { + pub fn new>(span: S) -> Self { + Self(span.into()) + } + + pub fn span(&self) -> Span { + match self { + Self(span) => *span, + } + } +} + +impl Display for Pkg { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "package") + } +} diff --git a/tamer/src/ld/xmle/lower.rs b/tamer/src/ld/xmle/lower.rs index e2125251..f7c03108 100644 --- a/tamer/src/ld/xmle/lower.rs +++ b/tamer/src/ld/xmle/lower.rs @@ -72,13 +72,15 @@ where Object::Root => (), Object::Ident(ident) => dest.push(ident)?, - Object::Expr(expr) => diagnostic_unreachable!( - expr.internal_error( - "this expression should not be present on the graph" + obj @ (Object::Pkg(_) | Object::Expr(_)) => { + diagnostic_unreachable!( + obj.internal_error( + "this object should not be present on the graph" + ) + .into(), + "linker graph should not contain {obj}", ) - .into(), - "linker graph should not contain expressions", - ), + } } } diff --git a/tamer/src/nir.rs b/tamer/src/nir.rs index 15a9cffc..e9545148 100644 --- a/tamer/src/nir.rs +++ b/tamer/src/nir.rs @@ -93,7 +93,7 @@ pub enum Nir { Open(NirEntity, Span), /// Finish definition of a [`NirEntity`] atop of the stack and pop it. - Close(Span), + Close(NirEntity, Span), /// Bind the given name as an identifier for the entity atop of the /// stack. @@ -133,7 +133,7 @@ impl Nir { Todo => None, TodoAttr(spair) => Some(spair.symbol()), - Open(_, _) | Close(_) => None, + Open(_, _) | Close(_, _) => None, BindIdent(spair) | Ref(spair) | Desc(spair) | Text(spair) => { Some(spair.symbol()) @@ -164,7 +164,7 @@ impl Functor for Nir { Todo => self, TodoAttr(spair) => TodoAttr(spair.map(f)), - Open(_, _) | Close(_) => self, + Open(_, _) | Close(_, _) => self, BindIdent(spair) => BindIdent(spair.map(f)), Ref(spair) => Ref(spair.map(f)), @@ -177,6 +177,9 @@ impl Functor for Nir { /// An object upon which other [`Nir`] tokens act. #[derive(Debug, PartialEq, Eq)] pub enum NirEntity { + /// Package of identifiers. + Package, + /// Rate (verb) block, /// representing a calculation with a scalar result. Rate, @@ -189,6 +192,10 @@ impl NirEntity { pub fn open>(self, span: S) -> Nir { Nir::Open(self, span.into()) } + + pub fn close>(self, span: S) -> Nir { + Nir::Close(self, span.into()) + } } impl Display for NirEntity { @@ -196,6 +203,7 @@ impl Display for NirEntity { use NirEntity::*; match self { + Package => write!(f, "package"), Rate => write!(f, "rate block"), TplParam => write!(f, "template param (metavariable)"), } @@ -220,7 +228,7 @@ impl Token for Nir { TodoAttr(spair) => spair.span(), Open(_, span) => *span, - Close(span) => *span, + Close(_, span) => *span, BindIdent(spair) | Ref(spair) | Desc(spair) | Text(spair) => { spair.span() @@ -240,7 +248,7 @@ impl Display for Nir { TodoAttr(spair) => write!(f, "TODO Attr {spair}"), Open(entity, _) => write!(f, "open {entity} entity"), - Close(_) => write!(f, "close entity"), + Close(entity, _) => write!(f, "close {entity} entity"), BindIdent(spair) => { write!(f, "bind identifier {}", TtQuote::wrap(spair)) } diff --git a/tamer/src/nir/air.rs b/tamer/src/nir/air.rs index 4de25c22..43f60627 100644 --- a/tamer/src/nir/air.rs +++ b/tamer/src/nir/air.rs @@ -66,11 +66,19 @@ impl ParseState for NirToAir { #[allow(unreachable_code)] // due to wip-nir-to-air match (self, tok) { + (Ready, Nir::Open(NirEntity::Package, span)) => { + Transition(Ready).ok(Air::PkgOpen(span)) + } + + (Ready, Nir::Close(NirEntity::Package, span)) => { + Transition(Ready).ok(Air::PkgClose(span)) + } + (Ready, Nir::Open(NirEntity::Rate, span)) => { Transition(Ready).ok(Air::ExprOpen(ExprOp::Sum, span)) } - (Ready, Nir::Close(span)) => { + (Ready, Nir::Close(NirEntity::Rate, span)) => { Transition(Ready).ok(Air::ExprClose(span)) } diff --git a/tamer/src/nir/air/test.rs b/tamer/src/nir/air/test.rs index 92bc793b..d4e1159d 100644 --- a/tamer/src/nir/air/test.rs +++ b/tamer/src/nir/air/test.rs @@ -24,6 +24,19 @@ type Sut = NirToAir; use Parsed::Object as O; +#[test] +fn package_to_pkg() { + let toks = vec![ + Nir::Open(NirEntity::Package, S1), + Nir::Close(NirEntity::Package, S2), + ]; + + assert_eq!( + Ok(vec![O(Air::PkgOpen(S1)), O(Air::PkgClose(S2)),]), + Sut::parse(toks.into_iter()).collect(), + ); +} + #[test] fn rate_to_sum_expr() { let id = SPair("foo".into(), S2); @@ -31,7 +44,7 @@ fn rate_to_sum_expr() { let toks = vec![ Nir::Open(NirEntity::Rate, S1), Nir::BindIdent(id), - Nir::Close(S3), + Nir::Close(NirEntity::Rate, S3), ]; assert_eq!( diff --git a/tamer/src/nir/interp.rs b/tamer/src/nir/interp.rs index 5baceb6d..284a93d6 100644 --- a/tamer/src/nir/interp.rs +++ b/tamer/src/nir/interp.rs @@ -315,7 +315,7 @@ impl ParseState for InterpState { // symbol that we've been fed // (the specification string). Transition(FinishSym(s, gen_param)) - .ok(Nir::Close(span)) + .ok(Nir::Close(NirEntity::TplParam, span)) .with_lookahead(tok) }; } diff --git a/tamer/src/nir/interp/test.rs b/tamer/src/nir/interp/test.rs index ce774bbc..1dd74c01 100644 --- a/tamer/src/nir/interp/test.rs +++ b/tamer/src/nir/interp/test.rs @@ -55,10 +55,10 @@ fn does_not_desugar_literal_only() { // ...not that it could. #[test] fn does_not_desugar_tokens_without_symbols() { - let toks = vec![Nir::Close(S1)]; + let toks = vec![Nir::Close(NirEntity::Rate, S1)]; assert_eq!( - Ok(vec![Object(Nir::Close(S1))]), + Ok(vec![Object(Nir::Close(NirEntity::Rate, S1))]), Sut::parse(toks.into_iter()).collect(), ); } @@ -132,7 +132,7 @@ fn desugars_literal_with_ending_var() { Object(Nir::Ref(SPair("@bar@".into(), c))), // This is an object generated from user input, so the closing // span has to identify what were generated from. - Object(Nir::Close(a)), + Object(Nir::Close(NirEntity::TplParam, a)), // Finally, // we replace the original provided attribute // (the interpolation specification) @@ -172,7 +172,7 @@ fn desugars_var_with_ending_literal() { Ok(vec![ Object(Nir::Ref(SPair("@foo@".into(), b))), Object(Nir::Text(SPair("bar".into(), c))), - Object(Nir::Close(a)), + Object(Nir::Close(NirEntity::TplParam, a)), Object(Nir::Ref(SPair(expect_name, a))), ]), sut.collect(), @@ -216,7 +216,7 @@ fn desugars_many_vars_and_literals() { // offsets. Object(Nir::Text(SPair("baz".into(), d))), Object(Nir::Ref(SPair("@quux@".into(), e))), - Object(Nir::Close(a)), + Object(Nir::Close(NirEntity::TplParam, a)), Object(Nir::Ref(SPair(expect_name, a))), ]), sut.collect(), @@ -266,7 +266,7 @@ fn proper_multibyte_handling() { // offsets. Object(Nir::Text(SPair("βaζ".into(), d))), Object(Nir::Ref(SPair("@qμuχ@".into(), e))), - Object(Nir::Close(a)), + Object(Nir::Close(NirEntity::TplParam, a)), Object(Nir::Ref(SPair(expect_name, a))), ]), sut.collect(), @@ -301,7 +301,7 @@ fn desugars_adjacent_interpolated_vars() { Object(Nir::Ref(SPair("@foo@".into(), b))), Object(Nir::Ref(SPair("@bar@".into(), c))), Object(Nir::Ref(SPair("@baz@".into(), d))), - Object(Nir::Close(a)), + Object(Nir::Close(NirEntity::TplParam, a)), Object(Nir::Ref(SPair(expect_name, a))), ]), sut.collect(), @@ -347,7 +347,7 @@ fn error_missing_closing_interp_delim() { // Having omitted the above token, // we're able to proceed as if the user didn't provide it at // all. - Ok(Object(Nir::Close(a))), + Ok(Object(Nir::Close(NirEntity::TplParam, a))), Ok(Object(Nir::Ref(SPair(expect_name, a)))), ], sut.collect::>>(), @@ -395,7 +395,7 @@ fn error_nested_delim() { // (end of the specification string) // and ignore everything that follows rather than // potentially interpret it in confusing ways. - Ok(Object(Nir::Close(a))), + Ok(Object(Nir::Close(NirEntity::TplParam, a))), Ok(Object(Nir::Ref(SPair(expect_name, a)))), ], sut.collect::>>(), @@ -438,7 +438,7 @@ fn error_empty_interp() { // It wouldn't have had any effect anyway, // being empty. Ok(Object(Nir::Text(SPair("cow".into(), d)))), - Ok(Object(Nir::Close(a))), + Ok(Object(Nir::Close(NirEntity::TplParam, a))), Ok(Object(Nir::Ref(SPair(expect_name, a)))), ], sut.collect::>>(), @@ -477,7 +477,7 @@ fn error_close_before_open() { // was supposed to be a literal or a param. // Just bail out; // maybe in the future we can do something better. - Ok(Object(Nir::Close(a))), + Ok(Object(Nir::Close(NirEntity::TplParam, a))), Ok(Object(Nir::Ref(SPair(expect_name, a)))), ], sut.collect::>>(), diff --git a/tamer/src/nir/parse.rs b/tamer/src/nir/parse.rs index 6410c9b8..418c13bb 100644 --- a/tamer/src/nir/parse.rs +++ b/tamer/src/nir/parse.rs @@ -225,7 +225,7 @@ ele_parse! { /// packages. /// See [`PkgTypeStmt`] for more information on the distinction between /// different package types. - PackageStmt := QN_PACKAGE { + PackageStmt := QN_PACKAGE(_, ospan) { @ { QN_XMLNS => TodoAttr, QN_XMLNS_C => TodoAttr, @@ -247,7 +247,8 @@ ele_parse! { // TODO: Can this go away now? QN_NAME => TodoAttr, - } => Todo, + } => NirEntity::Package.open(ospan), + /(cspan) => NirEntity::Package.close(cspan), ImportStmt, PkgBodyStmt, @@ -424,7 +425,7 @@ ele_parse! { // This is a kludge. QN_LOCAL => TodoAttr, } => NirEntity::Rate.open(ospan), - /(cspan) => Close(cspan.span()), + /(cspan) => NirEntity::Rate.close(cspan), CalcExpr, };