tamer: Refactor asg_builder into obj::xmlo::lower and asg::air
This finally uses `parse` all the way up to aggregation into the ASG, as can
be seen by the mess in `poc`. This will be further simplified---I just need
to get this committed so that I can mentally get it off my plate. I've been
separating this commit into smaller commits, but there's a point where it's
just not worth the effort anymore. I don't like making large changes such
as this one.
There is still work to do here. First, it's worth re-mentioning that
`poc` means "proof-of-concept", and represents things that still need a
proper home/abstraction.
Secondly, `poc` is retrieving the context of two parsers---`LowerContext`
and `Asg`. The latter is desirable, since it's the final aggregation point,
but the former needs to be eliminated; in particular, packages need to be
worked into the ASG so that `found` can be removed.
Recursively loading `xmlo` files still happens in `poc`, but the compiler
will need this as well. Once packages are on the ASG, along with their
state, that responsibility can be generalized as well.
That will then simplify lowering even further, to the point where hopefully
everything has the same shape (once final aggregation has an abstraction),
after which we can then create a final abstraction to concisely stitch
everything together. Right now, Rust isn't able to infer `S` for
`Lower<S, LS>`, which is unfortunate, but we'll be able to help it along
with a more explicit abstraction.
DEV-11864
2022-05-27 13:51:29 -04:00
|
|
|
// ASG IR
|
|
|
|
//
|
|
|
|
// Copyright (C) 2014-2022 Ryan Specialty Group, 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/>.
|
|
|
|
|
|
|
|
use super::{Asg, AsgError, FragmentText, IdentKind, Source};
|
|
|
|
use crate::{
|
|
|
|
parse::{self, ParseState, Token, Transition, Transitionable},
|
|
|
|
span::UNKNOWN_SPAN,
|
|
|
|
sym::SymbolId,
|
|
|
|
};
|
|
|
|
use std::fmt::{Debug, Display};
|
|
|
|
|
|
|
|
///! Intermediate representation for construction of the
|
|
|
|
///! [abstract semantic graph (ASG)](super) (AIR).
|
|
|
|
///!
|
|
|
|
///! AIR serves as an abstraction layer between higher-level parsers and the
|
|
|
|
///! aggregate ASG.
|
|
|
|
///! It allows parsers to operate as a raw stream of data without having to
|
|
|
|
///! worry about ownership of or references to the ASG,
|
|
|
|
///! and allows for multiple such parsers to be joined.
|
|
|
|
///!
|
|
|
|
///! AIR is _not_ intended to replace the API of the ASG---it
|
|
|
|
///! is intended as a termination point for the parsing pipeline,
|
|
|
|
///! and as such implements a subset of the ASG's API that is suitable
|
|
|
|
///! for aggregating raw data from source and object files.
|
|
|
|
///! Given that it does so little and is so close to the [`Asg`] API,
|
|
|
|
///! one might say that the abstraction is as light as air,
|
|
|
|
///! but that would surely result in face-palming and so we're not going
|
|
|
|
///! air such cringeworthy dad jokes here.
|
|
|
|
|
|
|
|
pub type IdentSym = SymbolId;
|
|
|
|
pub type DepSym = SymbolId;
|
|
|
|
|
|
|
|
/// AIR token.
|
|
|
|
///
|
|
|
|
/// These tokens mimic a public API for the ASG,
|
|
|
|
/// and allow parsers to be completely decoupled from the ASG object that
|
|
|
|
/// they will eventually aggregate data into.
|
|
|
|
///
|
|
|
|
/// This IR is not intended to perform sophisticated manipulation of the
|
|
|
|
/// ASG---it
|
|
|
|
/// is intended to perform initial aggregation as part of a parsing
|
|
|
|
/// phase,
|
|
|
|
/// populating the ASG with the raw data that that will be
|
|
|
|
/// subsequently analyzed and rewritten.
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
|
|
pub enum AirToken {
|
|
|
|
/// Declare a resolved identifier.
|
|
|
|
IdentDecl(IdentSym, IdentKind, Source),
|
|
|
|
/// Declare an external identifier that must be resolved before linking.
|
|
|
|
IdentExternDecl(IdentSym, IdentKind, Source),
|
|
|
|
/// Declare that an identifier depends on another for its definition.
|
|
|
|
IdentDep(IdentSym, DepSym),
|
|
|
|
/// Associate a code fragment with an identifier.
|
|
|
|
IdentFragment(IdentSym, FragmentText),
|
|
|
|
/// Root an identifier.
|
|
|
|
IdentRoot(IdentSym),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Token for AirToken {
|
tamer: parser::Parser: cfg(test) tracing
This produces useful parse traces that are output as part of a failing test
case. The parser generator macros can be a bit confusing to deal with when
things go wrong, so this helps to clarify matters.
This is _not_ intended to be machine-readable, but it does show that it
would be possible to generate machine-readable output to visualize the
entire lowering pipeline. Perhaps something for the future.
I left these inline in Parser::feed_tok because they help to elucidate what
is going on, just by reading what the trace would output---that is, it helps
to make the method more self-documenting, albeit a tad bit more
verbose. But with that said, it should probably be extracted at some point;
I don't want this to set a precedent where composition is feasible.
Here's an example from test cases:
[Parser::feed_tok] (input IR: XIRF)
| ==> Parser before tok is parsing attributes for `package`.
| | Attrs_(SutAttrsState_ { ___ctx: (QName(None, LocalPart(NCName(SymbolId(46 "package")))), OpenSpan(Span { len: 0, offset: 0, ctx: Context(SymbolId(1 "#!DUMMY")) }, 10)), ___done: false })
|
| ==> XIRF tok: `<unexpected>`
| | Open(QName(None, LocalPart(NCName(SymbolId(82 "unexpected")))), OpenSpan(Span { len: 0, offset: 1, ctx: Context(SymbolId(1 "#!DUMMY")) }, 10), Depth(1))
|
| ==> Parser after tok is expecting opening tag `<classify>`.
| | ChildA(Expecting_)
| | Lookahead: Some(Lookahead(Open(QName(None, LocalPart(NCName(SymbolId(82 "unexpected")))), OpenSpan(Span { len: 0, offset: 1, ctx: Context(SymbolId(1 "#!DUMMY")) }, 10), Depth(1))))
= note: this trace was output as a debugging aid because `cfg(test)`.
[Parser::feed_tok] (input IR: XIRF)
| ==> Parser before tok is expecting opening tag `<classify>`.
| | ChildA(Expecting_)
|
| ==> XIRF tok: `<unexpected>`
| | Open(QName(None, LocalPart(NCName(SymbolId(82 "unexpected")))), OpenSpan(Span { len: 0, offset: 1, ctx: Context(SymbolId(1 "#!DUMMY")) }, 10), Depth(1))
|
| ==> Parser after tok is attempting to recover by ignoring element with unexpected name `unexpected` (expected `classify`).
| | ChildA(RecoverEleIgnore_(QName(None, LocalPart(NCName(SymbolId(82 "unexpected")))), OpenSpan(Span { len: 0, offset: 1, ctx: Context(SymbolId(1 "#!DUMMY")) }, 10), Depth(1)))
| | Lookahead: None
= note: this trace was output as a debugging aid because `cfg(test)`.
DEV-7145
2022-07-18 14:32:34 -04:00
|
|
|
fn ir_name() -> &'static str {
|
|
|
|
"AIR"
|
|
|
|
}
|
|
|
|
|
tamer: Refactor asg_builder into obj::xmlo::lower and asg::air
This finally uses `parse` all the way up to aggregation into the ASG, as can
be seen by the mess in `poc`. This will be further simplified---I just need
to get this committed so that I can mentally get it off my plate. I've been
separating this commit into smaller commits, but there's a point where it's
just not worth the effort anymore. I don't like making large changes such
as this one.
There is still work to do here. First, it's worth re-mentioning that
`poc` means "proof-of-concept", and represents things that still need a
proper home/abstraction.
Secondly, `poc` is retrieving the context of two parsers---`LowerContext`
and `Asg`. The latter is desirable, since it's the final aggregation point,
but the former needs to be eliminated; in particular, packages need to be
worked into the ASG so that `found` can be removed.
Recursively loading `xmlo` files still happens in `poc`, but the compiler
will need this as well. Once packages are on the ASG, along with their
state, that responsibility can be generalized as well.
That will then simplify lowering even further, to the point where hopefully
everything has the same shape (once final aggregation has an abstraction),
after which we can then create a final abstraction to concisely stitch
everything together. Right now, Rust isn't able to infer `S` for
`Lower<S, LS>`, which is unfortunate, but we'll be able to help it along
with a more explicit abstraction.
DEV-11864
2022-05-27 13:51:29 -04:00
|
|
|
fn span(&self) -> crate::span::Span {
|
|
|
|
// TODO: This can be provided once the xmlo files store source
|
|
|
|
// locations for symbols.
|
|
|
|
UNKNOWN_SPAN
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl parse::Object for AirToken {}
|
|
|
|
|
|
|
|
impl Display for AirToken {
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
|
|
use AirToken::*;
|
|
|
|
|
|
|
|
match self {
|
|
|
|
IdentDecl(sym, ..) => {
|
|
|
|
write!(f, "declaration of identifier `{sym}`")
|
|
|
|
}
|
|
|
|
IdentExternDecl(sym, ..) => {
|
|
|
|
write!(f, "declaration of external identifier `{sym}`")
|
|
|
|
}
|
|
|
|
IdentDep(isym, dsym) => write!(
|
|
|
|
f,
|
|
|
|
"declaration of identifier dependency `{isym} -> {dsym}`"
|
|
|
|
),
|
|
|
|
IdentFragment(sym, ..) => {
|
|
|
|
write!(f, "identifier `{sym}` fragment text")
|
|
|
|
}
|
|
|
|
IdentRoot(sym) => write!(f, "rooting of identifier `{sym}`"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// AIR parser state.
|
|
|
|
///
|
|
|
|
/// This currently has no parsing state;
|
|
|
|
/// all state is stored on the ASG itself,
|
|
|
|
/// which is the parsing context.
|
|
|
|
#[derive(Debug, PartialEq, Eq, Default)]
|
2022-06-02 13:26:46 -04:00
|
|
|
pub enum AirAggregate {
|
tamer: Refactor asg_builder into obj::xmlo::lower and asg::air
This finally uses `parse` all the way up to aggregation into the ASG, as can
be seen by the mess in `poc`. This will be further simplified---I just need
to get this committed so that I can mentally get it off my plate. I've been
separating this commit into smaller commits, but there's a point where it's
just not worth the effort anymore. I don't like making large changes such
as this one.
There is still work to do here. First, it's worth re-mentioning that
`poc` means "proof-of-concept", and represents things that still need a
proper home/abstraction.
Secondly, `poc` is retrieving the context of two parsers---`LowerContext`
and `Asg`. The latter is desirable, since it's the final aggregation point,
but the former needs to be eliminated; in particular, packages need to be
worked into the ASG so that `found` can be removed.
Recursively loading `xmlo` files still happens in `poc`, but the compiler
will need this as well. Once packages are on the ASG, along with their
state, that responsibility can be generalized as well.
That will then simplify lowering even further, to the point where hopefully
everything has the same shape (once final aggregation has an abstraction),
after which we can then create a final abstraction to concisely stitch
everything together. Right now, Rust isn't able to infer `S` for
`Lower<S, LS>`, which is unfortunate, but we'll be able to help it along
with a more explicit abstraction.
DEV-11864
2022-05-27 13:51:29 -04:00
|
|
|
#[default]
|
|
|
|
Empty,
|
|
|
|
}
|
|
|
|
|
2022-06-02 13:26:46 -04:00
|
|
|
impl ParseState for AirAggregate {
|
tamer: Refactor asg_builder into obj::xmlo::lower and asg::air
This finally uses `parse` all the way up to aggregation into the ASG, as can
be seen by the mess in `poc`. This will be further simplified---I just need
to get this committed so that I can mentally get it off my plate. I've been
separating this commit into smaller commits, but there's a point where it's
just not worth the effort anymore. I don't like making large changes such
as this one.
There is still work to do here. First, it's worth re-mentioning that
`poc` means "proof-of-concept", and represents things that still need a
proper home/abstraction.
Secondly, `poc` is retrieving the context of two parsers---`LowerContext`
and `Asg`. The latter is desirable, since it's the final aggregation point,
but the former needs to be eliminated; in particular, packages need to be
worked into the ASG so that `found` can be removed.
Recursively loading `xmlo` files still happens in `poc`, but the compiler
will need this as well. Once packages are on the ASG, along with their
state, that responsibility can be generalized as well.
That will then simplify lowering even further, to the point where hopefully
everything has the same shape (once final aggregation has an abstraction),
after which we can then create a final abstraction to concisely stitch
everything together. Right now, Rust isn't able to infer `S` for
`Lower<S, LS>`, which is unfortunate, but we'll be able to help it along
with a more explicit abstraction.
DEV-11864
2022-05-27 13:51:29 -04:00
|
|
|
type Token = AirToken;
|
|
|
|
type Object = ();
|
|
|
|
type Error = AsgError;
|
|
|
|
|
|
|
|
/// Destination [`Asg`] that this parser lowers into.
|
|
|
|
///
|
|
|
|
/// This ASG will be yielded by [`parse::Parser::finalize`].
|
|
|
|
type Context = Asg;
|
|
|
|
|
|
|
|
fn parse_token(
|
|
|
|
self,
|
|
|
|
tok: Self::Token,
|
|
|
|
asg: &mut Self::Context,
|
|
|
|
) -> crate::parse::TransitionResult<Self> {
|
2022-06-02 13:26:46 -04:00
|
|
|
use AirAggregate::*;
|
tamer: Refactor asg_builder into obj::xmlo::lower and asg::air
This finally uses `parse` all the way up to aggregation into the ASG, as can
be seen by the mess in `poc`. This will be further simplified---I just need
to get this committed so that I can mentally get it off my plate. I've been
separating this commit into smaller commits, but there's a point where it's
just not worth the effort anymore. I don't like making large changes such
as this one.
There is still work to do here. First, it's worth re-mentioning that
`poc` means "proof-of-concept", and represents things that still need a
proper home/abstraction.
Secondly, `poc` is retrieving the context of two parsers---`LowerContext`
and `Asg`. The latter is desirable, since it's the final aggregation point,
but the former needs to be eliminated; in particular, packages need to be
worked into the ASG so that `found` can be removed.
Recursively loading `xmlo` files still happens in `poc`, but the compiler
will need this as well. Once packages are on the ASG, along with their
state, that responsibility can be generalized as well.
That will then simplify lowering even further, to the point where hopefully
everything has the same shape (once final aggregation has an abstraction),
after which we can then create a final abstraction to concisely stitch
everything together. Right now, Rust isn't able to infer `S` for
`Lower<S, LS>`, which is unfortunate, but we'll be able to help it along
with a more explicit abstraction.
DEV-11864
2022-05-27 13:51:29 -04:00
|
|
|
use AirToken::*;
|
|
|
|
|
|
|
|
match (self, tok) {
|
|
|
|
(Empty, IdentDecl(sym, kind, src)) => {
|
|
|
|
asg.declare(sym, kind, src).map(|_| ()).transition(Empty)
|
|
|
|
}
|
|
|
|
|
|
|
|
(Empty, IdentExternDecl(sym, kind, src)) => asg
|
|
|
|
.declare_extern(sym, kind, src)
|
|
|
|
.map(|_| ())
|
|
|
|
.transition(Empty),
|
|
|
|
|
|
|
|
(Empty, IdentDep(sym, dep)) => {
|
|
|
|
asg.add_dep_lookup(sym, dep);
|
|
|
|
Transition(Empty).incomplete()
|
|
|
|
}
|
|
|
|
|
|
|
|
(Empty, IdentFragment(sym, text)) => {
|
|
|
|
asg.set_fragment(sym, text).map(|_| ()).transition(Empty)
|
|
|
|
}
|
|
|
|
|
|
|
|
(Empty, IdentRoot(sym)) => {
|
|
|
|
let obj = asg.lookup_or_missing(sym);
|
|
|
|
asg.add_root(obj);
|
|
|
|
|
|
|
|
Transition(Empty).incomplete()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
tamer: xir::parse::ele: Superstate not to accept early EOF
This was accepting an early EOF when the active child `ParseState` was in an
accepting state, because it was not ensuring that anything on the stack was
also accepting.
Ideally, there should be nothing on the stack, and hopefully in the future
that's what happens. But with how things are today, it's important that, if
anything is on the stack, it is accepting.
Since `is_accepting` on the superstate is only called during finalization,
and because the check terminates early, and because the stack practically
speaking will only have a couple things on it max (unless we're in tail
position in a deeply nested tree, without TCO [yet]), this shouldn't be an
expensive check.
Implementing this did require that we expose `Context` to `is_accepting`,
which I had hoped to avoid having to do, but here we are.
DEV-7145
2022-08-11 13:49:11 -04:00
|
|
|
fn is_accepting(&self, _: &Self::Context) -> bool {
|
tamer: Refactor asg_builder into obj::xmlo::lower and asg::air
This finally uses `parse` all the way up to aggregation into the ASG, as can
be seen by the mess in `poc`. This will be further simplified---I just need
to get this committed so that I can mentally get it off my plate. I've been
separating this commit into smaller commits, but there's a point where it's
just not worth the effort anymore. I don't like making large changes such
as this one.
There is still work to do here. First, it's worth re-mentioning that
`poc` means "proof-of-concept", and represents things that still need a
proper home/abstraction.
Secondly, `poc` is retrieving the context of two parsers---`LowerContext`
and `Asg`. The latter is desirable, since it's the final aggregation point,
but the former needs to be eliminated; in particular, packages need to be
worked into the ASG so that `found` can be removed.
Recursively loading `xmlo` files still happens in `poc`, but the compiler
will need this as well. Once packages are on the ASG, along with their
state, that responsibility can be generalized as well.
That will then simplify lowering even further, to the point where hopefully
everything has the same shape (once final aggregation has an abstraction),
after which we can then create a final abstraction to concisely stitch
everything together. Right now, Rust isn't able to infer `S` for
`Lower<S, LS>`, which is unfortunate, but we'll be able to help it along
with a more explicit abstraction.
DEV-11864
2022-05-27 13:51:29 -04:00
|
|
|
*self == Self::Empty
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-02 13:26:46 -04:00
|
|
|
impl Display for AirAggregate {
|
tamer: Refactor asg_builder into obj::xmlo::lower and asg::air
This finally uses `parse` all the way up to aggregation into the ASG, as can
be seen by the mess in `poc`. This will be further simplified---I just need
to get this committed so that I can mentally get it off my plate. I've been
separating this commit into smaller commits, but there's a point where it's
just not worth the effort anymore. I don't like making large changes such
as this one.
There is still work to do here. First, it's worth re-mentioning that
`poc` means "proof-of-concept", and represents things that still need a
proper home/abstraction.
Secondly, `poc` is retrieving the context of two parsers---`LowerContext`
and `Asg`. The latter is desirable, since it's the final aggregation point,
but the former needs to be eliminated; in particular, packages need to be
worked into the ASG so that `found` can be removed.
Recursively loading `xmlo` files still happens in `poc`, but the compiler
will need this as well. Once packages are on the ASG, along with their
state, that responsibility can be generalized as well.
That will then simplify lowering even further, to the point where hopefully
everything has the same shape (once final aggregation has an abstraction),
after which we can then create a final abstraction to concisely stitch
everything together. Right now, Rust isn't able to infer `S` for
`Lower<S, LS>`, which is unfortunate, but we'll be able to help it along
with a more explicit abstraction.
DEV-11864
2022-05-27 13:51:29 -04:00
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
2022-06-02 13:26:46 -04:00
|
|
|
use AirAggregate::*;
|
tamer: Refactor asg_builder into obj::xmlo::lower and asg::air
This finally uses `parse` all the way up to aggregation into the ASG, as can
be seen by the mess in `poc`. This will be further simplified---I just need
to get this committed so that I can mentally get it off my plate. I've been
separating this commit into smaller commits, but there's a point where it's
just not worth the effort anymore. I don't like making large changes such
as this one.
There is still work to do here. First, it's worth re-mentioning that
`poc` means "proof-of-concept", and represents things that still need a
proper home/abstraction.
Secondly, `poc` is retrieving the context of two parsers---`LowerContext`
and `Asg`. The latter is desirable, since it's the final aggregation point,
but the former needs to be eliminated; in particular, packages need to be
worked into the ASG so that `found` can be removed.
Recursively loading `xmlo` files still happens in `poc`, but the compiler
will need this as well. Once packages are on the ASG, along with their
state, that responsibility can be generalized as well.
That will then simplify lowering even further, to the point where hopefully
everything has the same shape (once final aggregation has an abstraction),
after which we can then create a final abstraction to concisely stitch
everything together. Right now, Rust isn't able to infer `S` for
`Lower<S, LS>`, which is unfortunate, but we'll be able to help it along
with a more explicit abstraction.
DEV-11864
2022-05-27 13:51:29 -04:00
|
|
|
|
|
|
|
// This is not terribly useful beyond indicating which parser caused
|
|
|
|
// an error.
|
|
|
|
match self {
|
|
|
|
Empty => write!(f, "awaiting AIR input for ASG"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// These are tested as if they are another API directly atop of the ASG,
|
|
|
|
// since that is how they are used.
|
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
|
|
|
use std::assert_matches::assert_matches;
|
|
|
|
|
|
|
|
use crate::{
|
|
|
|
asg::{Ident, Object},
|
|
|
|
parse::{ParseError, Parsed},
|
|
|
|
};
|
|
|
|
|
|
|
|
use super::*;
|
|
|
|
|
2022-06-02 13:26:46 -04:00
|
|
|
type Sut = AirAggregate;
|
tamer: Refactor asg_builder into obj::xmlo::lower and asg::air
This finally uses `parse` all the way up to aggregation into the ASG, as can
be seen by the mess in `poc`. This will be further simplified---I just need
to get this committed so that I can mentally get it off my plate. I've been
separating this commit into smaller commits, but there's a point where it's
just not worth the effort anymore. I don't like making large changes such
as this one.
There is still work to do here. First, it's worth re-mentioning that
`poc` means "proof-of-concept", and represents things that still need a
proper home/abstraction.
Secondly, `poc` is retrieving the context of two parsers---`LowerContext`
and `Asg`. The latter is desirable, since it's the final aggregation point,
but the former needs to be eliminated; in particular, packages need to be
worked into the ASG so that `found` can be removed.
Recursively loading `xmlo` files still happens in `poc`, but the compiler
will need this as well. Once packages are on the ASG, along with their
state, that responsibility can be generalized as well.
That will then simplify lowering even further, to the point where hopefully
everything has the same shape (once final aggregation has an abstraction),
after which we can then create a final abstraction to concisely stitch
everything together. Right now, Rust isn't able to infer `S` for
`Lower<S, LS>`, which is unfortunate, but we'll be able to help it along
with a more explicit abstraction.
DEV-11864
2022-05-27 13:51:29 -04:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn ident_decl() {
|
|
|
|
let sym = "foo".into();
|
|
|
|
let kind = IdentKind::Tpl;
|
|
|
|
let src = Source {
|
|
|
|
src: Some("test/decl".into()),
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
|
|
|
|
let toks = vec![AirToken::IdentDecl(sym, kind.clone(), src.clone())]
|
|
|
|
.into_iter();
|
|
|
|
let mut sut = Sut::parse(toks);
|
|
|
|
|
|
|
|
assert_eq!(Some(Ok(Parsed::Incomplete)), sut.next());
|
|
|
|
|
|
|
|
let asg = sut.finalize().unwrap();
|
|
|
|
|
|
|
|
let ident_node =
|
|
|
|
asg.lookup(sym).expect("identifier was not added to graph");
|
|
|
|
let ident = asg.get(ident_node).unwrap();
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
Ok(ident),
|
|
|
|
Ident::declare(sym)
|
|
|
|
.resolve(kind.clone(), src.clone())
|
|
|
|
.map(Object::Ident)
|
|
|
|
.as_ref(),
|
|
|
|
);
|
|
|
|
|
|
|
|
// Re-instantiate the parser and test an error by attempting to
|
|
|
|
// redeclare the same identifier.
|
|
|
|
let bad_toks = vec![AirToken::IdentDecl(sym, kind, src)].into_iter();
|
|
|
|
let mut sut = Sut::parse_with_context(bad_toks, asg);
|
|
|
|
|
|
|
|
assert_matches!(
|
|
|
|
sut.next(),
|
|
|
|
Some(Err(ParseError::StateError(AsgError::IdentTransition(_)))),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn ident_extern_decl() {
|
|
|
|
let sym = "foo".into();
|
|
|
|
let kind = IdentKind::Tpl;
|
|
|
|
let src = Source {
|
|
|
|
src: Some("test/decl-extern".into()),
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
|
|
|
|
let toks =
|
|
|
|
vec![AirToken::IdentExternDecl(sym, kind.clone(), src.clone())]
|
|
|
|
.into_iter();
|
|
|
|
let mut sut = Sut::parse(toks);
|
|
|
|
|
|
|
|
assert_eq!(Some(Ok(Parsed::Incomplete)), sut.next());
|
|
|
|
|
|
|
|
let asg = sut.finalize().unwrap();
|
|
|
|
|
|
|
|
let ident_node =
|
|
|
|
asg.lookup(sym).expect("identifier was not added to graph");
|
|
|
|
let ident = asg.get(ident_node).unwrap();
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
Ok(ident),
|
|
|
|
Ident::declare(sym)
|
|
|
|
.extern_(kind, src.clone())
|
|
|
|
.map(Object::Ident)
|
|
|
|
.as_ref(),
|
|
|
|
);
|
|
|
|
|
|
|
|
// Re-instantiate the parser and test an error by attempting to
|
|
|
|
// redeclare with a different kind.
|
|
|
|
let different_kind = IdentKind::Meta;
|
|
|
|
let bad_toks =
|
|
|
|
vec![AirToken::IdentExternDecl(sym, different_kind, src)]
|
|
|
|
.into_iter();
|
|
|
|
let mut sut = Sut::parse_with_context(bad_toks, asg);
|
|
|
|
|
|
|
|
assert_matches!(
|
|
|
|
sut.next(),
|
|
|
|
Some(Err(ParseError::StateError(AsgError::IdentTransition(_)))),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn ident_dep() {
|
|
|
|
let ident = "foo".into();
|
|
|
|
let dep = "dep".into();
|
|
|
|
|
|
|
|
let toks = vec![AirToken::IdentDep(ident, dep)].into_iter();
|
|
|
|
let mut sut = Sut::parse(toks);
|
|
|
|
|
|
|
|
assert_eq!(Some(Ok(Parsed::Incomplete)), sut.next());
|
|
|
|
|
|
|
|
let asg = sut.finalize().unwrap();
|
|
|
|
|
|
|
|
let ident_node = asg
|
|
|
|
.lookup(ident)
|
|
|
|
.expect("identifier was not added to graph");
|
|
|
|
let dep_node = asg.lookup(dep).expect("dep was not added to graph");
|
|
|
|
|
|
|
|
assert!(asg.has_dep(ident_node, dep_node));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn ident_fragment() {
|
|
|
|
let sym = "frag".into();
|
|
|
|
let kind = IdentKind::Tpl;
|
|
|
|
let src = Source {
|
|
|
|
src: Some("test/frag".into()),
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
let frag = "fragment text".into();
|
|
|
|
|
|
|
|
let toks = vec![
|
|
|
|
// Identifier must be declared before it can be given a
|
|
|
|
// fragment.
|
|
|
|
AirToken::IdentDecl(sym, kind.clone(), src.clone()),
|
|
|
|
AirToken::IdentFragment(sym, frag),
|
|
|
|
]
|
|
|
|
.into_iter();
|
|
|
|
|
|
|
|
let mut sut = Sut::parse(toks);
|
|
|
|
|
|
|
|
assert_eq!(Some(Ok(Parsed::Incomplete)), sut.next()); // IdentDecl
|
|
|
|
assert_eq!(Some(Ok(Parsed::Incomplete)), sut.next()); // IdentFragment
|
|
|
|
|
|
|
|
let asg = sut.finalize().unwrap();
|
|
|
|
|
|
|
|
let ident_node =
|
|
|
|
asg.lookup(sym).expect("identifier was not added to graph");
|
|
|
|
let ident = asg.get(ident_node).unwrap();
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
Ok(ident),
|
|
|
|
Ident::declare(sym)
|
|
|
|
.resolve(kind.clone(), src.clone())
|
|
|
|
.and_then(|resolved| resolved.set_fragment(frag))
|
|
|
|
.map(Object::Ident)
|
|
|
|
.as_ref(),
|
|
|
|
);
|
|
|
|
|
|
|
|
// Re-instantiate the parser and test an error by attempting to
|
|
|
|
// re-set the fragment.
|
|
|
|
let bad_toks = vec![AirToken::IdentFragment(sym, frag)].into_iter();
|
|
|
|
let mut sut = Sut::parse_with_context(bad_toks, asg);
|
|
|
|
|
|
|
|
assert_matches!(
|
|
|
|
sut.next(),
|
|
|
|
Some(Err(ParseError::StateError(AsgError::IdentTransition(_)))),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Adding a root before the identifier exists should add a
|
|
|
|
// `Ident::Missing`.
|
|
|
|
#[test]
|
|
|
|
fn ident_root_missing() {
|
|
|
|
let sym = "toroot".into();
|
|
|
|
|
|
|
|
let toks = vec![AirToken::IdentRoot(sym)].into_iter();
|
|
|
|
let mut sut = Sut::parse(toks);
|
|
|
|
|
|
|
|
assert_eq!(Some(Ok(Parsed::Incomplete)), sut.next());
|
|
|
|
|
|
|
|
let asg = sut.finalize().unwrap();
|
|
|
|
|
|
|
|
let ident_node = asg
|
|
|
|
.lookup(sym)
|
|
|
|
.expect("identifier was not added to the graph");
|
|
|
|
let ident = asg.get(ident_node).unwrap();
|
|
|
|
|
|
|
|
// The identifier did not previously exist,
|
|
|
|
// and so a missing node is created as a placeholder.
|
|
|
|
assert_eq!(&Object::Ident(Ident::Missing(sym)), ident);
|
|
|
|
|
|
|
|
// And that missing identifier should be rooted.
|
|
|
|
assert!(asg.is_rooted(ident_node));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn ident_root_existing() {
|
|
|
|
let sym = "toroot".into();
|
|
|
|
let kind = IdentKind::Tpl;
|
|
|
|
let src = Source {
|
|
|
|
src: Some("test/root-existing".into()),
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
|
|
|
|
// Ensure that it won't auto-root based on the kind,
|
|
|
|
// otherwise we won't be testing the right thing.
|
|
|
|
assert!(!kind.is_auto_root());
|
|
|
|
|
|
|
|
let toks = vec![
|
|
|
|
AirToken::IdentDecl(sym, kind.clone(), src.clone()),
|
|
|
|
AirToken::IdentRoot(sym),
|
|
|
|
]
|
|
|
|
.into_iter();
|
|
|
|
|
|
|
|
let mut sut = Sut::parse(toks);
|
|
|
|
|
|
|
|
assert_eq!(Some(Ok(Parsed::Incomplete)), sut.next()); // IdentDecl
|
|
|
|
assert_eq!(Some(Ok(Parsed::Incomplete)), sut.next()); // IdentRoot
|
|
|
|
|
|
|
|
let asg = sut.finalize().unwrap();
|
|
|
|
|
|
|
|
let ident_node = asg
|
|
|
|
.lookup(sym)
|
|
|
|
.expect("identifier was not added to the graph");
|
|
|
|
let ident = asg.get(ident_node).unwrap();
|
|
|
|
|
|
|
|
// The previously-declared identifier...
|
|
|
|
assert_eq!(
|
|
|
|
Ok(ident),
|
|
|
|
Ident::declare(sym)
|
|
|
|
.resolve(kind.clone(), src.clone())
|
|
|
|
.map(Object::Ident)
|
|
|
|
.as_ref()
|
|
|
|
);
|
|
|
|
|
|
|
|
// ...should have been subsequently rooted.
|
|
|
|
assert!(asg.is_rooted(ident_node));
|
|
|
|
}
|
|
|
|
}
|