Skip to content

Commit f6adfa3

Browse files
committed
refactor: Not allowing 'zero' Position
At the cost of more verbose propagation of the current `Position`, removing the `at_no_pos`, `patch_err_pos`, `with_err_no_pos` and related methods. The code should not be constructing "zero" Position objects, hoping that they will be filled-in later. Removed method `try_map` of `Positioned` as it was used only once. Removed `ErrorEnvelope::NoPos` from interpreter Enforcing that `Position` cannot have zero row or col. Removed `Position::zero` method.
1 parent 7c563be commit f6adfa3

86 files changed

Lines changed: 618 additions & 540 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

rusty_basic/src/error_envelope.rs

Lines changed: 32 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,36 @@
11
use rusty_common::{HasPos, Position};
22

3-
//
4-
// ErrorEnvelope
5-
//
6-
3+
/// ErrorEnvelope wraps an error with stacktrace information.
74
#[derive(Clone, Debug, PartialEq, Eq)]
8-
pub enum ErrorEnvelope<T> {
9-
NoPos(T),
10-
Pos(T, Position),
11-
Stacktrace(T, Vec<Position>),
12-
}
13-
14-
impl<T> AsRef<T> for ErrorEnvelope<T> {
15-
fn as_ref(&self) -> &T {
16-
match self {
17-
Self::NoPos(t) | Self::Pos(t, _) | Self::Stacktrace(t, _) => t,
18-
}
19-
}
20-
}
5+
pub struct ErrorEnvelope<T>(T, Vec<Position>);
216

227
impl<T> ErrorEnvelope<T> {
23-
pub fn into_err(self) -> T {
24-
match self {
25-
Self::NoPos(t) | Self::Pos(t, _) | Self::Stacktrace(t, _) => t,
26-
}
8+
pub fn new(err: T, pos: Position) -> Self {
9+
Self(err, vec![pos])
2710
}
2811

29-
/// Patches the envelope with the given position.
30-
/// If the object already has a position or a stacktrace,
31-
/// it is returned as-is.
32-
pub fn patch_pos(self, pos: Position) -> Self {
33-
match self {
34-
Self::NoPos(t) => Self::Pos(t, pos),
35-
_ => self,
36-
}
12+
pub fn new_with_stacktrace(err: T, stacktrace: Vec<Position>) -> Self {
13+
Self(err, stacktrace)
3714
}
3815

39-
pub fn patch_stacktrace(self, v_new: &mut Vec<Position>) -> Self {
40-
let mut v_old: Vec<Position> = match &self {
41-
Self::NoPos(_) => vec![],
42-
Self::Pos(_, p) => vec![*p],
43-
Self::Stacktrace(_, v) => v.clone(),
44-
};
45-
v_old.append(v_new);
46-
let body = self.into_err();
47-
if v_old.is_empty() {
48-
Self::NoPos(body)
49-
} else if v_old.len() == 1 {
50-
Self::Pos(body, v_old.pop().unwrap())
16+
pub fn new_draining_stacktrace(err: T, stacktrace: &mut Vec<Position>) -> Self {
17+
debug_assert!(!stacktrace.is_empty());
18+
if stacktrace.len() == 1 {
19+
Self::new(err, stacktrace.pop().unwrap())
5120
} else {
52-
Self::Stacktrace(body, v_old)
21+
let new_stacktrace = stacktrace.drain(0..stacktrace.len()).collect();
22+
Self(err, new_stacktrace)
5323
}
5424
}
55-
}
5625

57-
//
58-
// result.with_err_no_pos()
59-
//
60-
61-
pub trait WithErrNoPos<TResult> {
62-
fn with_err_no_pos(self) -> TResult;
63-
}
26+
pub fn err(&self) -> &T {
27+
&self.0
28+
}
6429

65-
impl<T, E> WithErrNoPos<Result<T, ErrorEnvelope<E>>> for Result<T, E> {
66-
fn with_err_no_pos(self) -> Result<T, ErrorEnvelope<E>> {
67-
self.map_err(|e| ErrorEnvelope::NoPos(e))
30+
pub fn appen_draining_stacktrace(self, stacktrace: &mut Vec<Position>) -> Self {
31+
let Self(err, mut old_stacktrace) = self;
32+
old_stacktrace.append(stacktrace);
33+
Self(err, old_stacktrace)
6834
}
6935
}
7036

@@ -78,20 +44,24 @@ pub trait WithErrAt<Pos, TResult> {
7844

7945
impl<Pos: HasPos, T, E> WithErrAt<&Pos, Result<T, ErrorEnvelope<E>>> for Result<T, E> {
8046
fn with_err_at(self, p: &Pos) -> Result<T, ErrorEnvelope<E>> {
81-
self.map_err(|e| ErrorEnvelope::Pos(e, p.pos()))
47+
self.map_err(|e| ErrorEnvelope::new(e, p.pos()))
8248
}
8349
}
8450

85-
//
86-
// result.patch_err_pos()
87-
//
51+
pub trait WithStacktrace<O = Self> {
52+
/// Adds the given stacktrace to the error.
53+
/// The given stacktrace is emptied, to avoid adding the same items twice.
54+
fn with_stacktrace(self, stacktrace: &mut Vec<Position>) -> O;
55+
}
8856

89-
pub trait PatchErrPos<Pos, TResult> {
90-
fn patch_err_pos(self, p: Pos) -> TResult;
57+
impl<T> WithStacktrace for ErrorEnvelope<T> {
58+
fn with_stacktrace(self, stacktrace: &mut Vec<Position>) -> Self {
59+
self.appen_draining_stacktrace(stacktrace)
60+
}
9161
}
9262

93-
impl<Pos: HasPos, T, E> PatchErrPos<&Pos, Self> for Result<T, ErrorEnvelope<E>> {
94-
fn patch_err_pos(self, p: &Pos) -> Self {
95-
self.map_err(|e| e.patch_pos(p.pos()))
63+
impl<T, E> WithStacktrace<Result<T, ErrorEnvelope<E>>> for Result<T, E> {
64+
fn with_stacktrace(self, stacktrace: &mut Vec<Position>) -> Result<T, ErrorEnvelope<E>> {
65+
self.map_err(|err| ErrorEnvelope::new_draining_stacktrace(err, stacktrace))
9666
}
9767
}

rusty_basic/src/interpreter/built_ins/instr.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ mod tests {
6666
assert_prints!(r#"PRINT INSTR("", "")"#, "0");
6767
assert_eq!(
6868
interpret_err(r#"PRINT INSTR(0, "oops", "zero")"#),
69-
ErrorEnvelope::Pos(RuntimeError::IllegalFunctionCall, Position::new(1, 7))
69+
ErrorEnvelope::new(RuntimeError::IllegalFunctionCall, Position::new(1, 7))
7070
);
7171
}
7272
}

rusty_basic/src/interpreter/built_ins/kill.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ mod tests {
2424
fn test_kill_edge_cases() {
2525
assert_eq!(
2626
interpret_err(r#"KILL "KILL2.TXT""#),
27-
ErrorEnvelope::Pos(RuntimeError::FileNotFound, Position::new(1, 1))
27+
ErrorEnvelope::new(RuntimeError::FileNotFound, Position::new(1, 1))
2828
);
2929
}
3030
}

rusty_basic/src/interpreter/interpreter.rs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use super::handlers::{cast, comparison, logical, math, registers, subprogram, var_path};
2-
use crate::error_envelope::{WithErrAt, WithErrNoPos};
2+
use crate::error_envelope::WithErrAt;
33
use crate::instruction_generator::{Instruction, InstructionGeneratorResult, Path, PrinterType};
44
use crate::interpreter::arguments::ArgumentInfo;
55
use crate::interpreter::context::*;
@@ -17,7 +17,7 @@ use crate::interpreter::registers::{RegisterStack, Registers};
1717
use crate::interpreter::screen::{CrossTermScreen, Screen};
1818
use crate::interpreter::write_printer::WritePrinter;
1919
use crate::interpreter::Stdlib;
20-
use crate::{RuntimeError, RuntimeErrorPos};
20+
use crate::{RuntimeError, RuntimeErrorPos, WithStacktrace};
2121
use rusty_common::*;
2222
use rusty_linter::{HasUserDefinedTypes, QBNumberCast};
2323
use rusty_parser::UserDefinedTypes;
@@ -215,7 +215,7 @@ impl<TStdlib: Stdlib, TStdIn: Input, TStdOut: Printer, TLpt1: Printer, U: HasUse
215215
}
216216
},
217217
Err(e) => {
218-
self.last_error_code = Some(e.as_ref().get_code());
218+
self.last_error_code = Some(e.err().get_code());
219219
match ctx.error_handler {
220220
ErrorHandler::Address(handler_address) => {
221221
// store error address, so we can call RESUME and RESUME NEXT from within the error handler
@@ -227,7 +227,7 @@ impl<TStdlib: Stdlib, TStdIn: Input, TStdOut: Printer, TLpt1: Printer, U: HasUse
227227
i = ctx.nearest_statement_finder.find_next(i);
228228
}
229229
ErrorHandler::None => {
230-
return Err(e.patch_stacktrace(&mut self.stacktrace));
230+
return Err(e.with_stacktrace(&mut self.stacktrace));
231231
}
232232
}
233233
}
@@ -433,12 +433,14 @@ impl<TStdlib: Stdlib, TStdIn: Input, TStdOut: Printer, TLpt1: Printer, U: HasUse
433433
subprogram::push_a_to_named_arg(self, param_name);
434434
}
435435
Instruction::BuiltInSub(s) => {
436-
// note: not patching the error pos for built-ins because it's already in-place by Instruction::PushStack
437-
super::built_ins::run_sub(s, self).with_err_no_pos()?;
436+
// the stacktrace should be already populated by Instruction::PushStack
437+
debug_assert!(!self.stacktrace.is_empty());
438+
super::built_ins::run_sub(s, self).with_stacktrace(&mut self.stacktrace)?;
438439
}
439440
Instruction::BuiltInFunction(f) => {
440-
// note: not patching the error pos for built-ins because it's already in-place by Instruction::PushStack
441-
super::built_ins::run_function(f, self).with_err_no_pos()?;
441+
// the stacktrace should be already populated by Instruction::PushStack
442+
debug_assert!(!self.stacktrace.is_empty());
443+
super::built_ins::run_function(f, self).with_stacktrace(&mut self.stacktrace)?;
442444
}
443445
Instruction::Label(_) => (), // no-op
444446
Instruction::Halt => {

rusty_basic/src/interpreter/test_utils.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ macro_rules! assert_interpreter_err {
246246
($program:expr, $expected_err:expr, $expected_row:expr, $expected_col:expr) => {
247247
assert_eq!(
248248
$crate::interpreter::test_utils::interpret_err($program),
249-
$crate::ErrorEnvelope::Pos(
249+
$crate::ErrorEnvelope::new(
250250
$expected_err,
251251
rusty_common::Position::new($expected_row, $expected_col)
252252
)

rusty_basic/src/interpreter/tests/loops.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ fn test_for_loop_with_zero_step() {
7575
";
7676
assert_eq!(
7777
interpret_err(input),
78-
ErrorEnvelope::Pos(RuntimeError::ForLoopZeroStep, Position::new(2, 27))
78+
ErrorEnvelope::new(RuntimeError::ForLoopZeroStep, Position::new(2, 27))
7979
);
8080
}
8181

rusty_basic/src/interpreter/tests/on_error.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ fn reset_error_handler() {
4646
let err = result.unwrap_err();
4747
assert_eq!(
4848
err,
49-
RuntimeErrorPos::Pos(RuntimeError::DivisionByZero, Position::new(5, 13))
49+
RuntimeErrorPos::new(RuntimeError::DivisionByZero, Position::new(5, 13))
5050
);
5151
assert_eq!(interpreter.stdout().output(), "oops");
5252
}

rusty_basic/src/interpreter/tests/sub_call.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ fn test_stacktrace() {
106106
"#;
107107
assert_eq!(
108108
interpret_err(program),
109-
ErrorEnvelope::Stacktrace(
109+
ErrorEnvelope::new_with_stacktrace(
110110
RuntimeError::Other("Invalid expression. Must be name=value.".to_string()),
111111
vec![
112112
Position::new(10, 13), // "inside" Environ

rusty_common/src/position.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ pub struct Position {
77

88
impl Position {
99
pub fn new(row: u32, col: u32) -> Self {
10+
debug_assert!(row > 0);
11+
debug_assert!(col > 0);
1012
Self { row, col }
1113
}
1214

@@ -23,10 +25,6 @@ impl Position {
2325
Self::new(1, 1)
2426
}
2527

26-
pub fn zero() -> Self {
27-
Self::new(0, 0)
28-
}
29-
3028
pub fn row(&self) -> u32 {
3129
self.row
3230
}

rusty_common/src/positioned.rs

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ use crate::Position;
44
/// within the file it was read from.
55
#[derive(Clone, Debug, PartialEq, Eq)]
66
pub struct Positioned<T> {
7-
// TODO make fields private
87
pub element: T,
98
pub pos: Position,
109
}
@@ -29,16 +28,6 @@ impl<T> Positioned<T> {
2928
pos,
3029
}
3130
}
32-
33-
/// Converts this instance to a result, given the specified function.
34-
/// The function acts on the element of this instance and returns
35-
/// the result. The error of the result is converted to a [Positioned] error.
36-
pub fn try_map<F, U, E>(&self, op: F) -> Result<U, Positioned<E>>
37-
where
38-
F: FnOnce(&T) -> Result<U, E>,
39-
{
40-
(op)(&self.element).map_err(|e| e.at_pos(self.pos))
41-
}
4231
}
4332

4433
// AtPos
@@ -59,10 +48,6 @@ pub trait AtPos: Sized {
5948
fn at_start(self) -> Positioned<Self> {
6049
self.at_pos(Position::start())
6150
}
62-
63-
fn at_no_pos(self) -> Positioned<Self> {
64-
self.at_pos(Position::zero())
65-
}
6651
}
6752

6853
impl<T> AtPos for T {}
@@ -94,7 +79,7 @@ impl<T> HasPos for Box<Positioned<T>> {
9479
}
9580

9681
//
97-
// NoPosIter
82+
// NoPosIter TODO: the following are used only in tests
9883
//
9984

10085
pub struct NoPosIter<'a, T: 'a, I>

0 commit comments

Comments
 (0)