Mike Gerwitz

Activist for User Freedom

aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMike Gerwitz <mike.gerwitz@ryansg.com>2020-03-26 09:24:02 -0400
committerMike Gerwitz <mike.gerwitz@ryansg.com>2020-03-26 09:24:02 -0400
commitd39ec84399b9ef71871779b69d092d60bb98cb9c (patch)
treedb7d38383af5b54737b5a310aa49b7e85ae75a2e
parent03fa2ffc0b7496b5fd596e1217a50c52c285686d (diff)
parent4051debad2381ef66cff8c44176dd768ea911ad1 (diff)
downloadtame-d39ec84399b9ef71871779b69d092d60bb98cb9c.tar.gz
tame-d39ec84399b9ef71871779b69d092d60bb98cb9c.tar.bz2
tame-d39ec84399b9ef71871779b69d092d60bb98cb9c.zip
Proper extern resolution
This properly checks identifier types when resolving externs. It also includes a bit of refactoring. Note that some of that refactoring was already merged into master. The old linker was missing some things, so there are template changes in here as well. An example of an error currently: error: extern `__retry` of type `cgen[boolean; 1]` is incompatible with type `cgen[boolean; 0]`
-rw-r--r--core/extern.xml2
-rw-r--r--core/retry.xml2
-rw-r--r--core/ui.xml8
-rw-r--r--tamer/src/bin/tameld.rs10
-rw-r--r--tamer/src/ir/asg/base.rs241
-rw-r--r--tamer/src/ir/asg/graph.rs53
-rw-r--r--tamer/src/ir/asg/ident.rs42
-rw-r--r--tamer/src/ir/asg/mod.rs4
-rw-r--r--tamer/src/ir/asg/object.rs372
-rw-r--r--tamer/src/ir/legacyir.rs11
-rw-r--r--tamer/src/ld/poc.rs16
11 files changed, 560 insertions, 201 deletions
diff --git a/core/extern.xml b/core/extern.xml
index 0eb3861..6cb3a0f 100644
--- a/core/extern.xml
+++ b/core/extern.xml
@@ -46,7 +46,7 @@
<text>1</text>
</param>
- <extern name=":class:{@as@}" type="class" dim="0"
+ <extern name=":class:{@as@}" type="class" dim="@dim@"
yields="@yields@" />
<if name="@yields@">
diff --git a/core/retry.xml b/core/retry.xml
index 069f2c6..fe78ff5 100644
--- a/core/retry.xml
+++ b/core/retry.xml
@@ -9,7 +9,7 @@
<import package="extern" />
<import package="vector/cmatch" export="true" />
- <t:classify-extern yields="__retry" />
+ <t:classify-extern yields="__retry" dim="0" />
<template name="_suggest-retry-when_" desc="Retry Rating">
<param name="@values@" desc="Rule matches" />
diff --git a/core/ui.xml b/core/ui.xml
index e0e86f8..9f2bede 100644
--- a/core/ui.xml
+++ b/core/ui.xml
@@ -137,8 +137,14 @@
<param-value name="@on@" rmunderscore="true" lower="true" />
</param>
+ <!-- TODO: this default is transitionary, since there's no
+ meaningful default -->
+ <param name="@vis-dim@" desc="Classification dimensions (default 2)">
+ <text>2</text>
+ </param>
+
<extern name="ui_q_{@on@}" type="param" dtype="integer" dim="1" />
- <extern name="@__yields@" type="cgen" dtype="boolean" dim="1" />
+ <extern name="@__yields@" type="cgen" dtype="boolean" dim="@vis-dim@" />
<all>
<match on="@__yields@" />
diff --git a/tamer/src/bin/tameld.rs b/tamer/src/bin/tameld.rs
index e3ca4b0..bf91e94 100644
--- a/tamer/src/bin/tameld.rs
+++ b/tamer/src/bin/tameld.rs
@@ -45,7 +45,15 @@ pub fn main() -> Result<(), Box<dyn Error>> {
let usage = opts.usage(&format!("Usage: {} -o OUTPUT FILE", program));
match parse_options(opts, args) {
- Ok(Command::Link(input, output)) => poc::main(&input, &output),
+ Ok(Command::Link(input, output)) => match poc::main(&input, &output) {
+ Err(e) => {
+ eprintln!("error: {}", e);
+ eprintln!("fatal: failed to link `{}`", output);
+
+ std::process::exit(1);
+ }
+ ok => ok,
+ },
Ok(Command::Usage) => {
println!("{}", usage);
std::process::exit(exitcode::OK);
diff --git a/tamer/src/ir/asg/base.rs b/tamer/src/ir/asg/base.rs
index 5aea07e..cdf3cd5 100644
--- a/tamer/src/ir/asg/base.rs
+++ b/tamer/src/ir/asg/base.rs
@@ -23,7 +23,9 @@ use super::graph::{
Asg, AsgEdge, AsgError, AsgResult, Node, ObjectRef, SortableAsg,
};
use super::ident::IdentKind;
-use super::object::{FragmentText, IdentObjectData, IdentObjectState, Source};
+use super::object::{
+ FragmentText, IdentObjectData, IdentObjectState, Source, TransitionResult,
+};
use super::Sections;
use crate::sym::Symbol;
use fixedbitset::FixedBitSet;
@@ -128,17 +130,51 @@ where
/// Lookup `ident` or add a missing identifier to the graph and return a
/// reference to it.
///
- /// See [`IdentObjectState::missing`] for more information.
- #[inline]
+ /// See [`IdentObjectState::declare`] for more information.
fn lookup_or_missing(&mut self, ident: &'i Symbol<'i>) -> ObjectRef<Ix> {
self.lookup(ident).unwrap_or_else(|| {
- let index = self.graph.add_node(Some(O::missing(ident)));
+ let index = self.graph.add_node(Some(O::declare(ident)));
self.index_identifier(ident, index);
ObjectRef(index)
})
}
+ /// Perform a state transition on an identifier.
+ ///
+ /// Look up `ident` or add a missing identifier if it does not yet exist
+ /// (see `lookup_or_missing`).
+ /// Then invoke `f` with the located identifier and replace the
+ /// identifier on the graph with the result.
+ ///
+ /// This will safely restore graph state to the original identifier
+ /// value on transition failure.
+ fn with_ident<F>(
+ &mut self,
+ name: &'i Symbol<'i>,
+ f: F,
+ ) -> AsgResult<ObjectRef<Ix>, Ix>
+ where
+ F: FnOnce(O) -> TransitionResult<O>,
+ {
+ let identi = self.lookup_or_missing(name);
+ let node = self.graph.node_weight_mut(identi.0).unwrap();
+
+ let obj = node
+ .take()
+ .expect(&format!("internal error: missing object for {}", name));
+
+ f(obj)
+ .and_then(|obj| {
+ node.replace(obj);
+ Ok(identi)
+ })
+ .or_else(|(orig, err)| {
+ node.replace(orig);
+ Err(err.into())
+ })
+ }
+
/// Check graph for cycles
///
/// We want to catch any cycles before we start using the graph.
@@ -201,45 +237,16 @@ where
kind: IdentKind,
src: Source<'i>,
) -> AsgResult<ObjectRef<Ix>, Ix> {
- if let Some(existing) = self.lookup(name) {
- let node = self.graph.node_weight_mut(existing.0).unwrap();
-
- let obj = node.take().expect(&format!(
- "internal error: missing object for {}",
- name
- ));
-
- // TODO: test inconsistent state (fixed)
- return obj
- .redeclare(kind, src)
- .and_then(|obj| {
- node.replace(obj);
- Ok(existing)
- })
- .or_else(|(orig, err)| {
- node.replace(orig);
- Err(err.into())
- });
- }
-
- let node = self.graph.add_node(Some(O::ident(name, kind, src)));
-
- self.index_identifier(name, node);
-
- Ok(ObjectRef(node))
+ self.with_ident(name, |obj| obj.resolve(kind, src))
}
fn declare_extern(
&mut self,
name: &'i Symbol<'i>,
- expected_kind: IdentKind,
+ kind: IdentKind,
+ src: Source<'i>,
) -> AsgResult<ObjectRef<Ix>, Ix> {
- // TODO: resolution!
- let node = self.graph.add_node(Some(O::extern_(name, expected_kind)));
-
- self.index_identifier(name, node);
-
- Ok(ObjectRef(node))
+ self.with_ident(name, |obj| obj.extern_(kind, src))
}
fn set_fragment(
@@ -411,27 +418,21 @@ mod test {
#[derive(Debug, Default, PartialEq)]
struct StubIdentObject<'i> {
- given_missing: Option<&'i Symbol<'i>>,
- given_ident: Option<(&'i Symbol<'i>, IdentKind, Source<'i>)>,
- given_extern: Option<(&'i Symbol<'i>, IdentKind)>,
- given_redeclare: Option<(IdentKind, Source<'i>)>,
+ given_declare: Option<&'i Symbol<'i>>,
+ given_extern: Option<(IdentKind, Source<'i>)>,
+ given_resolve: Option<(IdentKind, Source<'i>)>,
given_set_fragment: Option<FragmentText>,
fail_redeclare: RefCell<Option<TransitionError>>,
+ fail_extern: RefCell<Option<TransitionError>>,
}
impl<'i> IdentObjectData<'i> for StubIdentObject<'i> {
fn name(&self) -> Option<&'i Symbol<'i>> {
- self.given_missing
- .or(self.given_ident.as_ref().map(|args| args.0))
- .or(self.given_extern.as_ref().map(|args| args.0))
+ self.given_declare
}
fn kind(&self) -> Option<&IdentKind> {
- self.given_ident
- .as_ref()
- .map(|args| &args.1)
- .or(self.given_extern.as_ref().map(|args| &args.1))
- .or(self.given_redeclare.as_ref().map(|args| &args.0))
+ self.given_resolve.as_ref().map(|args| &args.0)
}
fn src(&self) -> Option<&Source<'i>> {
@@ -448,42 +449,38 @@ mod test {
}
impl<'i> IdentObjectState<'i, StubIdentObject<'i>> for StubIdentObject<'i> {
- fn missing(ident: &'i Symbol<'i>) -> Self {
+ fn declare(ident: &'i Symbol<'i>) -> Self {
Self {
- given_missing: Some(ident),
+ given_declare: Some(ident),
..Default::default()
}
}
- fn ident(
- name: &'i Symbol<'i>,
+ fn resolve(
+ mut self,
kind: IdentKind,
src: Source<'i>,
- ) -> Self {
- Self {
- given_ident: Some((name, kind, src)),
- ..Default::default()
+ ) -> TransitionResult<StubIdentObject<'i>> {
+ if self.fail_redeclare.borrow().is_some() {
+ let err = self.fail_redeclare.replace(None).unwrap();
+ return Err((self, err));
}
- }
- fn extern_(name: &'i Symbol<'i>, kind: IdentKind) -> Self {
- Self {
- given_extern: Some((name, kind)),
- ..Default::default()
- }
+ self.given_resolve = Some((kind, src));
+ Ok(self)
}
- fn redeclare(
+ fn extern_(
mut self,
kind: IdentKind,
src: Source<'i>,
) -> TransitionResult<StubIdentObject<'i>> {
- if self.fail_redeclare.borrow().is_some() {
- let err = self.fail_redeclare.replace(None).unwrap();
+ if self.fail_extern.borrow().is_some() {
+ let err = self.fail_extern.replace(None).unwrap();
return Err((self, err));
}
- self.given_redeclare = Some((kind, src));
+ self.given_extern = Some((kind, src));
Ok(self)
}
@@ -545,28 +542,28 @@ mod test {
assert_ne!(nodea, nodeb);
+ assert_eq!(Some(&syma), sut.get(nodea).unwrap().given_declare);
assert_eq!(
Some((
- &syma,
IdentKind::Meta,
Source {
desc: Some("a".to_string()),
..Default::default()
},
)),
- sut.get(nodea).unwrap().given_ident
+ sut.get(nodea).unwrap().given_resolve
);
+ assert_eq!(Some(&symb), sut.get(nodeb).unwrap().given_declare);
assert_eq!(
Some((
- &symb,
IdentKind::Worksheet,
Source {
desc: Some("b".to_string()),
..Default::default()
},
)),
- sut.get(nodeb).unwrap().given_ident
+ sut.get(nodeb).unwrap().given_resolve
);
Ok(())
@@ -592,21 +589,6 @@ mod test {
}
#[test]
- fn declare_extern() -> AsgResult<(), u8> {
- let mut sut = Sut::with_capacity(0, 0);
-
- let sym = symbol_dummy!(1, "extern");
- let node = sut.declare_extern(&sym, IdentKind::Meta)?;
-
- assert_eq!(
- Some((&sym, IdentKind::Meta)),
- sut.get(node).unwrap().given_extern,
- );
-
- Ok(())
- }
-
- #[test]
fn declare_returns_existing() -> AsgResult<(), u8> {
let mut sut = Sut::with_capacity(0, 0);
@@ -626,10 +608,7 @@ mod test {
// same node is referenced.
assert_eq!(node, redeclare);
- assert_eq!(
- Some((rekind, resrc)),
- sut.get(node).unwrap().given_redeclare,
- );
+ assert_eq!(Some((rekind, resrc)), sut.get(node).unwrap().given_resolve,);
Ok(())
}
@@ -648,9 +627,8 @@ mod test {
// Set up an object to fail redeclaration.
let node = sut.declare(&sym, IdentKind::Meta, src.clone())?;
let obj = sut.get(node).unwrap();
- let msg = String::from("test fail");
- obj.fail_redeclare
- .replace(Some(TransitionError::Incompatible(msg.clone())));
+ let terr = TransitionError::Incompatible(String::from("test fail"));
+ obj.fail_redeclare.replace(Some(terr.clone()));
// Should invoke StubIdentObject::redeclare on the above `obj`.
let result = sut.declare(&sym, IdentKind::Meta, Source::default());
@@ -658,9 +636,71 @@ mod test {
if let Err(err) = result {
// The node should have been restored.
let obj = sut.get(node).unwrap();
- assert_eq!(src, obj.given_ident.as_ref().unwrap().2);
+ assert_eq!(src, obj.given_resolve.as_ref().unwrap().1);
+
+ assert_eq!(AsgError::ObjectTransition(terr), err);
+
+ Ok(())
+ } else {
+ panic!("failure expected: {:?}", result);
+ }
+ }
+
+ #[test]
+ fn declare_extern_returns_existing() -> AsgResult<(), u8> {
+ let mut sut = Sut::with_capacity(0, 0);
+
+ let sym = symbol_dummy!(1, "symext");
+ let src = Source::default();
+ let node = sut.declare_extern(&sym, IdentKind::Meta, src.clone())?;
+
+ // Remember that our stub does not care about compatibility.
+ let rekind = IdentKind::Class(Dim::from_u8(3));
+ let resrc = Source {
+ desc: Some("redeclare".into()),
+ ..Default::default()
+ };
+ let redeclare =
+ sut.declare_extern(&sym, rekind.clone(), resrc.clone())?;
+
+ // We don't care what the objects are for this test, just that the
+ // same node is referenced.
+ assert_eq!(node, redeclare);
+ assert_eq!(Some((rekind, resrc)), sut.get(node).unwrap().given_extern);
+
+ Ok(())
+ }
+
+ // Builds upon declare_returns_existing.
+ #[test]
+ fn declare_extern_fails_if_transition_fails() -> AsgResult<(), u8> {
+ let mut sut = Sut::with_capacity(0, 0);
+
+ let sym = symbol_dummy!(1, "symdup");
+ let src = Source {
+ desc: Some("orig".into()),
+ ..Default::default()
+ };
+
+ // Set up an object to fail redeclaration.
+ let node = sut.declare_extern(&sym, IdentKind::Meta, src.clone())?;
+ let obj = sut.get(node).unwrap();
+
+ // It doesn't matter that this isn't the error that'll actually be
+ // returned, as long as it's some sort of TransitionError.
+ let terr = TransitionError::Incompatible(String::from("test fail"));
+ obj.fail_extern.replace(Some(terr.clone()));
+
+ // Should invoke StubIdentObject::extern_ on the above `obj`.
+ let result =
+ sut.declare_extern(&sym, IdentKind::Meta, Source::default());
+
+ if let Err(err) = result {
+ // The node should have been restored.
+ let obj = sut.get(node).unwrap();
- assert_eq!(AsgError::IncompatibleIdent(msg), err);
+ assert_eq!(src, obj.given_extern.as_ref().unwrap().1);
+ assert_eq!(AsgError::ObjectTransition(terr), err);
Ok(())
} else {
@@ -691,7 +731,8 @@ mod test {
let obj = sut.get(node).unwrap();
- assert_eq!(Some((&sym, IdentKind::Meta, src,)), obj.given_ident);
+ assert_eq!(Some(&sym), obj.given_declare);
+ assert_eq!(Some((IdentKind::Meta, src)), obj.given_resolve);
assert_eq!(Some(fragment), obj.given_set_fragment);
Ok(())
@@ -747,8 +788,8 @@ mod test {
let (symnode, depnode) = sut.add_dep_lookup(&sym, &dep);
assert!(sut.has_dep(symnode, depnode));
- assert_eq!(Some(&sym), sut.get(symnode).unwrap().given_missing);
- assert_eq!(Some(&dep), sut.get(depnode).unwrap().given_missing);
+ assert_eq!(Some(&sym), sut.get(symnode).unwrap().given_declare);
+ assert_eq!(Some(&dep), sut.get(depnode).unwrap().given_declare);
Ok(())
}
@@ -775,8 +816,8 @@ mod test {
let obj = sut.get(declared).unwrap();
- assert_eq!(Some(&sym), obj.given_missing);
- assert_eq!(Some((IdentKind::Meta, src)), obj.given_redeclare);
+ assert_eq!(Some(&sym), obj.given_declare);
+ assert_eq!(Some((IdentKind::Meta, src)), obj.given_resolve);
Ok(())
}
diff --git a/tamer/src/ir/asg/graph.rs b/tamer/src/ir/asg/graph.rs
index c6e3a30..52078ed 100644
--- a/tamer/src/ir/asg/graph.rs
+++ b/tamer/src/ir/asg/graph.rs
@@ -62,9 +62,18 @@ where
/// then the operation will fail;
/// otherwise,
/// the existing identifier will be returned.
+ ///
+ /// If a concrete identifier has already been declared (see
+ /// [`Asg::declare`]),
+ /// then extern declarations will be compared and,
+ /// if compatible,
+ /// the identifier will be immediately _resolved_ and the object
+ /// on the graph will not be altered.
+ /// Resolution will otherwise fail in error.
+ ///
/// For more information on state transitions that can occur when
/// redeclaring an identifier that already exists,
- /// see [`IdentObjectState::redeclare`].
+ /// see [`IdentObjectState::resolve`].
///
/// A successful declaration will add an identifier to the graph
/// and return an [`ObjectRef`] reference.
@@ -93,12 +102,13 @@ where
/// Resolution will otherwise fail in error.
///
/// See [`IdentObjectState::extern_`] and
- /// [`IdentObjectState::redeclare`] for more information on
+ /// [`IdentObjectState::resolve`] for more information on
/// compatibility related to extern resolution.
fn declare_extern(
&mut self,
name: &'i Symbol<'i>,
- expected_kind: IdentKind,
+ kind: IdentKind,
+ src: Source<'i>,
) -> AsgResult<ObjectRef<Ix>, Ix>;
/// Set the fragment associated with a concrete identifier.
@@ -153,7 +163,7 @@ where
/// a missing identifier will be added as a placeholder,
/// allowing the ASG to be built with partial information as
/// identifiers continue to be discovered.
- /// See [`IdentObjectState::missing`] for more information.
+ /// See [`IdentObjectState::declare`] for more information.
///
/// References to both identifiers are returned in argument order.
fn add_dep_lookup(
@@ -227,18 +237,12 @@ pub type Node<O> = Option<O>;
/// The caller will know the problem values.
#[derive(Debug, PartialEq)]
pub enum AsgError<Ix: Debug> {
- /// The provided identifier is not in a state that is permitted to
- /// receive a fragment.
- ///
- /// See [`Asg::set_fragment`] for more information.
- BadFragmentDest(String),
-
- /// An attempt to redeclare an identifier with additional information
- /// has failed because the provided information was not compatible
- /// with the original declaration.
+ /// An object could not change state in the manner requested.
///
- /// See [`Asg::declare`] for more information.
- IncompatibleIdent(String),
+ /// See [`Asg::declare`] and [`Asg::set_fragment`] for more
+ /// information.
+ /// See also [`TransitionError`].
+ ObjectTransition(TransitionError),
/// The node was not expected in the current context
UnexpectedNode(String),
@@ -250,12 +254,7 @@ pub enum AsgError<Ix: Debug> {
impl<Ix: Debug> std::fmt::Display for AsgError<Ix> {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
- Self::BadFragmentDest(msg) => {
- write!(fmt, "bad fragment destination: {}", msg)
- }
- Self::IncompatibleIdent(msg) => {
- write!(fmt, "identifier redeclaration failed: {}", msg)
- }
+ Self::ObjectTransition(err) => std::fmt::Display::fmt(&err, fmt),
Self::UnexpectedNode(msg) => {
write!(fmt, "unexpected node: {}", msg)
}
@@ -268,16 +267,16 @@ impl<Ix: Debug> std::fmt::Display for AsgError<Ix> {
impl<Ix: Debug> std::error::Error for AsgError<Ix> {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
- None
+ match self {
+ Self::ObjectTransition(err) => err.source(),
+ _ => None,
+ }
}
}
impl<Ix: Debug> From<TransitionError> for AsgError<Ix> {
- fn from(e: TransitionError) -> Self {
- match e {
- TransitionError::Incompatible(msg) => Self::IncompatibleIdent(msg),
- TransitionError::BadFragmentDest(msg) => Self::BadFragmentDest(msg),
- }
+ fn from(err: TransitionError) -> Self {
+ Self::ObjectTransition(err)
}
}
diff --git a/tamer/src/ir/asg/ident.rs b/tamer/src/ir/asg/ident.rs
index 6a66316..b5ef44b 100644
--- a/tamer/src/ir/asg/ident.rs
+++ b/tamer/src/ir/asg/ident.rs
@@ -144,6 +144,42 @@ pub enum IdentKind {
Worksheet,
}
+impl std::fmt::Display for IdentKind {
+ /// Format identifier type for display to the user.
+ ///
+ /// TODO: We have not yet finalized how we will represent types in the
+ /// new type system,
+ /// so for now this just uses a syntax similar to Rust.
+ fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
+ match self {
+ Self::Cgen(dim) => {
+ write!(fmt, "cgen[{}; {}]", DataType::Boolean, dim)
+ }
+ Self::Class(dim) => {
+ write!(fmt, "class[{}; {}]", DataType::Boolean, dim)
+ }
+ Self::Const(dim, dtype) => write!(fmt, "const[{}; {}]", dtype, dim),
+ Self::Func(dim, dtype) => write!(fmt, "func[{}; {}]", dtype, dim),
+ Self::Gen(dim, dtype) => write!(fmt, "gen[{}; {}]", dtype, dim),
+ Self::Lparam(dim, dtype) => {
+ write!(fmt, "lparam[{}; {}]", dtype, dim)
+ }
+ Self::Param(dim, dtype) => write!(fmt, "param[{}; {}]", dtype, dim),
+ Self::Rate(dtype) => write!(fmt, "rate[{}; 0]", dtype),
+ Self::Tpl => write!(fmt, "tpl"),
+ Self::Type(dtype) => write!(fmt, "type[{}]", dtype),
+ Self::MapHead => write!(fmt, "map:head"),
+ Self::Map => write!(fmt, "map"),
+ Self::MapTail => write!(fmt, "map:tail"),
+ Self::RetMapHead => write!(fmt, "retmap:head"),
+ Self::RetMap => write!(fmt, "retmap"),
+ Self::RetMapTail => write!(fmt, "retmap:tail"),
+ Self::Meta => write!(fmt, "meta"),
+ Self::Worksheet => write!(fmt, "worksheet"),
+ }
+ }
+}
+
impl<'i> TryFrom<SymAttrs<'i>> for IdentKind {
type Error = &'static str;
@@ -247,6 +283,12 @@ impl AsRef<str> for Dim {
}
}
+impl std::fmt::Display for Dim {
+ fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
+ (self.0).fmt(fmt)
+ }
+}
+
/// Underlying datatype of identifier.
pub type DataType = SymDtype;
diff --git a/tamer/src/ir/asg/mod.rs b/tamer/src/ir/asg/mod.rs
index a547463..5176eae 100644
--- a/tamer/src/ir/asg/mod.rs
+++ b/tamer/src/ir/asg/mod.rs
@@ -77,10 +77,10 @@
//! let identb_sym = interner.intern("identb");
//!
//! let identa = asg.declare(identa_sym, IdentKind::Meta, Source::default())?;
-//! let identb = asg.declare_extern(identb_sym, IdentKind::Meta)?;
+//! let identb = asg.declare_extern(identb_sym, IdentKind::Meta, Source::default())?;
//!
//! assert_eq!(
-//! Some(&IdentObject::Extern(identb_sym, IdentKind::Meta)),
+//! Some(&IdentObject::Extern(identb_sym, IdentKind::Meta, Source::default())),
//! asg.get(identb),
//! );
//!
diff --git a/tamer/src/ir/asg/object.rs b/tamer/src/ir/asg/object.rs
index 2f5684d..0bf75e9 100644
--- a/tamer/src/ir/asg/object.rs
+++ b/tamer/src/ir/asg/object.rs
@@ -62,7 +62,13 @@ pub enum IdentObject<'i> {
/// the same name is loaded.
/// It is an error if the loaded identifier does not have a compatible
/// [`IdentKind`].
- Extern(&'i Symbol<'i>, IdentKind),
+ ///
+ /// The source location of an extern represents the location of the
+ /// extern declaration.
+ /// Once resolved, however,
+ /// the source will instead represent the location of the concrete
+ /// identifier.
+ Extern(&'i Symbol<'i>, IdentKind, Source<'i>),
/// Identifier with associated text.
///
@@ -80,7 +86,7 @@ pub enum IdentObject<'i> {
/// as [`IdentObject`];
/// this allows other representations to be used,
/// while still permitting the use of matching on [`IdentObject`]
-/// through the use of [`ident`](IdentObjectState::ident).
+/// through the use of [`ident`](IdentObjectData::as_ident).
///
/// Since an object implementing this trait may not be an identifier
/// (e.g. an expression),
@@ -134,7 +140,7 @@ impl<'i> IdentObjectData<'i> for IdentObject<'i> {
match self {
Self::Missing(name)
| Self::Ident(name, _, _)
- | Self::Extern(name, _)
+ | Self::Extern(name, _, _)
| Self::IdentFragment(name, _, _, _) => Some(name),
}
}
@@ -143,14 +149,14 @@ impl<'i> IdentObjectData<'i> for IdentObject<'i> {
match self {
Self::Missing(_) => None,
Self::Ident(_, kind, _)
- | Self::Extern(_, kind)
+ | Self::Extern(_, kind, _)
| Self::IdentFragment(_, kind, _, _) => Some(kind),
}
}
fn src(&self) -> Option<&Source<'i>> {
match self {
- Self::Missing(_) | Self::Extern(_, _) => None,
+ Self::Missing(_) | Self::Extern(_, _, _) => None,
Self::Ident(_, _, src) | Self::IdentFragment(_, _, src, _) => {
Some(src)
}
@@ -159,7 +165,7 @@ impl<'i> IdentObjectData<'i> for IdentObject<'i> {
fn fragment(&self) -> Option<&FragmentText> {
match self {
- Self::Missing(_) | Self::Ident(_, _, _) | Self::Extern(_, _) => {
+ Self::Missing(_) | Self::Ident(_, _, _) | Self::Extern(_, _, _) => {
None
}
Self::IdentFragment(_, _, _, text) => Some(text),
@@ -186,21 +192,33 @@ where
T: IdentObjectState<'i, T>,
{
/// Produce an object representing a missing identifier.
- fn missing(ident: &'i Symbol<'i>) -> T;
-
- /// Produce an object representing a concrete identifier.
- fn ident(name: &'i Symbol<'i>, kind: IdentKind, src: Source<'i>) -> T;
-
- /// Produce an object representing an extern.
- fn extern_(name: &'i Symbol<'i>, kind: IdentKind) -> T;
+ ///
+ /// This is the base state for all identifiers.
+ fn declare(ident: &'i Symbol<'i>) -> T;
- /// Attempt to redeclare an identifier with additional information.
+ /// Attempt to transition to a concrete identifier.
///
/// For specific information on compatibility rules,
/// see implementers of this trait,
/// since rules may vary between implementations.
- fn redeclare(self, kind: IdentKind, src: Source<'i>)
- -> TransitionResult<T>;
+ fn resolve(self, kind: IdentKind, src: Source<'i>) -> TransitionResult<T>;
+
+ /// Resolve identifier against an extern declaration or produce an
+ /// extern.
+ ///
+ /// If the existing identifier has an assigned [`IdentKind`],
+ /// then it will be compared for equality against the given `kind`.
+ /// If it matches,
+ /// then the current identifier will be returned as-is.
+ /// This represents an extern resolution that occurs when a concrete
+ /// identifier is located before an extern that requires it,
+ /// or my represent a duplicate (but compatible) extern
+ /// declaration.
+ ///
+ /// If no kind is assigned (such as [`IdentObject::Missing`]),
+ /// then a new extern is produced.
+ /// See for example [`IdentObject::Extern`].
+ fn extern_(self, kind: IdentKind, src: Source<'i>) -> TransitionResult<T>;
/// Attach a code fragment (compiled text) to an identifier.
///
@@ -214,18 +232,10 @@ where
}
impl<'i> IdentObjectState<'i, IdentObject<'i>> for IdentObject<'i> {
- fn missing(ident: &'i Symbol<'i>) -> Self {
+ fn declare(ident: &'i Symbol<'i>) -> Self {
IdentObject::Missing(ident)
}
- fn ident(name: &'i Symbol<'i>, kind: IdentKind, src: Source<'i>) -> Self {
- IdentObject::Ident(name, kind, src)
- }
-
- fn extern_(name: &'i Symbol<'i>, kind: IdentKind) -> Self {
- IdentObject::Extern(name, kind)
- }
-
/// Attempt to redeclare an identifier with additional information.
///
/// If an existing identifier is an [`IdentObject::Extern`],
@@ -244,7 +254,7 @@ impl<'i> IdentObjectState<'i, IdentObject<'i>> for IdentObject<'i> {
/// The kind of identifier cannot change,
/// but the argument is provided here for convenience so that the
/// caller does not need to perform such a check itself.
- fn redeclare(
+ fn resolve(
mut self,
kind: IdentKind,
src: Source<'i>,
@@ -257,6 +267,20 @@ impl<'i> IdentObjectState<'i, IdentObject<'i>> for IdentObject<'i> {
Ok(self)
}
+ IdentObject::Extern(name, ref orig_kind, _) => {
+ if orig_kind != &kind {
+ let err = TransitionError::ExternResolution {
+ name: name.to_string(),
+ expected: orig_kind.clone(),
+ given: kind.clone(),
+ };
+
+ return Err((self, err));
+ }
+
+ Ok(IdentObject::Ident(name, kind, src))
+ }
+
// TODO: no override-override
IdentObject::IdentFragment(name, _, orig_src, _)
if orig_src.virtual_ && src.override_ =>
@@ -269,11 +293,35 @@ impl<'i> IdentObjectState<'i, IdentObject<'i>> for IdentObject<'i> {
Ok(IdentObject::Ident(name, kind, src))
}
- // TODO: incompatible (check now-dangling commits)
+ // TODO
_ => Ok(self),
}
}
+ fn extern_(
+ self,
+ kind: IdentKind,
+ src: Source<'i>,
+ ) -> TransitionResult<IdentObject<'i>> {
+ match self.kind() {
+ None => Ok(IdentObject::Extern(self.name().unwrap(), kind, src)),
+ Some(cur_kind) => {
+ if cur_kind != &kind {
+ let err = TransitionError::ExternResolution {
+ name: self.name().unwrap().to_string(),
+ expected: kind.clone(),
+ given: cur_kind.clone(),
+ };
+
+ return Err((self, err));
+ }
+
+ // Resolved successfully, so keep what we already have.
+ Ok(self)
+ }
+ }
+ }
+
fn set_fragment(
self,
text: FragmentText,
@@ -340,15 +388,25 @@ impl<'i> IdentObjectState<'i, IdentObject<'i>> for IdentObject<'i> {
/// another.
///
/// TODO: Provide enough information to construct a useful message.
-#[derive(Debug, PartialEq)]
+#[derive(Clone, Debug, PartialEq)]
pub enum TransitionError {
/// An attempt to redeclare an identifier with additional information
/// has failed because the provided information was not compatible
/// with the original declaration.
///
- /// See [`IdentObjectState::redeclare`].
+ /// See [`IdentObjectState::resolve`].
Incompatible(String),
+ /// 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: String,
+ expected: IdentKind,
+ given: IdentKind,
+ },
+
/// The provided identifier is not in a state that is permitted to
/// receive a fragment.
///
@@ -363,6 +421,16 @@ impl std::fmt::Display for TransitionError {
write!(fmt, "object incompatible: {}", msg)
}
+ Self::ExternResolution {
+ name,
+ expected,
+ given,
+ } => write!(
+ fmt,
+ "extern `{}` of type `{}` is incompatible with type `{}`",
+ name, expected, given,
+ ),
+
Self::BadFragmentDest(msg) => {
write!(fmt, "bad fragment destination: {}", msg)
}
@@ -370,6 +438,12 @@ impl std::fmt::Display for TransitionError {
}
}
+impl std::error::Error for TransitionError {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+ None
+ }
+}
+
/// Compiled fragment for identifier.
///
/// This represents the text associated with an identifier.
@@ -494,7 +568,8 @@ mod test {
assert_eq!(
Some(&sym),
- IdentObject::Extern(&sym, IdentKind::Meta).name()
+ IdentObject::Extern(&sym, IdentKind::Meta, Source::default())
+ .name()
);
assert_eq!(
@@ -524,7 +599,8 @@ mod test {
assert_eq!(
Some(&kind),
- IdentObject::Extern(&sym, kind.clone()).kind()
+ IdentObject::Extern(&sym, kind.clone(), Source::default())
+ .kind()
);
assert_eq!(
@@ -554,7 +630,10 @@ mod test {
IdentObject::Ident(&sym, IdentKind::Meta, src.clone()).src()
);
- assert_eq!(None, IdentObject::Extern(&sym, IdentKind::Meta).src());
+ assert_eq!(
+ None,
+ IdentObject::Extern(&sym, IdentKind::Meta, src.clone()).src()
+ );
assert_eq!(
Some(&src),
@@ -583,7 +662,8 @@ mod test {
assert_eq!(
None,
- IdentObject::Extern(&sym, IdentKind::Meta).fragment()
+ IdentObject::Extern(&sym, IdentKind::Meta, Source::default())
+ .fragment()
);
assert_eq!(
@@ -618,7 +698,7 @@ mod test {
#[test]
fn ident_object_missing() {
let sym = symbol_dummy!(1, "missing");
- assert_eq!(IdentObject::Missing(&sym), IdentObject::missing(&sym));
+ assert_eq!(IdentObject::Missing(&sym), IdentObject::declare(&sym));
}
#[test]
@@ -632,19 +712,180 @@ mod test {
assert_eq!(
IdentObject::Ident(&sym, kind.clone(), src.clone()),
- IdentObject::ident(&sym, kind.clone(), src.clone()),
+ IdentObject::declare(&sym)
+ .resolve(kind.clone(), src.clone())
+ .unwrap(),
);
}
- #[test]
- fn ident_object_extern() {
- let sym = symbol_dummy!(1, "missing");
- let kind = IdentKind::Class(Dim::from_u8(1));
+ mod extern_ {
+ use super::*;
- assert_eq!(
- IdentObject::Extern(&sym, kind.clone()),
- IdentObject::extern_(&sym, kind.clone()),
- );
+ #[test]
+ fn ident_object() {
+ let sym = symbol_dummy!(1, "missing");
+ let kind = IdentKind::Class(Dim::from_u8(1));
+ let src = Source {
+ desc: Some("extern".into()),
+ ..Default::default()
+ };
+
+ assert_eq!(
+ Ok(IdentObject::Extern(&sym, kind.clone(), src.clone())),
+ IdentObject::declare(&sym).extern_(kind, src),
+ );
+ }
+
+ // Extern first, then identifier
+ #[test]
+ fn redeclare_compatible_resolves() {
+ let sym = symbol_dummy!(1, "extern_re_pre");
+ let kind = IdentKind::Class(Dim::from_u8(10));
+ let src = Source {
+ desc: Some("okay".into()),
+ ..Default::default()
+ };
+
+ // Compatible kind, should resolve.
+ let result = IdentObject::declare(&sym)
+ .extern_(kind.clone(), Source::default())
+ .and_then(|o| o.resolve(kind.clone(), src.clone()));
+
+ assert_eq!(Ok(IdentObject::Ident(&sym, kind, src)), result,);
+ }
+
+ // Identifier first, then extern
+ #[test]
+ fn redeclare_compatible_resolves_post() {
+ let sym = symbol_dummy!(1, "extern_re_post");
+ let kind = IdentKind::Class(Dim::from_u8(10));
+ let src = Source {
+ desc: Some("okay".into()),
+ ..Default::default()
+ };
+
+ // Compatible kind, should resolve.
+ let result = IdentObject::declare(&sym)
+ .resolve(kind.clone(), src.clone())
+ .and_then(|o| o.extern_(kind.clone(), Source::default()));
+
+ assert_eq!(Ok(IdentObject::Ident(&sym, kind, src)), result,);
+ }
+
+ #[test]
+ fn redeclare_another_extern() {
+ let sym = symbol_dummy!(1, "extern_extern");
+ let kind = IdentKind::Class(Dim::from_u8(20));
+ let src_first = Source {
+ desc: Some("first src".into()),
+ ..Default::default()
+ };
+ let src_second = Source {
+ desc: Some("second src".into()),
+ ..Default::default()
+ };
+
+ let result = IdentObject::declare(&sym)
+ .extern_(kind.clone(), src_first.clone())
+ .and_then(|o| o.extern_(kind.clone(), src_second));
+
+ // Note that, if it resolves, it should keep what is
+ // _existing_, meaning that it must keep the first src.
+ assert_eq!(
+ Ok(IdentObject::Extern(&sym, kind, src_first)),
+ result
+ );
+ }
+
+ // Extern first, then identifier
+ #[test]
+ fn redeclare_post_incompatible_kind() {
+ let sym = symbol_dummy!(1, "extern_re_bad_post");
+ let kind = IdentKind::Class(Dim::from_u8(10));
+ let src = Source {
+ desc: Some("bad kind".into()),
+ ..Default::default()
+ };
+
+ let orig = IdentObject::declare(&sym)
+ .extern_(kind.clone(), Source::default())
+ .unwrap();
+
+ // Incompatible kind
+ let kind_bad = IdentKind::Meta;
+ let result = orig.clone().resolve(kind_bad.clone(), src);
+
+ match result {
+ Err((given_orig, err @ _)) => {
+ assert_eq!(orig, given_orig);
+
+ if let TransitionError::ExternResolution {
+ name: e_name,
+ expected: e_expected,
+ given: e_given,
+ } = err.clone()
+ {
+ assert_eq!(sym.to_string(), e_name);
+ assert_eq!(kind, e_expected);
+ assert_eq!(kind_bad, e_given);
+ }
+
+ // Formatted error
+ let msg = format!("{}", err);
+
+ assert!(msg.contains(&format!("{}", sym)));
+ assert!(msg.contains(&format!("{}", kind)));
+ assert!(msg.contains(&format!("{}", kind_bad)));
+ }
+ _ => panic!("expected failure: {:?}", result),
+ }
+ }
+
+ // Identifier first, then extern
+ #[test]
+ fn redeclare_pre_incompatible_kind() {
+ let sym = symbol_dummy!(1, "extern_re_bad_pre");
+ let kind_given = IdentKind::Class(Dim::from_u8(10));
+ let src = Source {
+ desc: Some("bad kind".into()),
+ ..Default::default()
+ };
+
+ let orig = IdentObject::declare(&sym)
+ .resolve(kind_given.clone(), src.clone())
+ .unwrap();
+
+ // Extern with incompatible kind.
+ let kind_extern = IdentKind::Meta;
+ let result = orig
+ .clone()
+ .extern_(kind_extern.clone(), Source::default());
+
+ match result {
+ Err((given_orig, err @ _)) => {
+ assert_eq!(orig, given_orig);
+
+ if let TransitionError::ExternResolution {
+ name: e_name,
+ expected: e_expected,
+ given: e_given,
+ } = err.clone()
+ {
+ assert_eq!(sym.to_string(), e_name);
+ assert_eq!(kind_extern, e_expected);
+ assert_eq!(kind_given, e_given);
+ }
+
+ // Formatted error
+ let msg = format!("{}", err);
+
+ assert!(msg.contains(&format!("{}", sym)));
+ assert!(msg.contains(&format!("{}", kind_extern)));
+ assert!(msg.contains(&format!("{}", kind_given)));
+ }
+ _ => panic!("expected failure: {:?}", result),
+ }
+ }
}
// TODO: incompatible
@@ -652,13 +893,14 @@ mod test {
fn redeclare_returns_existing_compatible() {
let sym = symbol_dummy!(1, "symdup");
- let first =
- IdentObject::ident(&sym, IdentKind::Meta, Source::default());
+ let first = IdentObject::declare(&sym)
+ .resolve(IdentKind::Meta, Source::default())
+ .unwrap();
// Same declaration a second time
assert_eq!(
Ok(first.clone()),
- first.clone().redeclare(
+ first.clone().resolve(
first.kind().unwrap().clone(),
first.src().unwrap().clone(),
)
@@ -674,7 +916,9 @@ mod test {
};
let kind = IdentKind::Meta;
- let ident = IdentObject::ident(&sym, kind.clone(), src.clone());
+ let ident = IdentObject::declare(&sym)
+ .resolve(kind.clone(), src.clone())
+ .unwrap();
let text = FragmentText::from("a fragment");
let ident_with_frag = ident.set_fragment(text.clone());
@@ -687,8 +931,9 @@ mod test {
#[test]
fn add_fragment_to_fragment_fails() {
let sym = symbol_dummy!(1, "badsym");
- let ident =
- IdentObject::ident(&sym, IdentKind::Meta, Source::default());
+ let ident = IdentObject::declare(&sym)
+ .resolve(IdentKind::Meta, Source::default())
+ .unwrap();
let ident_with_frag = ident
.set_fragment("orig fragment".into())
@@ -720,14 +965,15 @@ mod test {
let over_src = symbol_dummy!(2, "src");
let kind = IdentKind::Meta;
- let virt = IdentObject::ident(
- &sym,
- kind.clone(),
- Source {
- virtual_: true,
- ..Default::default()
- },
- );
+ let virt = IdentObject::declare(&sym)
+ .resolve(
+ kind.clone(),
+ Source {
+ virtual_: true,
+ ..Default::default()
+ },
+ )
+ .unwrap();
let over_src = Source {
override_: true,
@@ -735,7 +981,7 @@ mod test {
..Default::default()
};
- let result = virt.redeclare(kind.clone(), over_src.clone());
+ let result = virt.resolve(kind.clone(), over_src.clone());
assert_eq!(Ok(IdentObject::Ident(&sym, kind, over_src)), result);
}
@@ -752,7 +998,9 @@ mod test {
..Default::default()
};
- let virt = IdentObject::ident(&sym, kind.clone(), virt_src.clone());
+ let virt = IdentObject::declare(&sym)
+ .resolve(kind.clone(), virt_src.clone())
+ .unwrap();
let text = FragmentText::from("remove me");
let virt_frag = virt.set_fragment(text.clone());
@@ -773,7 +1021,7 @@ mod test {
};
let result =
- virt_frag.unwrap().redeclare(kind.clone(), over_src.clone());
+ virt_frag.unwrap().resolve(kind.clone(), over_src.clone());
// The act of overriding the object should have cleared any
// existing fragment, making way for a new fragment to take its
@@ -789,7 +1037,9 @@ mod test {
..Default::default()
};
- let obj = IdentObject::ident(&sym, given, src.clone());
+ let obj = IdentObject::declare(&sym)
+ .resolve(given, src.clone())
+ .unwrap();
let fragment = "a fragment".to_string();
let obj_with_frag = obj.set_fragment(fragment.clone());
diff --git a/tamer/src/ir/legacyir.rs b/tamer/src/ir/legacyir.rs
index fbe68f0..1d23f3f 100644
--- a/tamer/src/ir/legacyir.rs
+++ b/tamer/src/ir/legacyir.rs
@@ -305,6 +305,17 @@ impl TryFrom<&[u8]> for SymDtype {
}
}
+impl std::fmt::Display for SymDtype {
+ fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
+ match self {
+ Self::Boolean => write!(fmt, "boolean"),
+ Self::Integer => write!(fmt, "integer"),
+ Self::Float => write!(fmt, "float"),
+ Self::Empty => write!(fmt, "(unknown)"),
+ }
+ }
+}
+
#[cfg(test)]
mod test {
use super::*;
diff --git a/tamer/src/ld/poc.rs b/tamer/src/ld/poc.rs
index 224fbeb..975ca35 100644
--- a/tamer/src/ld/poc.rs
+++ b/tamer/src/ld/poc.rs
@@ -174,12 +174,9 @@ fn load_xmlo<'a, 'i, I: Interner<'i>>(
Ok(XmloEvent::SymDecl(sym, attrs)) => {
if let Some(sym_src) = attrs.src {
found.insert(sym_src);
- } else if attrs.extern_ {
- // TODO: externs (they're implicitly handled, without
- // checks, by Missing)
- // depgraph.declare_extern(sym, kind);
} else {
let owned = attrs.src.is_none();
+ let extern_ = attrs.extern_;
let kind = (&attrs).try_into().map_err(|err| {
format!("sym `{}` attrs error: {}", sym, err)
@@ -201,10 +198,15 @@ fn load_xmlo<'a, 'i, I: Interner<'i>>(
|| kindval == IdentKind::Map
|| kindval == IdentKind::RetMap);
- let node = depgraph.declare(sym, kindval, src)?;
+ if extern_ {
+ depgraph.declare_extern(sym, kindval, src)?;
+ } else {
+ let node =
+ depgraph.declare(sym, kindval, src)?;
- if link_root {
- roots.push(node);
+ if link_root {
+ roots.push(node);
+ }
}
}
Err(e) => return Err(e.into()),