tamer: asg: Associate spans with identifiers and introduce diagnostics

This ASG implementation is a refactored form of original code from the
proof-of-concept linker, which was well before the span and diagnostic
implementations, and well before I knew for certain how I was going to solve
that problem.

This was quite the pain in the ass, but introduces spans to the AIR tokens
and graph so that we always have useful diagnostic information.  With that
said, there are some important things to note:

  1. Linker spans will originate from the `xmlo` files until we persist
     spans to those object files during `tamec`'s compilation.  But it's
     better than nothing.
  2. Some additional refactoring is still needed for consistency, e.g. use
     of `SPair`.
  3. This is just a preliminary introduction.  More refactoring will come as
     tamec is continued.

DEV-13041
main
Mike Gerwitz 2022-12-15 12:07:58 -05:00
parent 3cc40f387b
commit 0b2e563cdb
16 changed files with 916 additions and 731 deletions

View File

@ -30,13 +30,19 @@ use test::Bencher;
mod base {
use super::*;
use tamer::asg::{DefaultAsg, IdentKind, Source};
use tamer::sym::{GlobalSymbolIntern, SymbolId};
use tamer::{
asg::{DefaultAsg, IdentKind, Source},
parse::util::SPair,
span::UNKNOWN_SPAN,
sym::GlobalSymbolIntern,
};
type Sut = DefaultAsg;
fn interned_n(n: u16) -> Vec<SymbolId> {
(0..n).map(|i| i.to_string().intern()).collect()
fn interned_n(n: u16) -> Vec<SPair> {
(0..n)
.map(|i| SPair(i.to_string().intern(), UNKNOWN_SPAN))
.collect()
}
#[bench]
@ -141,7 +147,7 @@ mod base {
bench.iter(|| {
xs.iter()
.map(|sym| sut.lookup(*sym).unwrap())
.map(|sym| sut.lookup(sym.symbol()).unwrap())
.for_each(drop);
});
}
@ -308,14 +314,17 @@ mod object {
mod ident {
use super::*;
use tamer::asg::{Ident, IdentKind, Source};
use tamer::sym::GlobalSymbolIntern;
use tamer::{
asg::{Ident, IdentKind, Source},
parse::util::SPair,
span::UNKNOWN_SPAN as S0,
};
type Sut = Ident;
#[bench]
fn declare_1_000(bench: &mut Bencher) {
let sym = "sym".intern();
let sym = SPair("sym".into(), S0);
bench.iter(|| {
(0..1000).map(|_| Sut::declare(sym)).for_each(drop);
@ -324,13 +333,16 @@ mod object {
#[bench]
fn resolve_1_000_missing(bench: &mut Bencher) {
let sym = "sym".intern();
let sym = SPair("sym".into(), S0);
bench.iter(|| {
(0..1000)
.map(|_| {
Sut::declare(sym)
.resolve(IdentKind::Meta, Source::default())
Sut::declare(sym).resolve(
S0,
IdentKind::Meta,
Source::default(),
)
})
.for_each(drop);
});
@ -338,13 +350,16 @@ mod object {
#[bench]
fn extern_1_000_missing(bench: &mut Bencher) {
let sym = "sym".intern();
let sym = SPair("sym".into(), S0);
bench.iter(|| {
(0..1000)
.map(|_| {
Sut::declare(sym)
.extern_(IdentKind::Meta, Source::default())
Sut::declare(sym).extern_(
S0,
IdentKind::Meta,
Source::default(),
)
})
.for_each(drop);
});
@ -352,15 +367,15 @@ mod object {
#[bench]
fn resolve_1_000_extern(bench: &mut Bencher) {
let sym = "sym".intern();
let sym = SPair("sym".into(), S0);
bench.iter(|| {
(0..1000)
.map(|_| {
Sut::declare(sym)
.extern_(IdentKind::Meta, Source::default())
.extern_(S0, IdentKind::Meta, Source::default())
.unwrap()
.resolve(IdentKind::Meta, Source::default())
.resolve(S0, IdentKind::Meta, Source::default())
})
.for_each(drop);
});
@ -368,13 +383,14 @@ mod object {
#[bench]
fn resolve_1_000_override(bench: &mut Bencher) {
let sym = "sym".intern();
let sym = SPair("sym".into(), S0);
bench.iter(|| {
(0..1000)
.map(|_| {
Sut::declare(sym)
.resolve(
S0,
IdentKind::Meta,
Source {
virtual_: true,
@ -383,6 +399,7 @@ mod object {
)
.unwrap()
.resolve(
S0,
IdentKind::Meta,
Source {
override_: true,
@ -397,13 +414,14 @@ mod object {
// Override encountered before virtual
#[bench]
fn resolve_1_000_override_virt_after_override(bench: &mut Bencher) {
let sym = "sym".intern();
let sym = SPair("sym".into(), S0);
bench.iter(|| {
(0..1000)
.map(|_| {
Sut::declare(sym)
.resolve(
S0,
IdentKind::Meta,
Source {
override_: true,
@ -412,6 +430,7 @@ mod object {
)
.unwrap()
.resolve(
S0,
IdentKind::Meta,
Source {
virtual_: true,
@ -425,13 +444,13 @@ mod object {
#[bench]
fn set_fragment_1_000_resolved_with_new_str(bench: &mut Bencher) {
let sym = "sym".intern();
let sym = SPair("sym".into(), S0);
bench.iter(|| {
(0..1000)
.map(|_| {
Sut::declare(sym)
.resolve(IdentKind::Meta, Source::default())
.resolve(S0, IdentKind::Meta, Source::default())
.unwrap()
.set_fragment("".into())
})
@ -442,7 +461,7 @@ mod object {
// No need to do all of the others, since they're all the same thing.
#[bench]
fn declared_name_1_000(bench: &mut Bencher) {
let sym = "sym".intern();
let sym = SPair("sym".into(), S0);
bench.iter(|| {
(0..1000).map(|_| Sut::declare(sym).name()).for_each(drop);

View File

@ -22,17 +22,23 @@
extern crate tamer;
extern crate test;
use tamer::num::Dtype;
use test::Bencher;
use tamer::asg::{DefaultAsg, IdentKind, Source};
use tamer::ld::xmle::{lower::sort, Sections};
use tamer::sym::{GlobalSymbolIntern, SymbolId};
pub(crate) use tamer::{
asg::{DefaultAsg, IdentKind, Source},
ld::xmle::{lower::sort, Sections},
num::Dtype,
parse::util::SPair,
span::UNKNOWN_SPAN,
sym::{GlobalSymbolIntern, SymbolId},
};
type TestAsg = DefaultAsg;
fn interned_n(n: u16) -> Vec<SymbolId> {
(0..n).map(|i| i.to_string().intern()).collect()
fn interned_n(n: u16) -> Vec<SPair> {
(0..n)
.map(|i| SPair(i.to_string().intern(), UNKNOWN_SPAN))
.collect()
}
#[bench]

View File

@ -19,7 +19,8 @@
use super::{Asg, AsgError, FragmentText, IdentKind, Source};
use crate::{
parse::{self, ParseState, Token, Transition, Transitionable},
fmt::{DisplayWrapper, TtQuote},
parse::{self, util::SPair, ParseState, Token, Transition, Transitionable},
span::UNKNOWN_SPAN,
sym::SymbolId,
};
@ -65,15 +66,35 @@ pub enum Air {
Todo,
/// Declare a resolved identifier.
IdentDecl(IdentSym, IdentKind, Source),
IdentDecl(SPair, IdentKind, Source),
/// Declare an external identifier that must be resolved before linking.
IdentExternDecl(IdentSym, IdentKind, Source),
IdentExternDecl(SPair, IdentKind, Source),
/// Declare that an identifier depends on another for its definition.
IdentDep(IdentSym, DepSym),
///
/// The first identifier will depend on the second
/// (`0 -> 1`).
/// The spans associated with each [`SPair`] will be used
/// if the respective identifier has not yet been defined.
IdentDep(SPair, SPair),
/// Associate a code fragment with an identifier.
IdentFragment(IdentSym, FragmentText),
/// Root an identifier.
IdentRoot(IdentSym),
///
/// A fragment does not have an associated span because it is
/// conceptually associated with all the spans from which it is
/// derived;
/// the format of the object file will change in the future to
/// retain this information.
IdentFragment(SPair, FragmentText),
/// Root an identifier at the request of some entity at the associated
/// span of the [`SPair`].
///
/// Rooting is caused by _something_,
/// and the span is intended to aid in tracking down why rooting
/// occurred.
IdentRoot(SPair),
}
impl Token for Air {
@ -97,20 +118,27 @@ impl Display for Air {
match self {
Todo => write!(f, "TODO"),
IdentDecl(sym, ..) => {
write!(f, "declaration of identifier `{sym}`")
IdentDecl(spair, _, _) => {
write!(f, "declaration of identifier {}", TtQuote::wrap(spair))
}
IdentExternDecl(sym, ..) => {
write!(f, "declaration of external identifier `{sym}`")
IdentExternDecl(spair, _, _) => {
write!(
f,
"declaration of external identifier {}",
TtQuote::wrap(spair)
)
}
IdentDep(isym, dsym) => write!(
f,
// TODO: Use list wrapper
"declaration of identifier dependency `{isym} -> {dsym}`"
),
IdentFragment(sym, ..) => {
write!(f, "identifier `{sym}` fragment text")
IdentFragment(depsym, _text) => {
write!(f, "identifier {}` fragment text", TtQuote::wrap(depsym))
}
IdentRoot(sym) => {
write!(f, "rooting of identifier {}", TtQuote::wrap(sym))
}
IdentRoot(sym) => write!(f, "rooting of identifier `{sym}`"),
}
}
}
@ -147,12 +175,12 @@ impl ParseState for AirAggregate {
match (self, tok) {
(Empty, Todo) => Transition(Empty).incomplete(),
(Empty, IdentDecl(sym, kind, src)) => {
asg.declare(sym, kind, src).map(|_| ()).transition(Empty)
(Empty, IdentDecl(name, kind, src)) => {
asg.declare(name, kind, src).map(|_| ()).transition(Empty)
}
(Empty, IdentExternDecl(sym, kind, src)) => asg
.declare_extern(sym, kind, src)
(Empty, IdentExternDecl(name, kind, src)) => asg
.declare_extern(name, kind, src)
.map(|_| ())
.transition(Empty),

View File

@ -25,6 +25,7 @@ use std::assert_matches::assert_matches;
use crate::{
asg::{Ident, Object},
parse::{ParseError, Parsed},
span::dummy::*,
};
use super::*;
@ -40,7 +41,8 @@ fn ident_decl() {
..Default::default()
};
let toks = vec![Air::IdentDecl(sym, kind.clone(), src.clone())].into_iter();
let toks = vec![Air::IdentDecl(SPair(sym, S1), kind.clone(), src.clone())]
.into_iter();
let mut sut = Sut::parse(toks);
assert_eq!(Some(Ok(Parsed::Incomplete)), sut.next());
@ -53,15 +55,15 @@ fn ident_decl() {
assert_eq!(
Ok(ident),
Ident::declare(sym)
.resolve(kind.clone(), src.clone())
Ident::declare(SPair(sym, S1))
.resolve(S1, 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![Air::IdentDecl(sym, kind, src)].into_iter();
let bad_toks = vec![Air::IdentDecl(SPair(sym, S2), kind, src)].into_iter();
let mut sut = Sut::parse_with_context(bad_toks, asg);
assert_matches!(
@ -79,8 +81,12 @@ fn ident_extern_decl() {
..Default::default()
};
let toks =
vec![Air::IdentExternDecl(sym, kind.clone(), src.clone())].into_iter();
let toks = vec![Air::IdentExternDecl(
SPair(sym, S1),
kind.clone(),
src.clone(),
)]
.into_iter();
let mut sut = Sut::parse(toks);
assert_eq!(Some(Ok(Parsed::Incomplete)), sut.next());
@ -93,8 +99,8 @@ fn ident_extern_decl() {
assert_eq!(
Ok(ident),
Ident::declare(sym)
.extern_(kind, src.clone())
Ident::declare(SPair(sym, S1))
.extern_(S1, kind, src.clone())
.map(Object::Ident)
.as_ref(),
);
@ -103,7 +109,8 @@ fn ident_extern_decl() {
// redeclare with a different kind.
let different_kind = IdentKind::Meta;
let bad_toks =
vec![Air::IdentExternDecl(sym, different_kind, src)].into_iter();
vec![Air::IdentExternDecl(SPair(sym, S2), different_kind, src)]
.into_iter();
let mut sut = Sut::parse_with_context(bad_toks, asg);
assert_matches!(
@ -117,7 +124,8 @@ fn ident_dep() {
let ident = "foo".into();
let dep = "dep".into();
let toks = vec![Air::IdentDep(ident, dep)].into_iter();
let toks =
vec![Air::IdentDep(SPair(ident, S1), SPair(dep, S2))].into_iter();
let mut sut = Sut::parse(toks);
assert_eq!(Some(Ok(Parsed::Incomplete)), sut.next());
@ -145,8 +153,8 @@ fn ident_fragment() {
let toks = vec![
// Identifier must be declared before it can be given a
// fragment.
Air::IdentDecl(sym, kind.clone(), src.clone()),
Air::IdentFragment(sym, frag),
Air::IdentDecl(SPair(sym, S1), kind.clone(), src.clone()),
Air::IdentFragment(SPair(sym, S1), frag),
]
.into_iter();
@ -163,8 +171,8 @@ fn ident_fragment() {
assert_eq!(
Ok(ident),
Ident::declare(sym)
.resolve(kind.clone(), src.clone())
Ident::declare(SPair(sym, S1))
.resolve(S1, kind.clone(), src.clone())
.and_then(|resolved| resolved.set_fragment(frag))
.map(Object::Ident)
.as_ref(),
@ -172,7 +180,7 @@ fn ident_fragment() {
// Re-instantiate the parser and test an error by attempting to
// re-set the fragment.
let bad_toks = vec![Air::IdentFragment(sym, frag)].into_iter();
let bad_toks = vec![Air::IdentFragment(SPair(sym, S1), frag)].into_iter();
let mut sut = Sut::parse_with_context(bad_toks, asg);
assert_matches!(
@ -187,7 +195,7 @@ fn ident_fragment() {
fn ident_root_missing() {
let sym = "toroot".into();
let toks = vec![Air::IdentRoot(sym)].into_iter();
let toks = vec![Air::IdentRoot(SPair(sym, S1))].into_iter();
let mut sut = Sut::parse(toks);
assert_eq!(Some(Ok(Parsed::Incomplete)), sut.next());
@ -201,7 +209,7 @@ fn ident_root_missing() {
// The identifier did not previously exist,
// and so a missing node is created as a placeholder.
assert_eq!(&Object::Ident(Ident::Missing(sym)), ident);
assert_eq!(&Object::Ident(Ident::Missing(SPair(sym, S1))), ident);
// And that missing identifier should be rooted.
assert!(asg.is_rooted(ident_node));
@ -221,8 +229,8 @@ fn ident_root_existing() {
assert!(!kind.is_auto_root());
let toks = vec![
Air::IdentDecl(sym, kind.clone(), src.clone()),
Air::IdentRoot(sym),
Air::IdentDecl(SPair(sym, S1), kind.clone(), src.clone()),
Air::IdentRoot(SPair(sym, S2)),
]
.into_iter();
@ -241,8 +249,8 @@ fn ident_root_existing() {
// The previously-declared identifier...
assert_eq!(
Ok(ident),
Ident::declare(sym)
.resolve(kind.clone(), src.clone())
Ident::declare(SPair(sym, S1))
.resolve(S1, kind.clone(), src.clone())
.map(Object::Ident)
.as_ref()
);

View File

@ -23,6 +23,8 @@ use super::{
AsgError, FragmentText, Ident, IdentKind, Object, Source, TransitionResult,
};
use crate::global;
use crate::parse::util::SPair;
use crate::parse::Token;
use crate::sym::SymbolId;
use petgraph::graph::{DiGraph, Graph, NodeIndex};
use std::fmt::Debug;
@ -173,12 +175,18 @@ impl Asg {
/// Lookup `ident` or add a missing identifier to the graph and return a
/// reference to it.
///
/// The provided span is necessary to seed the missing identifier with
/// some sort of context to aid in debugging why a missing identifier
/// was introduced to the graph.
///
/// See [`Ident::declare`] for more information.
pub(super) fn lookup_or_missing(&mut self, ident: SymbolId) -> ObjectRef {
self.lookup(ident).unwrap_or_else(|| {
pub(super) fn lookup_or_missing(&mut self, ident: SPair) -> ObjectRef {
let sym = ident.symbol();
self.lookup(sym).unwrap_or_else(|| {
let index = self.graph.add_node(Some(Ident::declare(ident).into()));
self.index_identifier(ident, index);
self.index_identifier(sym, index);
ObjectRef::new(index)
})
}
@ -186,7 +194,7 @@ impl Asg {
/// Perform a state transition on an identifier by name.
///
/// Look up `ident` or add a missing identifier if it does not yet exist
/// (see `lookup_or_missing`).
/// (see [`Self::lookup_or_missing`]).
/// Then invoke `f` with the located identifier and replace the
/// identifier on the graph with the result.
///
@ -194,7 +202,7 @@ impl Asg {
/// value on transition failure.
fn with_ident_lookup<F>(
&mut self,
name: SymbolId,
name: SPair,
f: F,
) -> AsgResult<ObjectRef>
where
@ -296,13 +304,13 @@ impl Asg {
/// and return an [`ObjectRef`] reference.
pub fn declare(
&mut self,
name: SymbolId,
name: SPair,
kind: IdentKind,
src: Source,
) -> AsgResult<ObjectRef> {
let is_auto_root = kind.is_auto_root();
self.with_ident_lookup(name, |obj| obj.resolve(kind, src))
self.with_ident_lookup(name, |obj| obj.resolve(name.span(), kind, src))
.and_then(|node| {
is_auto_root.then(|| self.add_root(node));
Ok(node)
@ -331,11 +339,11 @@ impl Asg {
/// compatibility related to extern resolution.
pub fn declare_extern(
&mut self,
name: SymbolId,
name: SPair,
kind: IdentKind,
src: Source,
) -> AsgResult<ObjectRef> {
self.with_ident_lookup(name, |obj| obj.extern_(kind, src))
self.with_ident_lookup(name, |obj| obj.extern_(name.span(), kind, src))
}
/// Set the fragment associated with a concrete identifier.
@ -345,7 +353,7 @@ impl Asg {
/// see [`Ident::set_fragment`].
pub fn set_fragment(
&mut self,
name: SymbolId,
name: SPair,
text: FragmentText,
) -> AsgResult<ObjectRef> {
self.with_ident_lookup(name, |obj| obj.set_fragment(text))
@ -426,8 +434,8 @@ impl Asg {
/// References to both identifiers are returned in argument order.
pub fn add_dep_lookup(
&mut self,
ident: SymbolId,
dep: SymbolId,
ident: SPair,
dep: SPair,
) -> (ObjectRef, ObjectRef) {
let identi = self.lookup_or_missing(ident);
let depi = self.lookup_or_missing(dep);
@ -469,8 +477,7 @@ impl From<ObjectRef> for NodeIndex {
mod test {
use super::super::error::AsgError;
use super::*;
use crate::num::Dim;
use crate::sym::GlobalSymbolIntern;
use crate::{num::Dim, span::dummy::*, sym::GlobalSymbolIntern};
use std::assert_matches::assert_matches;
type Sut = Asg;
@ -495,11 +502,11 @@ mod test {
// index to create a gap, and then use an index within that gap
// to ensure that it's not considered an already-defined
// identifier.
let syma = "syma".intern();
let symb = "symab".intern();
let syma = "syma".into();
let symb = "symab".into();
let nodea = sut.declare(
syma,
SPair(syma, S1),
IdentKind::Meta,
Source {
desc: Some("a".into()),
@ -508,7 +515,7 @@ mod test {
)?;
let nodeb = sut.declare(
symb,
SPair(symb, S2),
IdentKind::Worksheet,
Source {
desc: Some("b".into()),
@ -519,7 +526,7 @@ mod test {
assert_ne!(nodea, nodeb);
let givena = sut.get_ident(nodea).unwrap();
assert_eq!(syma, givena.name());
assert_eq!(SPair(syma, S1), givena.name());
assert_eq!(Some(&IdentKind::Meta), givena.kind());
assert_eq!(
Some(&Source {
@ -530,7 +537,7 @@ mod test {
);
let givenb = sut.get_ident(nodeb).unwrap();
assert_eq!(symb, givenb.name());
assert_eq!(SPair(symb, S2), givenb.name());
assert_eq!(Some(&IdentKind::Worksheet), givenb.kind());
assert_eq!(
Some(&Source {
@ -551,8 +558,11 @@ mod test {
// Sanity check, in case this changes.
assert!(auto_kind.is_auto_root());
let auto_root_node =
sut.declare("auto_root".intern(), auto_kind, Default::default())?;
let auto_root_node = sut.declare(
SPair("auto_root".into(), S1),
auto_kind,
Default::default(),
)?;
// Should have been automatically added as a root.
assert!(sut
@ -563,7 +573,7 @@ mod test {
assert!(!no_auto_kind.is_auto_root());
let no_auto_root_node = sut.declare(
"no_auto_root".intern(),
SPair("no_auto_root".into(), S2),
no_auto_kind,
Default::default(),
)?;
@ -582,7 +592,7 @@ mod test {
let sym = "lookup".into();
let node = sut.declare(
sym,
SPair(sym, S1),
IdentKind::Meta,
Source {
generated: true,
@ -599,15 +609,16 @@ mod test {
fn declare_fails_if_transition_fails() -> AsgResult<()> {
let mut sut = Sut::new();
let sym = "symdup".intern();
let sym = "symdup".into();
let src = Source {
desc: Some("orig".into()),
..Default::default()
};
// Set up an object to fail redeclaration.
let node = sut.declare(sym, IdentKind::Meta, src.clone())?;
let result = sut.declare(sym, IdentKind::Meta, Source::default());
let node = sut.declare(SPair(sym, S1), IdentKind::Meta, src.clone())?;
let result =
sut.declare(SPair(sym, S2), IdentKind::Meta, Source::default());
assert_matches!(result, Err(AsgError::IdentTransition(..)));
@ -621,16 +632,18 @@ mod test {
fn declare_extern_returns_existing() -> AsgResult<()> {
let mut sut = Sut::new();
let sym = "symext".intern();
let sym = "symext".into();
let src = Source::default();
let kind = IdentKind::Class(Dim::Matrix);
let node = sut.declare_extern(sym, kind.clone(), src.clone())?;
let node =
sut.declare_extern(SPair(sym, S1), kind.clone(), src.clone())?;
let resrc = Source {
desc: Some("redeclare".into()),
..Default::default()
};
let redeclare = sut.declare_extern(sym, kind.clone(), resrc.clone())?;
let redeclare =
sut.declare_extern(SPair(sym, S2), kind.clone(), resrc.clone())?;
assert_eq!(node, redeclare);
@ -642,17 +655,20 @@ mod test {
fn declare_extern_fails_if_transition_fails() -> AsgResult<()> {
let mut sut = Sut::new();
let sym = "symdup".intern();
let sym = "symdup".into();
let src = Source {
desc: Some("orig".into()),
..Default::default()
};
let node = sut.declare(sym, IdentKind::Meta, src.clone())?;
let node = sut.declare(SPair(sym, S1), IdentKind::Meta, src.clone())?;
// Changes kind, which is invalid.
let result =
sut.declare_extern(sym, IdentKind::Worksheet, Source::default());
let result = sut.declare_extern(
SPair(sym, S2),
IdentKind::Worksheet,
Source::default(),
);
assert_matches!(result, Err(AsgError::IdentTransition(..)));
@ -666,15 +682,15 @@ mod test {
fn add_fragment_to_ident() -> AsgResult<()> {
let mut sut = Sut::new();
let sym = "tofrag".intern();
let sym = "tofrag".into();
let src = Source {
generated: true,
..Default::default()
};
let node = sut.declare(sym, IdentKind::Meta, src.clone())?;
let node = sut.declare(SPair(sym, S1), IdentKind::Meta, src.clone())?;
let fragment = "a fragment".intern();
let node_with_frag = sut.set_fragment(sym, fragment)?;
let node_with_frag = sut.set_fragment(SPair(sym, S2), fragment)?;
// Attaching a fragment should _replace_ the node, not create a
// new one
@ -685,7 +701,7 @@ mod test {
let obj = sut.get_ident(node).unwrap();
assert_eq!(sym, obj.name());
assert_eq!(SPair(sym, S1), obj.name());
assert_eq!(Some(&IdentKind::Meta), obj.kind());
assert_eq!(Some(&src), obj.src());
assert_eq!(Some(fragment), obj.fragment());
@ -697,25 +713,25 @@ mod test {
fn add_fragment_to_ident_fails_if_transition_fails() -> AsgResult<()> {
let mut sut = Sut::new();
let sym = "failfrag".intern();
let sym = "failfrag".into();
let src = Source {
generated: true,
..Default::default()
};
// The failure will come from terr below, not this.
let node = sut.declare(sym, IdentKind::Meta, src.clone())?;
let node = sut.declare(SPair(sym, S1), IdentKind::Meta, src.clone())?;
// The first set will succeed.
sut.set_fragment(sym, "".into())?;
sut.set_fragment(SPair(sym, S2), "".into())?;
// This will fail.
let result = sut.set_fragment(sym, "".into());
let result = sut.set_fragment(SPair(sym, S3), "".into());
// The node should have been restored.
let obj = sut.get_ident(node).unwrap();
assert_eq!(sym, obj.name());
assert_eq!(SPair(sym, S1), obj.name());
assert_matches!(result, Err(AsgError::IdentTransition(..)));
Ok(())
@ -725,11 +741,13 @@ mod test {
fn add_ident_dep_to_ident() -> AsgResult<()> {
let mut sut = Sut::new();
let sym = "sym".intern();
let dep = "dep".intern();
let sym = "sym".into();
let dep = "dep".into();
let symnode = sut.declare(sym, IdentKind::Meta, Source::default())?;
let depnode = sut.declare(dep, IdentKind::Meta, Source::default())?;
let symnode =
sut.declare(SPair(sym, S1), IdentKind::Meta, Source::default())?;
let depnode =
sut.declare(SPair(dep, S2), IdentKind::Meta, Source::default())?;
sut.add_dep(symnode, depnode);
assert!(sut.has_dep(symnode, depnode));
@ -746,8 +764,8 @@ mod test {
fn add_dep_lookup_existing() -> AsgResult<()> {
let mut sut = Sut::new();
let sym = "sym".intern();
let dep = "dep".intern();
let sym = SPair("sym".into(), S1);
let dep = SPair("dep".into(), S2);
let _ = sut.declare(sym, IdentKind::Meta, Source::default())?;
let _ = sut.declare(dep, IdentKind::Meta, Source::default())?;
@ -762,8 +780,8 @@ mod test {
fn add_dep_lookup_missing() -> AsgResult<()> {
let mut sut = Sut::new();
let sym = "sym".intern();
let dep = "dep".intern();
let sym = SPair("sym".into(), S1);
let dep = SPair("dep".into(), S2);
// both of these are missing
let (symnode, depnode) = sut.add_dep_lookup(sym, dep);
@ -779,8 +797,8 @@ mod test {
fn declare_return_missing_symbol() -> AsgResult<()> {
let mut sut = Sut::new();
let sym = "sym".intern();
let dep = "dep".intern();
let sym = SPair("sym".into(), S1);
let dep = SPair("dep".into(), S2);
// both of these are missing, see add_dep_lookup_missing
let (symnode, _) = sut.add_dep_lookup(sym, dep);

View File

@ -20,7 +20,11 @@
//! Identifiers (a type of [object][super::object]).
use crate::{
diagnose::{Annotate, Diagnostic},
fmt::{DisplayWrapper, TtQuote},
num::{Dim, Dtype},
parse::{util::SPair, Token},
span::Span,
sym::{st, GlobalSymbolResolve, SymbolId},
};
@ -42,16 +46,26 @@ pub enum Ident {
///
/// This variant contains the symbol representing the name of the
/// expected identifier.
/// By defining an object as missing,
/// this allows the graph to be built incrementally as objects are
/// discovered.
Missing(SymbolId),
/// This is important,
/// since identifiers in TAME may be referenced before they are
/// defined.
///
/// The [`Span`] associated with this missing identifier represents one
/// of the references to the identifier,
/// ensuring that there is always some useful information to help
/// debug missing identifiers.
/// A reference to this missing identifier ought to have its own span so
/// that diagnostic messages can direct the user to other instances
/// where unknown identifiers were referenced.
Missing(SPair),
/// A resolved identifier.
///
/// This represents an identifier that has been declared with certain
/// type information.
Ident(SymbolId, IdentKind, Source),
/// An identifier has a single canonical location represented by the
/// [`SPair`]'s [`Span`].
Ident(SPair, IdentKind, Source),
/// An identifier that has not yet been resolved.
///
@ -60,12 +74,12 @@ pub enum Ident {
/// It is an error if the loaded identifier does not have a compatible
/// [`IdentKind`].
///
/// The source location of an extern represents the location of the
/// extern declaration.
/// The [`Span`] and [`Source`] of an extern represents the location of
/// the extern declaration.
/// Once resolved, however,
/// the source will instead represent the location of the concrete
/// both will instead represent the location of the concrete
/// identifier.
Extern(SymbolId, IdentKind, Source),
Extern(SPair, IdentKind, Source),
/// Identifier with associated text.
///
@ -74,20 +88,29 @@ pub enum Ident {
/// They are produced by the compiler and it is the job of the
/// [linker][crate::ld] to put them into the correct order for the
/// final executable.
IdentFragment(SymbolId, IdentKind, Source, FragmentText),
IdentFragment(SPair, IdentKind, Source, FragmentText),
}
impl Ident {
/// Identifier name.
pub fn name(&self) -> SymbolId {
pub fn name(&self) -> SPair {
match self {
Self::Missing(name, ..)
Self::Missing(name)
| Self::Ident(name, ..)
| Self::Extern(name, ..)
| Self::IdentFragment(name, ..) => *name,
}
}
pub fn span(&self) -> Span {
match self {
Self::Missing(SPair(_, span))
| Self::Ident(SPair(_, span), ..)
| Self::Extern(SPair(_, span), ..)
| Self::IdentFragment(SPair(_, span), ..) => *span,
}
}
/// Identifier [`IdentKind`].
///
/// If the object does not have a kind
@ -97,9 +120,9 @@ impl Ident {
match self {
Self::Missing(..) => None,
Self::Ident(_, kind, ..)
| Self::Extern(_, kind, ..)
| Self::IdentFragment(_, kind, ..) => Some(kind),
Self::Ident(_, kind, _)
| Self::Extern(_, kind, _)
| Self::IdentFragment(_, kind, _, _) => Some(kind),
}
}
@ -133,7 +156,11 @@ impl Ident {
/// Produce an object representing a missing identifier.
///
/// This is the base state for all identifiers.
pub fn declare(ident: SymbolId) -> Self {
/// The [`Span`] associated with the pair should be the span of whatever
/// reference triggered this declaration;
/// it will be later replaced with the span of the identifier once
/// its definition is found.
pub fn declare(ident: SPair) -> Self {
Ident::Missing(ident)
}
@ -167,6 +194,7 @@ impl Ident {
/// an identifier cannot be redeclared and this operation will fail.
pub fn resolve(
self,
span: Span,
kind: IdentKind,
mut src: Source,
) -> TransitionResult<Ident> {
@ -176,64 +204,65 @@ impl Ident {
if src.override_ =>
{
if !orig_src.virtual_ {
let err =
TransitionError::NonVirtualOverride { name: name };
let err = TransitionError::NonVirtualOverride(name, span);
return Err((self, err));
}
if orig_kind != &kind {
let err = TransitionError::VirtualOverrideKind {
// TODO: defer lookup to error display
name: name,
existing: orig_kind.clone(),
given: kind.clone(),
};
let err = TransitionError::VirtualOverrideKind(
name,
orig_kind.clone(),
(kind.clone(), span),
);
return Err((self, err));
}
// Ensure that virtual flags are cleared to prohibit
// override-overrides. The compiler should do this; this is
// just an extra layer of defense.
// override-overrides.
// The compiler should do this;
// this is just an extra layer of defense.
src.virtual_ = false;
// Note that this has the effect of clearing fragments if we
// originally were in state `Ident::IdentFragment`.
Ok(Ident::Ident(name, kind, src))
// originally were in state `Ident::IdentFragment`.
Ok(Ident::Ident(name.map_span(|_| span), kind, src))
}
// If we encountered the override _first_, flip the context by
// declaring a new identifier and trying to override that.
// If we encountered the override _first_,
// flip the context by declaring a new identifier and trying
// to override that.
Ident::Ident(name, orig_kind, orig_src) if orig_src.override_ => {
Self::declare(name)
.resolve(kind, src)?
.resolve(orig_kind, orig_src)
.resolve(name.span(), kind, src)?
.resolve(span, orig_kind, orig_src)
}
// Same as above, but for fragments, we want to keep the
// _original override_ fragment.
// Same as above,
// but for fragments,
// we want to keep the _original override_ fragment.
Ident::IdentFragment(name, orig_kind, orig_src, orig_text)
if orig_src.override_ =>
{
Self::declare(name)
.resolve(kind, src)?
.resolve(orig_kind, orig_src)?
.resolve(name.span(), kind, src)?
.resolve(span, orig_kind, orig_src)?
.set_fragment(orig_text)
}
Ident::Extern(name, ref orig_kind, _) => {
if orig_kind != &kind {
let err = TransitionError::ExternResolution {
name: name,
expected: orig_kind.clone(),
given: kind.clone(),
};
let err = TransitionError::ExternResolution(
name,
orig_kind.clone(),
(kind, span),
);
return Err((self, err));
}
Ok(Ident::Ident(name, kind, src))
Ok(Ident::Ident(name.map_span(|_| span), kind, src))
}
// These represent the prologue and epilogue of maps.
@ -246,10 +275,13 @@ impl Ident {
..,
) => Ok(self),
Ident::Missing(name) => Ok(Ident::Ident(name, kind, src)),
Ident::Missing(name) => {
Ok(Ident::Ident(name.map_span(|_| span), kind, src))
}
// TODO: Remove guards and catch-all for exhaustiveness check.
_ => {
let err = TransitionError::Redeclare { name: self.name() };
let err = TransitionError::Redeclare(self.name(), span);
Err((self, err))
}
@ -273,16 +305,10 @@ impl Ident {
/// considered to be unresolved.
pub fn resolved(&self) -> Result<&Ident, UnresolvedError> {
match self {
Ident::Missing(name) => {
Err(UnresolvedError::Missing { name: *name })
}
Ident::Missing(name) => Err(UnresolvedError::Missing(*name)),
Ident::Extern(name, ref kind, ref src) => {
Err(UnresolvedError::Extern {
name: *name,
kind: kind.clone(),
pkg_name: src.pkg_name,
})
Ident::Extern(name, ref kind, _) => {
Err(UnresolvedError::Extern(*name, kind.clone()))
}
Ident::Ident(..) | Ident::IdentFragment(..) => Ok(self),
@ -306,18 +332,21 @@ impl Ident {
/// See for example [`Ident::Extern`].
pub fn extern_(
self,
span: Span,
kind: IdentKind,
src: Source,
) -> TransitionResult<Ident> {
match self.kind() {
None => Ok(Ident::Extern(self.name(), kind, src)),
None => {
Ok(Ident::Extern(self.name().map_span(|_| span), kind, src))
}
Some(cur_kind) => {
if cur_kind != &kind {
let err = TransitionError::ExternResolution {
name: self.name(),
expected: kind.clone(),
given: cur_kind.clone(),
};
let err = TransitionError::ExternResolution(
self.name(),
cur_kind.clone(),
(kind, span),
);
return Err((self, err));
}
@ -366,10 +395,8 @@ impl Ident {
) => Ok(self),
_ => {
let msg =
format!("identifier is not a Ident::Ident): {:?}", self,);
Err((self, TransitionError::BadFragmentDest { name: msg }))
let name = self.name();
Err((self, TransitionError::BadFragmentDest(name)))
}
}
}
@ -381,73 +408,66 @@ impl Ident {
pub enum TransitionError {
/// Attempted to redeclare a concrete, non-virtual identifier without an
/// override.
Redeclare { name: SymbolId },
Redeclare(SPair, Span),
/// Extern resolution failure.
///
/// An extern could not be resolved because the provided identifier had
/// a type that is incompatible with the extern definition.
ExternResolution {
name: SymbolId,
expected: IdentKind,
given: IdentKind,
},
///
// TODO: Need more granular spans for `IdentKind`.
ExternResolution(SPair, IdentKind, (IdentKind, Span)),
/// Attempt to override a non-virtual identifier.
NonVirtualOverride { name: SymbolId },
NonVirtualOverride(SPair, Span),
/// Overriding a virtual identifier failed due to an incompatible
/// [`IdentKind`].
VirtualOverrideKind {
name: SymbolId,
existing: IdentKind,
given: IdentKind,
},
// TODO: More granular spans for kind.
VirtualOverrideKind(SPair, IdentKind, (IdentKind, Span)),
/// The provided identifier is not in a state that is permitted to
/// receive a fragment.
///
/// See [`Ident::set_fragment`].
BadFragmentDest { name: String },
BadFragmentDest(SPair),
}
impl std::fmt::Display for TransitionError {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
use TransitionError::*;
match self {
Self::Redeclare { name } => write!(
Redeclare(name, _) => write!(
fmt,
"cannot redeclare identifier `{}`",
name,
"cannot redeclare identifier {}",
TtQuote::wrap(name),
),
Self::ExternResolution {
name,
expected,
given,
} => write!(
ExternResolution(name, expected, (given, _)) => write!(
fmt,
"extern `{}` of type `{}` is incompatible with type `{}`",
name, expected, given,
"extern {} of type {} is incompatible with type {}",
TtQuote::wrap(name),
TtQuote::wrap(expected),
TtQuote::wrap(given),
),
Self::NonVirtualOverride { name } => write!(
NonVirtualOverride(name, _) => write!(
fmt,
"non-virtual identifier `{}` cannot be overridden",
name,
"non-virtual identifier {} cannot be overridden",
TtQuote::wrap(name),
),
Self::VirtualOverrideKind {
name,
existing,
given,
} => write!(
VirtualOverrideKind(name, existing, (given, _)) => write!(
fmt,
"virtual identifier `{}` of type `{}` cannot be overridden with type `{}`",
name, existing, given,
"virtual identifier {} of type {} cannot be overridden with type {}",
TtQuote::wrap(name),
TtQuote::wrap(existing),
TtQuote::wrap(given),
),
Self::BadFragmentDest{name: msg} => {
write!(fmt, "bad fragment destination: {}", msg)
BadFragmentDest(name) => {
write!(fmt, "bad fragment destination: {}", TtQuote::wrap(name))
}
}
}
@ -459,41 +479,100 @@ impl std::error::Error for TransitionError {
}
}
impl Diagnostic for TransitionError {
fn describe(&self) -> Vec<crate::diagnose::AnnotatedSpan> {
use TransitionError::*;
match self {
Redeclare(name, sdup) => vec![
name.note("first declaration was found here"),
sdup.error(format!("cannot redeclare {}", TtQuote::wrap(name))),
sdup.help("identifiers in TAME are immutable and can"),
sdup.help(" only be associated with one definition."),
],
ExternResolution(name, expected, (given, sresolve)) => vec![
name.note(format!(
"extern {} declared here with type {}",
TtQuote::wrap(name),
TtQuote::wrap(expected),
)),
sresolve.error(format!(
"attempted to resolve extern {} with incompatible type {}",
TtQuote::wrap(name),
TtQuote::wrap(given),
)),
],
// TODO: Does this make a lie out of `Redeclare`'s help?
NonVirtualOverride(name, soverride) => vec![
name.note("attempting to override this non-virtual identifier"),
soverride.error(format!(
"cannot override {} because the first declaration \
was not virtual",
TtQuote::wrap(name),
)),
// TODO: let's see what type of help text will be useful
// once we've decided what is to be done with virtual
// identifiers generally
],
VirtualOverrideKind(name, orig, (given, soverride)) => vec![
name.note(format!(
"attempting to override this identifier of type {}",
TtQuote::wrap(orig),
)),
soverride.error(format!(
"type of this override is {}, but {} was expected",
TtQuote::wrap(given),
TtQuote::wrap(orig),
)),
],
BadFragmentDest(name) => vec![
name.internal_error(
"identifier {} cannot be assigned a text fragment",
),
name.help(
"the term 'text fragment' refers to compiled code from an",
),
name.help(" object file; this error should never occur."),
],
}
}
}
/// Resolved identifier was expected.
#[derive(Clone, Debug, PartialEq)]
pub enum UnresolvedError {
/// Expected identifier is missing and nothing about it is known.
Missing { name: SymbolId },
///
/// The span represents the first reference to this identifier that
/// caused it to be added to the graph;
/// it is not necessarily the _only_ reference.
/// When reporting errors based on references to unknown identifiers,
/// keep this in mind to avoid duplicates.
Missing(SPair),
/// Expected identifier has not yet been resolved with a concrete
/// definition.
Extern {
/// Identifier name.
name: SymbolId,
/// Expected identifier type.
kind: IdentKind,
/// Name of package where the extern was defined.
pkg_name: Option<SymbolId>,
},
Extern(SPair, IdentKind),
}
impl std::fmt::Display for UnresolvedError {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
use UnresolvedError::*;
match self {
UnresolvedError::Missing { name } => {
write!(fmt, "missing expected identifier `{}`", name,)
Missing(name) => {
write!(fmt, "unknown identifier {}", TtQuote::wrap(name))
}
UnresolvedError::Extern {
name,
kind,
pkg_name,
} => write!(
Extern(name, kind) => write!(
fmt,
"unresolved extern `{}` of type `{}`, declared in `{}`",
name,
kind,
pkg_name.map(|s| s.lookup_str()).unwrap_or(&"<unknown>"),
"unresolved extern {} of type {}",
TtQuote::wrap(name),
TtQuote::wrap(kind),
),
}
}
@ -505,6 +584,38 @@ impl std::error::Error for UnresolvedError {
}
}
impl Diagnostic for UnresolvedError {
fn describe(&self) -> Vec<crate::diagnose::AnnotatedSpan> {
use UnresolvedError::*;
match self {
// TODO: Do we want a single span, or should errors also be
// thrown for every reference to missing?
Missing(name) => vec![name.error(format!(
"identifier {} has not been defined",
TtQuote::wrap(name),
))],
Extern(name, _kind) => vec![
name.error(format!(
"no imported package provided a \
compatible definition for {}",
TtQuote::wrap(name),
)),
name.help(
"an extern declares an identifier so that it may be used,"
),
name.help(
" but with the expectation that some imported package will"
),
name.help(
" later provide a concrete definition for it."
)
],
}
}
}
/// Compiled fragment for identifier.
///
/// This represents the text associated with an identifier.

File diff suppressed because it is too large Load Diff

View File

@ -28,7 +28,7 @@ use super::xmle::{
use crate::{
asg::{
air::{Air, AirAggregate},
Asg, AsgError, DefaultAsg, Ident, Object,
Asg, AsgError, DefaultAsg, Object,
},
diagnose::{AnnotatedSpan, Diagnostic},
fs::{
@ -95,7 +95,7 @@ pub fn xmle(package_path: &str, output: &str) -> Result<(), TameldError> {
.get(obj)
.unwrap()
.as_ident_ref()
.map(Ident::name)
.map(|ident| ident.name().symbol())
})
.collect();
@ -147,7 +147,7 @@ pub fn graphml(package_path: &str, output: &str) -> Result<(), TameldError> {
};
(
n.name().lookup_str().into(),
n.name().symbol().lookup_str().into(),
n.kind().unwrap().as_sym(),
format!("{}", generated),
)
@ -412,9 +412,9 @@ impl Diagnostic for TameldError {
Self::XmloLowerError(e) => e.describe(),
Self::AirLowerError(e) => e.describe(),
Self::FinalizeError(e) => e.describe(),
Self::SortError(e) => e.describe(),
Self::Io(_)
| Self::SortError(_)
| Self::XirWriterError(_)
| Self::CycleError(_)
| Self::Fmt(_) => vec![],

View File

@ -24,6 +24,7 @@
use super::section::{SectionsError, XmleSections};
use crate::{
asg::{Asg, Ident, IdentKind, Object, ObjectRef},
diagnose::Diagnostic,
sym::{st, GlobalSymbolResolve, SymbolId},
};
use petgraph::visit::DfsPostOrder;
@ -171,6 +172,18 @@ impl std::error::Error for SortError {
}
}
impl Diagnostic for SortError {
fn describe(&self) -> Vec<crate::diagnose::AnnotatedSpan> {
use SortError::*;
match self {
SectionsError(e) => e.describe(),
// TODO
Cycles(_) => vec![],
}
}
}
#[cfg(test)]
mod test {
use super::*;
@ -178,6 +191,8 @@ mod test {
asg::{FragmentText, Ident, Source},
ld::xmle::{section::PushResult, Sections},
num::{Dim, Dtype},
parse::util::SPair,
span::dummy::*,
sym::GlobalSymbolIntern,
};
@ -188,28 +203,28 @@ mod test {
let text = "dummy fragment".intern();
{
let sym = st::L_MAP_UUUHEAD.into();
let sym = SPair(st::L_MAP_UUUHEAD.into(), S1);
asg.declare(sym, IdentKind::MapHead, Default::default())
.unwrap();
asg.set_fragment(sym, text).unwrap();
}
{
let sym = st::L_MAP_UUUTAIL.into();
let sym = SPair(st::L_MAP_UUUTAIL.into(), S2);
asg.declare(sym, IdentKind::MapTail, Default::default())
.unwrap();
asg.set_fragment(sym, text).unwrap();
}
{
let sym = st::L_RETMAP_UUUHEAD.into();
let sym = SPair(st::L_RETMAP_UUUHEAD.into(), S3);
asg.declare(sym, IdentKind::RetMapHead, Default::default())
.unwrap();
asg.set_fragment(sym, text).unwrap();
}
{
let sym = st::L_RETMAP_UUUTAIL.into();
let sym = SPair(st::L_RETMAP_UUUTAIL.into(), S4);
asg.declare(sym, IdentKind::RetMapTail, Default::default())
.unwrap();
asg.set_fragment(sym, text).unwrap();
@ -261,13 +276,21 @@ mod test {
// Add them in an unsorted order.
let adep = asg
.declare("adep".into(), IdentKind::Meta, Default::default())
.declare(
SPair("adep".into(), S1),
IdentKind::Meta,
Default::default(),
)
.unwrap();
let a = asg
.declare("a".into(), IdentKind::Meta, Default::default())
.declare(SPair("a".into(), S2), IdentKind::Meta, Default::default())
.unwrap();
let adepdep = asg
.declare("adepdep".into(), IdentKind::Meta, Default::default())
.declare(
SPair("adepdep".into(), S3),
IdentKind::Meta,
Default::default(),
)
.unwrap();
asg.add_dep(a, adep);
@ -307,8 +330,8 @@ mod test {
fn graph_sort_missing_node() -> SortResult<()> {
let mut asg = make_asg();
let sym = "sym".intern();
let dep = "dep".intern();
let sym = SPair("sym".into(), S1);
let dep = SPair("dep".into(), S2);
let sym_node = asg
.declare(
@ -347,8 +370,8 @@ mod test {
// "empty" (it has the head/tail {ret,}map objects)
let asg_empty = make_asg();
let sym = "sym".intern();
let dep = "dep".intern();
let sym = SPair("sym".into(), S1);
let dep = SPair("dep".into(), S2);
asg_nonempty_no_roots.add_dep_lookup(sym, dep);
@ -364,8 +387,8 @@ mod test {
fn graph_sort_simple_cycle() -> SortResult<()> {
let mut asg = make_asg();
let sym = "sym".intern();
let dep = "dep".intern();
let sym = SPair("sym".into(), S1);
let dep = SPair("dep".into(), S2);
let sym_node = asg
.declare(
@ -414,10 +437,10 @@ mod test {
fn graph_sort_two_simple_cycles() -> SortResult<()> {
let mut asg = make_asg();
let sym = "sym".intern();
let sym2 = "sym2".intern();
let dep = "dep".intern();
let dep2 = "dep2".intern();
let sym = SPair("sym".into(), S1);
let sym2 = SPair("sym2".into(), S2);
let dep = SPair("dep".into(), S3);
let dep2 = SPair("dep2".into(), S4);
let sym_node = asg
.declare(
@ -494,8 +517,8 @@ mod test {
fn graph_sort_no_cycle_with_edge_to_same_node() -> SortResult<()> {
let mut asg = make_asg();
let sym = "sym".intern();
let dep = "dep".intern();
let sym = SPair("sym".into(), S1);
let dep = SPair("dep".into(), S2);
let sym_node = asg
.declare(
@ -540,9 +563,9 @@ mod test {
fn graph_sort_cycle_with_a_few_steps() -> SortResult<()> {
let mut asg = make_asg();
let sym1 = "sym1".intern();
let sym2 = "sym2".intern();
let sym3 = "sym3".intern();
let sym1 = SPair("sym1".into(), S1);
let sym2 = SPair("sym2".into(), S2);
let sym3 = SPair("sym3".into(), S3);
let sym1_node = asg
.declare(
@ -605,9 +628,9 @@ mod test {
) -> SortResult<()> {
let mut asg = make_asg();
let sym1 = "sym1".intern();
let sym2 = "sym2".intern();
let sym3 = "sym3".intern();
let sym1 = SPair("sym1".into(), S1);
let sym2 = SPair("sym2".into(), S2);
let sym3 = SPair("sym3".into(), S3);
let sym1_node = asg
.declare(
@ -669,9 +692,9 @@ mod test {
fn graph_sort_cyclic_bookended_by_functions() -> SortResult<()> {
let mut asg = make_asg();
let sym1 = "sym1".intern();
let sym2 = "sym2".intern();
let sym3 = "sym3".intern();
let sym1 = SPair("sym1".into(), S1);
let sym2 = SPair("sym2".into(), S2);
let sym3 = SPair("sym3".into(), S3);
let sym1_node = asg
.declare(
@ -733,8 +756,8 @@ mod test {
fn graph_sort_cyclic_function_ignored() -> SortResult<()> {
let mut asg = make_asg();
let sym = "sym".intern();
let dep = "dep".intern();
let sym = SPair("sym".into(), S1);
let dep = SPair("dep".into(), S2);
let sym_node = asg
.declare(
@ -779,9 +802,9 @@ mod test {
fn graph_sort_cyclic_function_is_bookended() -> SortResult<()> {
let mut asg = make_asg();
let sym1 = "sym1".intern();
let sym2 = "sym2".intern();
let sym3 = "sym3".intern();
let sym1 = SPair("sym1".into(), S1);
let sym2 = SPair("sym2".into(), S2);
let sym3 = SPair("sym3".into(), S3);
let sym1_node = asg
.declare(
@ -843,9 +866,9 @@ mod test {
fn graph_sort_ignore_non_linked() -> SortResult<()> {
let mut asg = make_asg();
let sym = "sym".intern();
let dep = "dep".intern();
let ignored = "ignored".intern();
let sym = SPair("sym".into(), S1);
let dep = SPair("dep".into(), S2);
let ignored = SPair("ignored".into(), S3);
let sym_node = asg
.declare(

View File

@ -26,8 +26,13 @@
//! which places the relocatable object code fragments in the order
//! necessary for execution.
use crate::asg::{Ident, IdentKind, UnresolvedError};
use crate::sym::SymbolId;
use crate::{
asg::{Ident, IdentKind, UnresolvedError},
diagnose::{Annotate, Diagnostic},
fmt::{DisplayWrapper, TtQuote},
parse::util::SPair,
sym::SymbolId,
};
use fxhash::FxHashSet;
use std::mem::take;
use std::result::Result;
@ -108,7 +113,6 @@ impl<'a> XmleSections<'a> for Sections<'a> {
fn push(&mut self, ident: &'a Ident) -> PushResult {
self.deps.push(ident);
// TODO: This cannot happen, so use an API without Option.
let name = ident.name();
let frag = ident.fragment();
@ -191,7 +195,7 @@ impl<'a> XmleSections<'a> for Sections<'a> {
}
fn expect_frag(
ident_name: SymbolId,
ident_name: SPair,
frag: Option<SymbolId>,
) -> PushResult<SymbolId> {
frag.ok_or(SectionsError::MissingFragment(ident_name))
@ -211,7 +215,7 @@ pub enum SectionsError {
UnresolvedObject(UnresolvedError),
/// Identifier is missing an expected text fragment.
MissingFragment(SymbolId),
MissingFragment(SPair),
/// The kind of an object encountered during sorting could not be
/// determined.
@ -220,7 +224,7 @@ pub enum SectionsError {
/// sections.
/// It should never be the case that a resolved object has no kind,
/// so this likely represents a compiler bug.
MissingObjectKind(SymbolId),
MissingObjectKind(SPair),
}
impl From<UnresolvedError> for SectionsError {
@ -240,28 +244,57 @@ impl std::error::Error for SectionsError {
impl std::fmt::Display for SectionsError {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
use SectionsError::*;
match self {
Self::UnresolvedObject(err) => err.fmt(fmt),
Self::MissingFragment(name) => write!(
UnresolvedObject(err) => err.fmt(fmt),
MissingFragment(name) => write!(
fmt,
"missing text fragment for object `{}` (this may be a compiler bug!)",
name,
"missing text fragment for object identified by {}",
TtQuote::wrap(name),
),
Self::MissingObjectKind(name) => write!(
MissingObjectKind(name) => write!(
fmt,
"missing object kind for object `{}` (this may be a compiler bug!)",
name,
"missing object kind for object identified by {}",
TtQuote::wrap(name),
),
}
}
}
impl Diagnostic for SectionsError {
fn describe(&self) -> Vec<crate::diagnose::AnnotatedSpan> {
use SectionsError::*;
match self {
UnresolvedObject(e) => e.describe(),
MissingFragment(name) => vec![
name.internal_error(
"text fragment for this object cannot be found",
),
name.help("this means that the compiler failed to produce"),
name.help(" object code associated with this identifier."),
],
MissingObjectKind(name) => vec![
name.internal_error(
"the kind of object for this identifier is unknown",
),
name.help("this means that the compiler failed to output"),
name.help(" complete type information for this identifier."),
],
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::asg::{Ident, IdentKind, Source};
use crate::num::{Dim, Dtype};
use crate::sym::GlobalSymbolIntern;
use crate::{
asg::{Ident, IdentKind, Source},
num::{Dim, Dtype},
span::dummy::*,
sym::GlobalSymbolIntern,
};
type Sut<'a> = Sections<'a>;
@ -282,7 +315,7 @@ mod test {
let mut sut = Sut::new();
let a = Ident::IdentFragment(
"a".intern(),
SPair("a".into(), S1),
IdentKind::Const(Dim::Scalar, Dtype::Integer),
Default::default(),
"fraga".intern(),
@ -290,7 +323,7 @@ mod test {
// Different section than a, to be sure that we still add it.
let b = Ident::IdentFragment(
"b".intern(),
SPair("b".into(), S2),
IdentKind::MapHead,
Default::default(),
"fragb".intern(),
@ -311,19 +344,19 @@ mod test {
let mut sut = Sut::new();
let cgen = Ident::Ident(
"cgen".intern(),
SPair("cgen".into(), S1),
IdentKind::Cgen(Dim::Vector),
Default::default(),
);
let gen = Ident::Ident(
"gen".intern(),
SPair("gen".into(), S2),
IdentKind::Gen(Dim::Vector, Dtype::Integer),
Default::default(),
);
let lparam = Ident::Ident(
"lparam".intern(),
SPair("lparam".into(), S3),
IdentKind::Lparam(Dim::Vector, Dtype::Integer),
Default::default(),
);
@ -351,7 +384,7 @@ mod test {
let mut sut_b = Sections::new();
let a = Ident::IdentFragment(
"a".intern(),
SPair("a".into(), S1),
IdentKind::Map,
Source {
from: Some("froma".intern()),
@ -361,7 +394,7 @@ mod test {
);
let b = Ident::IdentFragment(
"a".intern(),
SPair("a".into(), S2),
IdentKind::Map,
Source {
from: Some("fromb".intern()),
@ -395,7 +428,7 @@ mod test {
($sut:ident, { $($name:ident: $kind:expr,)* }) => {
$(
let $name = Ident::IdentFragment(
stringify!($name).intern(),
SPair(stringify!($name).into(), S1),
$kind,
Default::default(),
stringify!($kind).intern(), // fragment

View File

@ -125,7 +125,7 @@ impl<'a> DepListIter<'a> {
obj,
),
}
}).and_then(|(sym, kind, src)| {
}).and_then(|(name, kind, src)| {
self.toks.push(Token::Close(None, CloseSpan::empty(LSPAN)));
self.toks_push_attr(QN_DESC, src.desc);
@ -145,7 +145,8 @@ impl<'a> DepListIter<'a> {
false => None,
});
self.toks_push_attr(QN_NAME, Some(sym));
// TODO: Note that we're not yet writing any span information.
self.toks_push_attr(QN_NAME, Some(name.symbol()));
self.toks_push_obj_attrs(kind);
Some(Token::Open(QN_P_SYM, OpenSpan::without_name_span(LSPAN)))

View File

@ -18,16 +18,16 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use super::*;
use crate::ld::xmle::section::PushResult;
use crate::ld::xmle::Sections;
use crate::num::{Dim, Dtype};
use crate::sym::{GlobalSymbolIntern, GlobalSymbolResolve};
use crate::xir::tree::merge_attr_fragments;
use crate::{
asg::{IdentKind, Source},
ld::xmle::{section::PushResult, Sections},
num::{Dim, Dtype},
parse::util::SPair,
span::dummy::*,
sym::{GlobalSymbolIntern, GlobalSymbolResolve},
xir::{
pred::{not, open},
tree::parser_from,
tree::{merge_attr_fragments, parser_from},
},
};
use std::collections::HashSet;
@ -79,7 +79,7 @@ fn test_writes_deps() -> TestResult {
let objs = [
Ident::Ident(
"cgentest".intern(),
SPair("cgentest".into(), S1),
IdentKind::Cgen(Dim::Vector),
Source {
yields: Some("yieldsValue".intern()),
@ -90,74 +90,82 @@ fn test_writes_deps() -> TestResult {
},
),
Ident::Ident(
"classtest".intern(),
SPair("classtest".into(), S2),
IdentKind::Class(Dim::Matrix),
Default::default(),
),
Ident::Ident(
"consttest".intern(),
SPair("consttest".into(), S3),
IdentKind::Const(Dim::Scalar, Dtype::Boolean),
Default::default(),
),
Ident::Ident(
"functest".intern(),
SPair("functest".into(), S4),
IdentKind::Func(Dim::Matrix, Dtype::Integer),
Default::default(),
),
Ident::Ident(
"gentest".intern(),
SPair("gentest".into(), S5),
IdentKind::Gen(Dim::Matrix, Dtype::Boolean),
Default::default(),
),
Ident::Ident(
"lparamtest".intern(),
SPair("lparamtest".into(), S6),
IdentKind::Gen(Dim::Matrix, Dtype::Float),
Default::default(),
),
Ident::Ident(
"paramtest".intern(),
SPair("paramtest".into(), S7),
IdentKind::Gen(Dim::Scalar, Dtype::Integer),
Default::default(),
),
Ident::Ident(
"ratetest".intern(),
SPair("ratetest".into(), S8),
IdentKind::Rate(Dtype::Integer),
Default::default(),
),
Ident::Ident("tpltest".intern(), IdentKind::Tpl, Default::default()),
Ident::Ident(
"typetest".intern(),
SPair("tpltest".into(), S9),
IdentKind::Tpl,
Default::default(),
),
Ident::Ident(
SPair("typetest".into(), S1),
IdentKind::Type(Dtype::Integer),
Default::default(),
),
Ident::Ident(
"mapheadtest".intern(),
SPair("mapheadtest".into(), S2),
IdentKind::MapHead,
Default::default(),
),
Ident::Ident("maptest".intern(), IdentKind::Map, Default::default()),
Ident::Ident(
"maptailtest".intern(),
SPair("maptest".into(), S3),
IdentKind::Map,
Default::default(),
),
Ident::Ident(
SPair("maptailtest".into(), S4),
IdentKind::MapTail,
Default::default(),
),
Ident::Ident(
"retmapheadtest".intern(),
SPair("retmapheadtest".into(), S5),
IdentKind::RetMapHead,
Default::default(),
),
Ident::Ident(
"retmaptest".intern(),
SPair("retmaptest".into(), S6),
IdentKind::RetMap,
Default::default(),
),
Ident::Ident(
"retmaptailtest".intern(),
SPair("retmaptailtest".into(), S7),
IdentKind::RetMapTail,
Default::default(),
),
Ident::Ident(
"metatest".intern(),
SPair("metatest".into(), S8),
IdentKind::Meta,
Source {
desc: Some("test desc".intern()),
@ -165,7 +173,7 @@ fn test_writes_deps() -> TestResult {
},
),
Ident::Ident(
"worksheettest".intern(),
SPair("worksheettest".into(), S9),
IdentKind::Worksheet,
Default::default(),
),
@ -246,7 +254,10 @@ fn test_writes_deps() -> TestResult {
let ident = &objs[i];
let attrs = ele.attrs();
assert_eq!(attrs.find(QN_NAME).map(|a| a.value()), Some(ident.name()),);
assert_eq!(
attrs.find(QN_NAME).map(|a| a.value()),
Some(ident.name().symbol()),
);
assert_eq!(
attrs.find(QN_TYPE).map(|a| a.value()),
@ -351,7 +362,7 @@ fn test_writes_map_froms() -> TestResult {
let relroot = "relroot-deps".intern();
let a = Ident::IdentFragment(
"a".intern(),
SPair("a".into(), S1),
IdentKind::Map,
Source {
from: Some("froma".intern()),
@ -361,7 +372,7 @@ fn test_writes_map_froms() -> TestResult {
);
let b = Ident::IdentFragment(
"a".intern(),
SPair("a".into(), S2),
IdentKind::Map,
Source {
from: Some("fromb".intern()),
@ -425,14 +436,14 @@ macro_rules! test_exec_sec {
let frag_b = "b fragment".intern();
let a = Ident::IdentFragment(
"a".intern(),
SPair("a".into(), S1),
$type,
Default::default(),
frag_a,
);
let b = Ident::IdentFragment(
"b".intern(),
SPair("b".into(), S2),
$type,
Default::default(),
frag_b,

View File

@ -30,7 +30,7 @@ use crate::{
asg::{air::Air, IdentKind, Source},
diagnose::{AnnotatedSpan, Diagnostic},
obj::xmlo::{SymAttrs, SymType},
parse::{ParseState, ParseStatus, Transition, Transitionable},
parse::{util::SPair, ParseState, ParseStatus, Transition, Transitionable},
span::Span,
sym::SymbolId,
};
@ -87,13 +87,13 @@ impl XmloAirContext {
type PackageName = SymbolId;
/// State machine for lowering into the [`Asg`](crate::asg::Asg) via
/// [`AirToken`].
/// [`Air`].
#[derive(Debug, PartialEq, Eq, Default)]
pub enum XmloToAir {
#[default]
PackageExpected,
Package(PackageName, Span),
SymDep(PackageName, Span, SymbolId),
SymDep(PackageName, Span, SPair),
/// End of header (EOH) reached.
Done(Span),
}
@ -137,9 +137,14 @@ impl ParseState for XmloToAir {
// here.
(
Package(pkg_name, span),
XmloToken::PkgEligClassYields(pkg_elig, _),
XmloToken::PkgEligClassYields(pkg_elig, elig_span),
) => {
Transition(Package(pkg_name, span)).ok(Air::IdentRoot(pkg_elig))
// The span for this is a bit awkward,
// given that rooting is automatic,
// but it it should never have to be utilized in
// diagnostics unless there is a compiler bug.
Transition(Package(pkg_name, span))
.ok(Air::IdentRoot(SPair(pkg_elig, elig_span)))
}
(
@ -152,13 +157,15 @@ impl ParseState for XmloToAir {
(
Package(pkg_name, span) | SymDep(pkg_name, span, ..),
XmloToken::SymDepStart(sym, _),
) => Transition(SymDep(pkg_name, span, sym)).incomplete(),
XmloToken::SymDepStart(sym, sym_span),
) => Transition(SymDep(pkg_name, span, SPair(sym, sym_span)))
.incomplete(),
(SymDep(pkg_name, span, sym), XmloToken::Symbol(dep_sym, _)) => {
Transition(SymDep(pkg_name, span, sym))
.ok(Air::IdentDep(sym, dep_sym))
}
(
SymDep(pkg_name, span, sym),
XmloToken::Symbol(dep_sym, dep_span),
) => Transition(SymDep(pkg_name, span, sym))
.ok(Air::IdentDep(sym, SPair(dep_sym, dep_span))),
(
Package(pkg_name, span),
@ -176,7 +183,7 @@ impl ParseState for XmloToAir {
(
Package(pkg_name, span),
XmloToken::SymDecl(sym, attrs, _span),
XmloToken::SymDecl(sym, attrs, sym_span),
) => {
let extern_ = attrs.extern_;
@ -201,11 +208,15 @@ impl ParseState for XmloToAir {
if extern_ {
Ok(ParseStatus::Object(Air::IdentExternDecl(
sym, kindval, src,
SPair(sym, sym_span),
kindval,
src,
)))
} else {
Ok(ParseStatus::Object(Air::IdentDecl(
sym, kindval, src,
SPair(sym, sym_span),
kindval,
src,
)))
}
})
@ -214,9 +225,9 @@ impl ParseState for XmloToAir {
(
Package(pkg_name, span) | SymDep(pkg_name, span, _),
XmloToken::Fragment(sym, text, _),
XmloToken::Fragment(sym, text, frag_span),
) => Transition(Package(pkg_name, span))
.ok(Air::IdentFragment(sym, text)),
.ok(Air::IdentFragment(SPair(sym, frag_span), text)),
// We don't need to read any further than the end of the
// header (symtable, sym-deps, fragments).
@ -400,7 +411,7 @@ mod test {
num::{Dim, Dtype},
obj::xmlo::{SymAttrs, SymType},
parse::Parsed,
span::{dummy::*, UNKNOWN_SPAN},
span::dummy::*,
sym::GlobalSymbolIntern,
};
@ -444,7 +455,7 @@ mod test {
assert_eq!(
Ok(vec![
Parsed::Incomplete, // PkgName
Parsed::Object(Air::IdentRoot(elig_sym)),
Parsed::Object(Air::IdentRoot(SPair(elig_sym, S2))),
Parsed::Incomplete, // Eoh
]),
Sut::parse(toks.into_iter()).collect(),
@ -469,8 +480,14 @@ mod test {
Ok(vec![
Parsed::Incomplete, // PkgName
Parsed::Incomplete, // SymDepStart
Parsed::Object(Air::IdentDep(sym_from, sym_to1)),
Parsed::Object(Air::IdentDep(sym_from, sym_to2)),
Parsed::Object(Air::IdentDep(
SPair(sym_from, S2),
SPair(sym_to1, S3)
)),
Parsed::Object(Air::IdentDep(
SPair(sym_from, S2),
SPair(sym_to2, S4)
)),
Parsed::Incomplete, // Eoh
]),
Sut::parse(toks.into_iter()).collect(),
@ -578,7 +595,7 @@ mod test {
assert_eq!(Some(Ok(Parsed::Incomplete)), sut.next()); // PkgName
assert_eq!(
Some(Ok(Parsed::Object(Air::IdentExternDecl(
sym_extern,
SPair(sym_extern, S1),
IdentKind::Meta,
Source {
pkg_name: None,
@ -589,7 +606,7 @@ mod test {
);
assert_eq!(
Some(Ok(Parsed::Object(Air::IdentDecl(
sym_non_extern,
SPair(sym_non_extern, S2),
IdentKind::Meta,
Source {
pkg_name: None,
@ -600,7 +617,7 @@ mod test {
);
assert_eq!(
Some(Ok(Parsed::Object(Air::IdentDecl(
sym_map,
SPair(sym_map, S3),
IdentKind::Map,
Source {
pkg_name: None,
@ -611,7 +628,7 @@ mod test {
);
assert_eq!(
Some(Ok(Parsed::Object(Air::IdentDecl(
sym_retmap,
SPair(sym_retmap, S4),
IdentKind::RetMap,
Source {
pkg_name: None,
@ -653,7 +670,7 @@ mod test {
ty: Some(SymType::Meta),
..Default::default()
},
UNKNOWN_SPAN,
S2,
),
XmloToken::Eoh(S1),
];
@ -662,7 +679,7 @@ mod test {
Ok(vec![
Parsed::Incomplete, // PkgName
Parsed::Object(Air::IdentDecl(
sym,
SPair(sym, S2),
IdentKind::Meta,
Source {
pkg_name: Some(pkg_name),
@ -696,7 +713,7 @@ mod test {
ty: Some(SymType::Meta),
..Default::default()
},
UNKNOWN_SPAN,
S2,
),
XmloToken::Eoh(S1),
];
@ -705,7 +722,7 @@ mod test {
Ok(vec![
Parsed::Incomplete, // PkgName
Parsed::Object(Air::IdentDecl(
sym,
SPair(sym, S2),
IdentKind::Meta,
Source {
pkg_name: Some(pkg_name), // Name inherited
@ -748,7 +765,7 @@ mod test {
assert_eq!(
Ok(vec![
Parsed::Incomplete, // PkgName
Parsed::Object(Air::IdentFragment(sym, frag)),
Parsed::Object(Air::IdentFragment(SPair(sym, S2), frag)),
Parsed::Incomplete, // Eoh
]),
Sut::parse(toks.into_iter()).collect(),

View File

@ -70,6 +70,10 @@ pub enum XmloToken {
/// A symbol reference whose interpretation is dependent on the current
/// state.
///
/// The span currently references the object file itself,
/// but in the future this will resolve to a span stored within the
/// object file representing the source location of this symbol.
Symbol(SymbolId, Span),
/// Text (compiled code) fragment for a given symbol.
@ -364,8 +368,9 @@ impl ParseState for SymtableState {
}
// Symbol @name found.
(Sym(span, None, attrs), Xirf::Attr(Attr(QN_NAME, name, _))) => {
Transition(Sym(span, Some(name), attrs)).incomplete()
(Sym(_, None, attrs), Xirf::Attr(Attr(QN_NAME, name, aspan))) => {
Transition(Sym(aspan.value_span(), Some(name), attrs))
.incomplete()
}
(

View File

@ -191,7 +191,7 @@ macro_rules! symtable_tests {
#[doc=stringify!($key)]
Parsed::Incomplete,
)*
Parsed::Object((name, expected, SSYM)),
Parsed::Object((name, expected, S3)),
]),
Err(expected) => Err(ParseError::StateError(expected)),
},
@ -345,7 +345,7 @@ fn symtable_sym_generated_true() {
Parsed::Incomplete, // Opening tag
Parsed::Incomplete, // @name
Parsed::Incomplete, // @preproc:generated
Parsed::Object((name, expected, SSYM)),
Parsed::Object((name, expected, S3)),
]),
SymtableState::parse(toks).collect(),
);
@ -385,7 +385,7 @@ fn symtable_map_from() {
Parsed::Incomplete, // <preproc:from
Parsed::Incomplete, // @name
Parsed::Incomplete, // />
Parsed::Object((name, expected, SSYM)),
Parsed::Object((name, expected, S3)),
]),
SymtableState::parse(toks).collect(),
);

View File

@ -36,7 +36,12 @@ use std::fmt::Display;
/// This newtype is required because foreign traits
/// (such as [`Display`])
/// cannot be implemented on tuples at the time of writing.
#[derive(Debug, PartialEq, Eq)]
///
/// This implements [`Copy`] because each inner type is small
/// (and itself [`Copy`]),
/// and in practice,
/// we'd just destructure it to copy each part anyway.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct SPair(pub SymbolId, pub Span);
impl SPair {
@ -61,6 +66,18 @@ impl SPair {
Self(sym, span) => Self(f(sym), span),
}
}
/// Map over the [`Span`] of the pair while retaining the associated
/// [`SymbolId`].
///
/// This operation is useful,
/// for example,
/// when resolving or overriding identifiers.
pub fn map_span(self, f: impl FnOnce(Span) -> Span) -> Self {
match self {
Self(sym, span) => Self(sym, f(span)),
}
}
}
impl Token for SPair {