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
main
Mike Gerwitz 2023-01-30 16:51:24 -05:00
parent 2f08985111
commit 39d093525c
12 changed files with 510 additions and 108 deletions

View File

@ -18,10 +18,10 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
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<Dormant>),
/// A package is being defined.
PkgDfn(ObjectIndex<Pkg>, ExprStack<Dormant>),
/// Building an expression.
///
/// Expressions may be nested arbitrarily deeply.
BuildingExpr(ExprStack<Active>, ObjectIndex<Expr>),
BuildingExpr(ObjectIndex<Pkg>, ExprStack<Active>, ObjectIndex<Expr>),
}
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))
}

View File

@ -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::<Vec<_>>(),
);
}
#[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::<Vec<_>>(),
);
}
/// Parse using [`Sut`] when the test does not care about the outer package.
fn parse_as_pkg_body<I: IntoIterator<Item = Air>>(
toks: I,
) -> Parser<Sut, impl Iterator<Item = Air> + Debug>
where
<I as IntoIterator>::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::<Vec<_>>(),
);
}
// 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::<Vec<_>>(),
);
}
#[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::<Vec<_>>(),
);
}
#[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::<Vec<_>>(),
parse_as_pkg_body(toks).collect::<Vec<_>>(),
);
}
@ -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::<Vec<_>>(),
parse_as_pkg_body(toks).collect::<Vec<_>>(),
);
}
@ -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::<Vec<_>>(),
parse_as_pkg_body(toks).collect::<Vec<_>>(),
);
}
@ -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::<Vec<_>>(),
parse_as_pkg_body(toks).collect::<Vec<_>>(),
);
}
@ -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::<Vec<_>>(),
);
@ -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::<Vec<_>>(),
);
@ -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::<Vec<_>>(),
);
@ -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::<Vec<_>>(),
);
@ -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::<Vec<_>>(),
);
@ -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::<Vec<_>>(),
);
@ -869,7 +1050,7 @@ fn asg_from_toks<I: IntoIterator<Item = Air>>(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()
}

View File

@ -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<TransitionError> 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 \

View File

@ -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<Pkg> for Object {
fn from(pkg: Pkg) -> Self {
Self::Pkg(pkg)
}
}
impl From<Ident> for Object {
fn from(ident: Ident) -> Self {
Self::Ident(ident)
@ -244,6 +264,17 @@ impl From<Expr> for Object {
}
}
impl From<Object> 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<Object> for Ident {
/// Narrow an object into an [`Ident`],
/// panicing if the object is not of that type.
@ -272,6 +303,15 @@ impl AsRef<Object> for Object {
}
}
impl AsRef<Pkg> for Object {
fn as_ref(&self) -> &Pkg {
match self {
Object::Pkg(ref pkg) => pkg,
_ => self.narrowing_panic("a package"),
}
}
}
impl AsRef<Ident> for Object {
fn as_ref(&self) -> &Ident {
match self {
@ -343,6 +383,21 @@ impl<O: ObjectKind> ObjectIndex<O> {
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,
}
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
//! 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<S: Into<Span>>(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")
}
}

View File

@ -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",
),
}
}
}

View File

@ -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<SymbolId> 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<SymbolId> 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<S: Into<Span>>(self, span: S) -> Nir {
Nir::Open(self, span.into())
}
pub fn close<S: Into<Span>>(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))
}

View File

@ -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))
}

View File

@ -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!(

View File

@ -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)
};
}

View File

@ -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::<Vec<ParsedResult<Sut>>>(),
@ -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::<Vec<ParsedResult<Sut>>>(),
@ -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::<Vec<ParsedResult<Sut>>>(),
@ -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::<Vec<ParsedResult<Sut>>>(),

View File

@ -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,
};