Skip to content

Commit a74b456

Browse files
Merge pull request #2 from gdziadkiewicz/Add_error_handling_and_more_tests
Add better error handling and more tests
2 parents 2e9ef8e + cbbed9e commit a74b456

File tree

4 files changed

+152
-54
lines changed

4 files changed

+152
-54
lines changed

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,8 @@ Inspired by https://www.codewars.com/kata/526156943dfe7ce06200063e
77
### TODO:
88
* [x] Add CLI
99
* [x] Add handling of whitespace
10-
* [ ] Add better error handling to the interpreted
11-
* [x] Add CI
10+
* [x] Add better error handling to the interpreter
11+
* [x] Add CI
12+
* [ ] Rewrite tests with Results to get rid of unwrapping
13+
* [ ] Rewrite add, sub and mul test as property tests
14+
* [ ] Try to use b"" to make the tests more readable

build.ps1

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
cargo test --all-features
2+
if ($LASTEXITCODE -ne 0) {
3+
Write-Error "Cargo test failed with exit code $LASTEXITCODE"
4+
exit $LASTEXITCODE
5+
}
6+
7+
cargo fmt --all -- --check
8+
if ($LASTEXITCODE -ne 0) {
9+
Write-Error "Cargo fmt failed with exit code $LASTEXITCODE"
10+
exit $LASTEXITCODE
11+
}
12+
13+
cargo clippy --all --all-features --tests -- -D warnings
14+
if ($LASTEXITCODE -ne 0) {
15+
Write-Error "Cargo clippy failed with exit code $LASTEXITCODE"
16+
exit $LASTEXITCODE
17+
}

src/lib.rs

Lines changed: 129 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,29 @@
1-
pub fn brain_luck(code: &str, input: Vec<u8>) -> Vec<u8> {
1+
//create error type for my lib and use it for brain_luck function
2+
use std::error::Error;
3+
4+
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
5+
pub enum BrainLuckError {
6+
UnexpectedCharInCode(char),
7+
UnexpectedEndOfInput,
8+
UnbalancedBrackets,
9+
}
10+
11+
impl std::fmt::Display for BrainLuckError {
12+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
13+
let message = match self {
14+
BrainLuckError::UnexpectedCharInCode(c) => {
15+
format!("unexpected char {} occured in code.", c)
16+
}
17+
BrainLuckError::UnexpectedEndOfInput => String::from("unexpected end of input."),
18+
BrainLuckError::UnbalancedBrackets => String::from("unbalanced brackets."),
19+
};
20+
f.write_str(&message)
21+
}
22+
}
23+
24+
impl Error for BrainLuckError {}
25+
26+
pub fn brain_luck(code: &str, input: Vec<u8>) -> Result<Vec<u8>, BrainLuckError> {
227
let mut output = Vec::new();
328
let mut input = input.iter();
429
let mut memory = vec![0u8; 1000];
@@ -8,54 +33,29 @@ pub fn brain_luck(code: &str, input: Vec<u8>) -> Vec<u8> {
833
let insts = code.chars().collect::<Vec<_>>();
934
while let Some(c) = insts.get(ins_ptr) {
1035
match c {
11-
'>' => {
12-
mem_ptr += 1;
13-
ins_ptr += 1;
14-
}
15-
'<' => {
16-
mem_ptr -= 1;
17-
ins_ptr += 1;
18-
}
19-
'+' => {
20-
memory[mem_ptr] = memory[mem_ptr].wrapping_add(1);
21-
ins_ptr += 1;
22-
}
23-
'-' => {
24-
memory[mem_ptr] = memory[mem_ptr].wrapping_sub(1);
25-
ins_ptr += 1;
26-
}
27-
'.' => {
28-
output.push(memory[mem_ptr]);
29-
ins_ptr += 1;
30-
}
31-
',' => {
32-
let b = input.next().unwrap();
33-
memory[mem_ptr] = *b;
34-
ins_ptr += 1;
35-
}
36+
'>' => mem_ptr += 1,
37+
'<' => mem_ptr -= 1,
38+
//TODO: add handling for mem_ptr going out of bounds (or accessing memory outside of bounds)
39+
'+' => memory[mem_ptr] = memory[mem_ptr].wrapping_add(1),
40+
'-' => memory[mem_ptr] = memory[mem_ptr].wrapping_sub(1),
41+
'.' => output.push(memory[mem_ptr]),
42+
',' => memory[mem_ptr] = *input.next().ok_or(BrainLuckError::UnexpectedEndOfInput)?,
3643
'[' => {
37-
let b = memory[mem_ptr];
38-
if b == 0 {
39-
//jump to next after closing bracket
40-
ins_ptr = after_matching_closing_bracket(&insts, ins_ptr, Bracket::LBracket);
41-
} else {
42-
ins_ptr += 1;
44+
if memory[mem_ptr] == 0 {
45+
ins_ptr = matching_closing_bracket_ptr(&insts, ins_ptr, Bracket::LBracket)?;
4346
}
4447
}
4548
']' => {
46-
let b = memory[mem_ptr];
47-
if b != 0 {
48-
//jump to previous after closing bracket
49-
ins_ptr = after_matching_closing_bracket(&insts, ins_ptr, Bracket::RBracket);
50-
} else {
51-
ins_ptr += 1;
49+
if memory[mem_ptr] != 0 {
50+
ins_ptr = matching_closing_bracket_ptr(&insts, ins_ptr, Bracket::RBracket)?;
5251
}
5352
}
54-
c if c.is_whitespace() => ins_ptr += 1,
55-
unexpected_char => panic!("Unexpected char {} occured in code.", unexpected_char),
53+
c if c.is_whitespace() => (),
54+
unexpected_char => return Err(BrainLuckError::UnexpectedCharInCode(*unexpected_char)),
5655
}
56+
ins_ptr += 1;
5757
}
58-
output
58+
Ok(output)
5959
}
6060

6161
enum Bracket {
@@ -66,14 +66,18 @@ enum Bracket {
6666
macro_rules! add {
6767
($u:ident,$s:ident) => {
6868
if $s < 0 {
69-
$u - (-$s) as usize
69+
$u.checked_sub((-$s) as usize)
7070
} else {
71-
$u + $s as usize
71+
Some($u + $s as usize)
7272
}
7373
};
7474
}
7575

76-
fn after_matching_closing_bracket(insts: &[char], ins_ptr: usize, b: Bracket) -> usize {
76+
fn matching_closing_bracket_ptr(
77+
insts: &[char],
78+
ins_ptr: usize,
79+
b: Bracket,
80+
) -> Result<usize, BrainLuckError> {
7781
let direction = match b {
7882
Bracket::LBracket => 1,
7983
Bracket::RBracket => -1,
@@ -84,18 +88,22 @@ fn after_matching_closing_bracket(insts: &[char], ins_ptr: usize, b: Bracket) ->
8488
loop {
8589
let c = insts.get(ptr);
8690
match c {
87-
None => panic!("Unbalanced brackets(EOF)!"),
91+
None => return Err(BrainLuckError::UnbalancedBrackets),
8892
Some('[') => counter += direction,
8993
Some(']') => counter -= direction,
9094
Some(_) => (),
9195
}
9296
if counter < 0 {
93-
panic!("Unbalanced brackets!")
97+
return Err(BrainLuckError::UnbalancedBrackets);
9498
}
9599
if counter == 0 {
96-
return ptr;
100+
return Ok(ptr);
97101
}
98-
ptr = add!(ptr, direction);
102+
ptr = match add!(ptr, direction) {
103+
Some(p) => p,
104+
None if direction == -1 => return Err(BrainLuckError::UnbalancedBrackets),
105+
None => unreachable!(),
106+
};
99107
}
100108
}
101109

@@ -108,7 +116,7 @@ mod tests {
108116
fn echo_until_byte_255_encountered() {
109117
// Echo until byte 255 encountered
110118
assert!(
111-
String::from_utf8(brain_luck(",+[-.,+]", ez_vec("Codewars", 255))).unwrap()
119+
String::from_utf8(brain_luck(",+[-.,+]", ez_vec("Codewars", 255)).unwrap()).unwrap()
112120
== "Codewars"
113121
);
114122
}
@@ -117,14 +125,84 @@ mod tests {
117125
fn echo_until_byte_0_encountered() {
118126
// Echo until byte 0 encountered
119127
assert!(
120-
String::from_utf8(brain_luck(",[.[-],]", ez_vec("Codewars", 0))).unwrap() == "Codewars"
128+
String::from_utf8(brain_luck(",[.[-],]", ez_vec("Codewars", 0)).unwrap()).unwrap()
129+
== "Codewars"
121130
);
122131
}
123132

124133
#[test]
125134
fn multiply_two_numbers() {
126135
// Multiply two numbers
127-
assert!(brain_luck(",>,<[>[->+>+<<]>>[-<<+>>]<<<-]>>.", vec![8, 9]) == vec![72]);
136+
assert!(brain_luck(",>,<[>[->+>+<<]>>[-<<+>>]<<<-]>>.", vec![8, 9]).unwrap() == vec![72]);
137+
}
138+
139+
#[test]
140+
fn unexpected_char_in_code() {
141+
// Unexpected character in code
142+
assert!(matches!(
143+
brain_luck(",[.[-]a,]", ez_vec("Hello", 0)),
144+
Err(BrainLuckError::UnexpectedCharInCode('a'))
145+
));
146+
}
147+
148+
#[test]
149+
fn unbalanced_brackets() {
150+
// Unbalanced brackets
151+
assert!(
152+
brain_luck("[.[-],", ez_vec("Hello", 0)) == Err(BrainLuckError::UnbalancedBrackets)
153+
);
154+
}
155+
156+
#[test]
157+
fn unexpected_end_of_input() {
158+
// Unexpected end of input
159+
assert!(matches!(
160+
brain_luck(",[.[-],]", vec![72, 101, 108, 108]),
161+
Err(BrainLuckError::UnexpectedEndOfInput)
162+
));
163+
}
164+
165+
#[test]
166+
fn add_two_numbers() {
167+
// Add two numbers
168+
assert!(brain_luck(",>,<[->+<]>.", vec![5, 10]).unwrap() == vec![15]);
169+
}
170+
171+
#[test]
172+
fn subtract_two_numbers() {
173+
// Subtract two numbers
174+
assert!(brain_luck(">,>,[<[->]<]>>[[<+>-]>>]<<<.", vec![10, 5]).unwrap() == vec![5]);
175+
}
176+
177+
#[test]
178+
fn matching_closing_bracket_ptr_balanced() {
179+
// Balanced brackets
180+
let code = "++[--[++]]";
181+
let insts = code.chars().collect::<Vec<_>>();
182+
assert!(matching_closing_bracket_ptr(&insts, 2, Bracket::LBracket).unwrap() == 9);
183+
assert!(matching_closing_bracket_ptr(&insts, 9, Bracket::RBracket).unwrap() == 2);
184+
}
185+
186+
#[test]
187+
fn matching_closing_bracket_ptr_unbalanced_left() {
188+
// Unbalanced brackets (left)
189+
let code = "++[--[++]";
190+
let insts = code.chars().collect::<Vec<_>>();
191+
assert!(matches!(
192+
matching_closing_bracket_ptr(&insts, 2, Bracket::LBracket),
193+
Err(BrainLuckError::UnbalancedBrackets)
194+
));
195+
}
196+
197+
#[test]
198+
fn matching_closing_bracket_ptr_unbalanced_right() {
199+
// Unbalanced brackets (right)
200+
let code = "++[--[++]]]";
201+
let insts = code.chars().collect::<Vec<_>>();
202+
assert!(matches!(
203+
matching_closing_bracket_ptr(&insts, 10, Bracket::RBracket),
204+
Err(BrainLuckError::UnbalancedBrackets)
205+
));
128206
}
129207

130208
// Takes a static string and a terminating byte and returns an owned Vec<u8> for convenience

src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,6 @@ fn main() -> Result<(), Box<dyn Error>> {
2121
let code = fs::read_to_string(&args.code)?;
2222
let input = args.input.as_ref().map(fs::read).unwrap_or(Ok(vec![]))?;
2323
let output = brain_luck(&code, input);
24-
println!("{}", String::from_utf8(output)?);
24+
println!("{}", String::from_utf8(output?)?);
2525
Ok(())
2626
}

0 commit comments

Comments
 (0)