Skip to content

Commit 0800dd2

Browse files
authored
Add error enum (#44)
* initial attempts at errors * fill in errors * bump ver * fix graph feat
1 parent 93f155e commit 0800dd2

File tree

6 files changed

+93
-55
lines changed

6 files changed

+93
-55
lines changed

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "safety-net"
3-
version = "0.1.7"
3+
version = "0.2.0"
44
edition = "2024"
55
license = "MIT OR Apache-2.0"
66

@@ -38,6 +38,7 @@ readme = "README.md"
3838
repository = "https://github.com/matth2k/safety-net"
3939

4040
[dependencies]
41+
thiserror = { version = "2.0.16" }
4142
bitvec = { version = "1.0.1" }
4243
petgraph = { version = "0.8.2", optional = true }
4344
serde = { version = "1.0.219", optional = true, features = ["derive"] }

src/error.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*!
2+
3+
Error types.
4+
5+
*/
6+
7+
use thiserror::Error;
8+
9+
use crate::circuit::{Identifier, Net};
10+
11+
/// Errors for the `safety-net` library.
12+
#[derive(Error, Debug)]
13+
pub enum Error {
14+
/// Error for an analysis cannot run due to cycles.
15+
#[error("Cycles detected along nets {0:?}")]
16+
CycleDetected(Vec<Net>),
17+
/// Errors in parsing literals/identifiers.
18+
#[error("Parsing error `{0}`")]
19+
ParseError(String),
20+
/// The labeled nets in the netlist are not unique.
21+
#[error("Non-unique nets: {0:?}")]
22+
NonuniqueNets(Vec<Net>),
23+
/// The labeled instances in the netlist are not unique.
24+
#[error("Non-unique instances: {0:?}")]
25+
NonuniqueInsts(Vec<Identifier>),
26+
/// The netlist has no outputs.
27+
#[error("No outputs in netlist")]
28+
NoOutputs,
29+
/// A deletion would cause a dangling reference.
30+
#[error("Attempted to create a dangling reference to nets {0:?}")]
31+
DanglingReference(Vec<Net>),
32+
/// Mismatch in number of arguments
33+
#[error("Expected {0} arguments, got {1}")]
34+
ArgumentMismatch(usize, usize),
35+
/// An input needs an alias to be an output
36+
#[error("Input net {0} needs an alias to be an output")]
37+
InputNeedsAlias(Net),
38+
/// A net that was expected but not found
39+
#[error("Expected to find net {0} in netlist")]
40+
NetNotFound(Net),
41+
}

src/graph.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66

77
use crate::circuit::{Instantiable, Net};
8+
use crate::error::Error;
89
#[cfg(feature = "graph")]
910
use crate::netlist::Connection;
1011
use crate::netlist::iter::DFSIterator;
@@ -21,7 +22,7 @@ where
2122
Self: Sized + 'a,
2223
{
2324
/// Construct the analysis to the current state of the netlist.
24-
fn build(netlist: &'a Netlist<I>) -> Result<Self, String>;
25+
fn build(netlist: &'a Netlist<I>) -> Result<Self, Error>;
2526
}
2627

2728
/// A table that maps nets to the circuit nodes they drive
@@ -68,7 +69,7 @@ impl<'a, I> Analysis<'a, I> for FanOutTable<'a, I>
6869
where
6970
I: Instantiable,
7071
{
71-
fn build(netlist: &'a Netlist<I>) -> Result<Self, String> {
72+
fn build(netlist: &'a Netlist<I>) -> Result<Self, Error> {
7273
let mut net_fan_out: HashMap<Net, Vec<NetRef<I>>> = HashMap::new();
7374
let mut node_fan_out: HashMap<NetRef<I>, Vec<NetRef<I>>> = HashMap::new();
7475
let mut is_an_output: HashSet<Net> = HashSet::new();
@@ -140,15 +141,15 @@ impl<'a, I> Analysis<'a, I> for SimpleCombDepth<'a, I>
140141
where
141142
I: Instantiable,
142143
{
143-
fn build(netlist: &'a Netlist<I>) -> Result<Self, String> {
144+
fn build(netlist: &'a Netlist<I>) -> Result<Self, Error> {
144145
let mut comb_depth: HashMap<NetRef<I>, usize> = HashMap::new();
145146

146147
let mut nodes = Vec::new();
147148
for (driven, _) in netlist.outputs() {
148-
let mut dfs = DFSIterator::new(netlist, driven.unwrap());
149+
let mut dfs = DFSIterator::new(netlist, driven.clone().unwrap());
149150
while let Some(n) = dfs.next() {
150151
if dfs.check_cycles() {
151-
return Err("Cycle detected in the netlist".to_string());
152+
return Err(Error::CycleDetected(vec![driven.as_net().clone()]));
152153
}
153154
nodes.push(n);
154155
}
@@ -252,7 +253,7 @@ impl<'a, I> Analysis<'a, I> for MultiDiGraph<'a, I>
252253
where
253254
I: Instantiable,
254255
{
255-
fn build(netlist: &'a Netlist<I>) -> Result<Self, String> {
256+
fn build(netlist: &'a Netlist<I>) -> Result<Self, Error> {
256257
// If we verify, we can hash by name
257258
netlist.verify()?;
258259
let mut mapping = HashMap::new();

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Take a look at some [examples](https://github.com/matth2k/safety-net/tree/main/e
1414

1515
pub mod attribute;
1616
pub mod circuit;
17+
pub mod error;
1718
pub mod graph;
1819
pub mod logic;
1920
pub mod netlist;

src/logic.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
use std::{fmt, str::FromStr};
88

9+
use crate::error::Error;
10+
911
#[derive(Debug, Clone, PartialEq, Eq, Copy)]
1012
/// An enum to represent four-state logic
1113
pub enum Logic {
@@ -114,15 +116,15 @@ impl From<bool> for Logic {
114116
}
115117

116118
impl FromStr for Logic {
117-
type Err = String;
119+
type Err = Error;
118120

119121
fn from_str(s: &str) -> Result<Self, Self::Err> {
120122
match s {
121123
"1'b1" | "1'h1" => Ok(Logic::True),
122124
"1'b0" | "1'h0" => Ok(Logic::False),
123125
"1'bx" | "1'hx" => Ok(Logic::X),
124126
"1'bz" | "1'hz" => Ok(Logic::Z),
125-
_ => Err(format!("Invalid logic value: {s}")),
127+
_ => Err(Error::ParseError(s.to_string())),
126128
}
127129
}
128130
}

src/netlist.rs

Lines changed: 38 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use crate::{
88
attribute::{Attribute, AttributeKey, AttributeValue, Parameter},
99
circuit::{Identifier, Instantiable, Net, Object},
10+
error::Error,
1011
graph::{Analysis, FanOutTable},
1112
logic::Logic,
1213
};
@@ -614,7 +615,7 @@ where
614615
///
615616
/// Panics if cell is a multi-output circuit node.
616617
/// Panics if the reference to the netlist is lost.
617-
pub fn expose_as_output(self) -> Result<Self, String> {
618+
pub fn expose_as_output(self) -> Result<Self, Error> {
618619
let netlist = self
619620
.netref
620621
.borrow()
@@ -647,17 +648,18 @@ where
647648
///
648649
/// # Panics
649650
/// Panics if the reference to the netlist is lost.
650-
pub fn expose_net(&self, net: &Net) -> Result<(), String> {
651+
pub fn expose_net(&self, net: &Net) -> Result<(), Error> {
651652
let netlist = self
652653
.netref
653654
.borrow()
654655
.owner
655656
.upgrade()
656657
.expect("NetRef is unlinked from netlist");
657-
let net_index = self.netref.borrow().find_net(net).ok_or(format!(
658-
"Net {} not found in circuit node",
659-
net.get_identifier()
660-
))?;
658+
let net_index = self
659+
.netref
660+
.borrow()
661+
.find_net(net)
662+
.ok_or(Error::NetNotFound(net.clone()))?;
661663
let dr = DrivenNet::new(net_index, self.clone());
662664
netlist.expose_net(dr)?;
663665
Ok(())
@@ -782,7 +784,7 @@ where
782784
/// # Panics
783785
///
784786
/// Panics if the reference to the netlist is lost.
785-
pub fn delete_uses(self) -> Result<Object<I>, String> {
787+
pub fn delete_uses(self) -> Result<Object<I>, Error> {
786788
let netlist = self
787789
.netref
788790
.borrow()
@@ -798,7 +800,7 @@ where
798800
///
799801
/// Panics if either `self` or `other` is a multi-output circuit node.
800802
/// Panics if the weak reference to the netlist is lost.
801-
pub fn replace_uses_with(self, other: &Self) -> Result<Object<I>, String> {
803+
pub fn replace_uses_with(self, other: &Self) -> Result<Object<I>, Error> {
802804
let netlist = self
803805
.netref
804806
.borrow()
@@ -1157,7 +1159,7 @@ where
11571159
self: &Rc<Self>,
11581160
object: Object<I>,
11591161
operands: &[DrivenNet<I>],
1160-
) -> Result<NetRef<I>, String> {
1162+
) -> Result<NetRef<I>, Error> {
11611163
let index = self.objects.borrow().len();
11621164
let weak = Rc::downgrade(self);
11631165
let operands = operands
@@ -1199,19 +1201,15 @@ where
11991201
inst_type: I,
12001202
inst_name: Identifier,
12011203
operands: &[DrivenNet<I>],
1202-
) -> Result<NetRef<I>, String> {
1204+
) -> Result<NetRef<I>, Error> {
12031205
let nets = inst_type
12041206
.get_output_ports()
12051207
.into_iter()
12061208
.map(|pnet| pnet.with_name(&inst_name + pnet.get_identifier()))
12071209
.collect::<Vec<_>>();
12081210
let input_count = inst_type.get_input_ports().into_iter().count();
12091211
if operands.len() != input_count {
1210-
return Err(format!(
1211-
"Expected {} operands, got {}",
1212-
input_count,
1213-
operands.len()
1214-
));
1212+
return Err(Error::ArgumentMismatch(input_count, operands.len()));
12151213
}
12161214
let obj = Object::Instance(nets, inst_name, inst_type);
12171215
self.insert_object(obj, operands)
@@ -1282,22 +1280,20 @@ where
12821280
}
12831281

12841282
/// Set an added object as a top-level output.
1285-
pub fn expose_net(&self, net: DrivenNet<I>) -> Result<DrivenNet<I>, String> {
1283+
pub fn expose_net(&self, net: DrivenNet<I>) -> Result<DrivenNet<I>, Error> {
12861284
if net.is_an_input() {
1287-
return Err(
1288-
"Cannot expose an input net as output without a new name to bind to".to_string(),
1289-
);
1285+
return Err(Error::InputNeedsAlias(net.as_net().clone()));
12901286
}
12911287
let mut outputs = self.outputs.borrow_mut();
12921288
outputs.insert(net.get_operand(), net.as_net().clone());
12931289
Ok(net)
12941290
}
12951291

12961292
/// Unlink a circuit node from the rest of the netlist. Return the object that was being stored.
1297-
pub fn delete_net_uses(&self, netref: NetRef<I>) -> Result<Object<I>, String> {
1293+
pub fn delete_net_uses(&self, netref: NetRef<I>) -> Result<Object<I>, Error> {
12981294
let unwrapped = netref.clone().unwrap();
12991295
if Rc::strong_count(&unwrapped) > 3 {
1300-
return Err("Cannot delete. References still exist on this node".to_string());
1296+
return Err(Error::DanglingReference(netref.nets().collect()));
13011297
}
13021298
let old_index = unwrapped.borrow().get_index();
13031299
let objects = self.objects.borrow();
@@ -1336,10 +1332,10 @@ where
13361332

13371333
/// Replaces the uses of a circuit node with another circuit node. The [Object] stored at `of` is returned.
13381334
/// Panics if `of` and `with` are not single-output nodes.
1339-
pub fn replace_net_uses(&self, of: NetRef<I>, with: &NetRef<I>) -> Result<Object<I>, String> {
1335+
pub fn replace_net_uses(&self, of: NetRef<I>, with: &NetRef<I>) -> Result<Object<I>, Error> {
13401336
let unwrapped = of.clone().unwrap();
13411337
if Rc::strong_count(&unwrapped) > 3 {
1342-
return Err("Cannot replace. References still exist on this node".to_string());
1338+
return Err(Error::DanglingReference(of.nets().collect()));
13431339
}
13441340

13451341
let old_tag: DrivenNet<I> = of.clone().into();
@@ -1405,7 +1401,7 @@ where
14051401
}
14061402

14071403
/// Constructs an analysis of the netlist.
1408-
pub fn get_analysis<'a, A: Analysis<'a, I>>(&'a self) -> Result<A, String> {
1404+
pub fn get_analysis<'a, A: Analysis<'a, I>>(&'a self) -> Result<A, Error> {
14091405
A::build(self)
14101406
}
14111407

@@ -1450,7 +1446,7 @@ where
14501446
}
14511447

14521448
/// Cleans unused nodes from the netlist, returning `Ok(true)` if the netlist changed.
1453-
pub fn clean_once(&self) -> Result<bool, String> {
1449+
pub fn clean_once(&self) -> Result<bool, Error> {
14541450
let mut dead_objs = HashSet::new();
14551451
{
14561452
let fan_out = self.get_analysis::<FanOutTable<I>>()?;
@@ -1477,11 +1473,10 @@ where
14771473
let mut remap: HashMap<usize, usize> = HashMap::new();
14781474
for (old_index, obj) in old_objects.into_iter().enumerate() {
14791475
if dead_objs.contains(&old_index) {
1476+
// 1. this ref, 2. as an output
14801477
if Rc::strong_count(&obj) > 2 {
1481-
return Err(format!(
1482-
"Cannot delete object {} as a NetRef still exists, or it is an output. SC = {}",
1483-
obj.borrow().get(),
1484-
Rc::strong_count(&obj)
1478+
return Err(Error::DanglingReference(
1479+
obj.borrow().get().get_nets().to_vec(),
14851480
));
14861481
}
14871482
continue;
@@ -1513,7 +1508,7 @@ where
15131508

15141509
/// Greedly removes unused nodes from the netlist, until it stops changing.
15151510
/// Returns true if the netlist was changed.
1516-
pub fn clean(&self) -> Result<bool, String> {
1511+
pub fn clean(&self) -> Result<bool, Error> {
15171512
if !self.clean_once()? {
15181513
Ok(false)
15191514
} else {
@@ -1526,42 +1521,39 @@ where
15261521
}
15271522

15281523
/// Returns `true` if all the nets are uniquely named
1529-
fn nets_unique(&self) -> bool {
1524+
fn nets_unique(&self) -> Result<(), Error> {
15301525
let mut nets = HashSet::new();
15311526
for net in self.into_iter() {
1532-
if !nets.insert(net.take_identifier()) {
1533-
return false;
1527+
if !nets.insert(net.clone().take_identifier()) {
1528+
return Err(Error::NonuniqueNets(vec![net]));
15341529
}
15351530
}
1536-
true
1531+
Ok(())
15371532
}
15381533

15391534
/// Returns `true` if all the nets are uniquely named
1540-
fn insts_unique(&self) -> bool {
1535+
fn insts_unique(&self) -> Result<(), Error> {
15411536
let mut insts = HashSet::new();
15421537
for inst in self.objects() {
15431538
if let Some(name) = inst.get_instance_name()
1544-
&& !insts.insert(name)
1539+
&& !insts.insert(name.clone())
15451540
{
1546-
return false;
1541+
return Err(Error::NonuniqueInsts(vec![name]));
15471542
}
15481543
}
1549-
true
1544+
Ok(())
15501545
}
15511546

15521547
/// Verifies that a netlist is well-formed.
1553-
pub fn verify(&self) -> Result<(), String> {
1548+
pub fn verify(&self) -> Result<(), Error> {
15541549
if self.outputs.borrow().is_empty() {
1555-
return Err("Netlist has no outputs".to_string());
1550+
return Err(Error::NoOutputs);
15561551
}
15571552

1558-
if !self.nets_unique() {
1559-
return Err("Netlist contains non-unique nets (multiple drivers)".to_string());
1560-
}
1553+
self.nets_unique()?;
1554+
1555+
self.insts_unique()?;
15611556

1562-
if !self.insts_unique() {
1563-
return Err("Netlist contains non-unique instances".to_string());
1564-
}
15651557
Ok(())
15661558
}
15671559
}

0 commit comments

Comments
 (0)