Skip to content

Commit 28399c9

Browse files
committed
feat(interpreter): pretty error msgs on div/mod by zero
1 parent 1e0beb5 commit 28399c9

3 files changed

Lines changed: 54 additions & 13 deletions

File tree

interpreter/src/lib.rs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
pub(crate) mod ir;
77
mod vm;
88

9+
use std::borrow::Cow;
10+
911
use ariadne::{Color, Label, ReportBuilder};
1012
pub use ir::{
1113
Instruction,
@@ -24,12 +26,15 @@ pub enum InterpreterError {
2426
RecursionDepth(AriadneSpan),
2527
#[error("Indirect call to an undefined function!")]
2628
UnknownIndFunction(AriadneSpan, String),
29+
#[error("Attempted to divide `{}` by zero here!", quacks_like_a_float(.1))]
30+
DivByZeroAttempted(AriadneSpan, String),
2731
}
2832

2933
impl InterpreterError {
3034
pub fn emit_diagnostic(&self, store: &mut DiagnosticStore) {
3135
match self {
32-
Self::UnknownIndFunction(span, _)
36+
Self::DivByZeroAttempted(span, _)
37+
| Self::UnknownIndFunction(span, _)
3338
| Self::RecursionDepth(span)
3439
| Self::ArityMismatch(span, _, _)
3540
| Self::UnknownFunction(span) => self.add_diagnostic_cached(store, span.clone()),
@@ -43,7 +48,8 @@ impl Diagnostic for InterpreterError {
4348
}
4449
fn span(&self) -> Option<Span> {
4550
match self {
46-
Self::UnknownIndFunction((_, span), _)
51+
Self::DivByZeroAttempted((_, span), _)
52+
| Self::UnknownIndFunction((_, span), _)
4753
| Self::RecursionDepth((_, span))
4854
| Self::ArityMismatch((_, span), _, _)
4955
| Self::UnknownFunction((_, span)) => Some(span.clone()),
@@ -75,10 +81,26 @@ impl Diagnostic for InterpreterError {
7581
Self::UnknownIndFunction(_, name) => {
7682
&format!("This code tried to call the unknown function `{name}` indirectly.")
7783
}
84+
Self::DivByZeroAttempted(_, _) => {
85+
"Division and modular arithmetic by zero is always fatal in AWK. To avoid this, \
86+
you can use an\nif statement or a ternary expression. You may optionally introduce \
87+
IEEE 754 not-a-number values:\n \
88+
`a / b` --> `(+b != 0) ? (a / b) : +\"+nan\"`.\nNote the importance of the `+` \
89+
operator, which forces values into numbers. Otherwise, some\nvalues of `b`, like \
90+
an empty string, would still trigger this error. It also parses the \"+nan\"."
91+
}
7892
};
7993
report.set_help(note);
8094
}
8195
fn is_unrecoverable(&self) -> bool {
8296
true
8397
}
8498
}
99+
100+
fn quacks_like_a_float(val: &str) -> Cow<'_, str> {
101+
if val.parse::<f64>().is_ok() {
102+
val.into()
103+
} else {
104+
format!("{val:?}").into()
105+
}
106+
}

interpreter/src/vm.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -494,15 +494,29 @@ impl<'a> Interpreter<'a> {
494494
self.write_reg(dest, val);
495495
}
496496
Instruction::Divide { dest, lhs, rhs, tyl, tyr } => {
497-
let val = Arg::get_val2(lhs, tyl, rhs, tyr, self, |lhs, rhs| lhs / rhs);
497+
let Some(val) = Arg::get_val2(lhs, tyl, rhs, tyr, self, |lhs, rhs| lhs / rhs)
498+
else {
499+
let uninit = &mut MaybeUninit::uninit();
500+
return Err(InterpreterError::DivByZeroAttempted(
501+
self.get_span(metadata),
502+
lhs.get_val(tyl, self, uninit).to_string(),
503+
));
504+
};
498505
self.write_reg(dest, val);
499506
}
500507
Instruction::Raise { dest, lhs, rhs, tyl, tyr } => {
501508
let val = Arg::get_val2(lhs, tyl, rhs, tyr, self, |lhs, rhs| lhs ^ rhs);
502509
self.write_reg(dest, val);
503510
}
504511
Instruction::Modulo { dest, lhs, rhs, tyl, tyr } => {
505-
let val = Arg::get_val2(lhs, tyl, rhs, tyr, self, |lhs, rhs| lhs % rhs);
512+
let Some(val) = Arg::get_val2(lhs, tyl, rhs, tyr, self, |lhs, rhs| lhs % rhs)
513+
else {
514+
let uninit = &mut MaybeUninit::uninit();
515+
return Err(InterpreterError::DivByZeroAttempted(
516+
self.get_span(metadata),
517+
lhs.get_val(tyl, self, uninit).to_string(),
518+
));
519+
};
506520
self.write_reg(dest, val);
507521
}
508522
Instruction::Concat { dest, lhs, rhs, tyl, tyr } => {

interpreter/src/vm/types.rs

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use std::{
88
cell::RefCell,
99
fmt::Display,
1010
hash::Hash,
11+
hint::cold_path,
1112
io::Write,
1213
mem::discriminant,
1314
ops::{Add, BitXor, Div, Mul, Rem, Sub},
@@ -17,6 +18,14 @@ use std::{
1718
use ahash::RandomState;
1819
use hashbrown::HashMap;
1920

21+
#[inline(always)]
22+
fn likely(b: bool) -> bool {
23+
if !b {
24+
cold_path();
25+
}
26+
b
27+
}
28+
2029
/// Shared array storage. AWK arrays are reference-counted (assignment of array
2130
/// names is not a deep copy); `Rc`/`RefCell` is enough while the VM is single-threaded.
2231
pub type ArrayMap<'a> = HashMap<String, Value<'a>, RandomState>;
@@ -155,13 +164,11 @@ impl<'a> Mul for &'_ Value<'a> {
155164
}
156165

157166
impl<'a> Div for &'_ Value<'a> {
158-
type Output = Value<'a>;
167+
type Output = Option<Value<'a>>;
159168

160169
fn div(self, rhs: Self) -> Self::Output {
161170
let rhs = rhs.to_num();
162-
// TODO: panic "nicely" on div by zero.
163-
assert!(rhs != 0., "Division by zero attempted in '/'!");
164-
Value::Float(self.to_num() / rhs)
171+
likely(rhs != 0.).then(|| Value::Float(self.to_num() / rhs))
165172
}
166173
}
167174

@@ -174,13 +181,11 @@ impl<'a> BitXor for &'_ Value<'a> {
174181
}
175182

176183
impl<'a> Rem for &'_ Value<'a> {
177-
type Output = Value<'a>;
184+
type Output = Option<Value<'a>>;
178185

179186
fn rem(self, rhs: Self) -> Self::Output {
180-
let (lhs, rhs) = (self.to_num(), rhs.to_num());
181-
// TODO: panic "nicely" on div by zero.
182-
assert!(lhs != 0. || rhs != 0., "Division by zero attempted in '%'!");
183-
Value::Float(lhs % rhs)
187+
let rhs = rhs.to_num();
188+
likely(rhs != 0.).then(|| Value::Float(self.to_num() % rhs))
184189
}
185190
}
186191

0 commit comments

Comments
 (0)