diff --git a/src/token.rs b/src/token.rs index fc97550..56f1673 100644 --- a/src/token.rs +++ b/src/token.rs @@ -52,18 +52,22 @@ impl From<&str> for DelimTokenType { } impl DelimTokenType { - pub fn string(&self) -> String { + pub(crate) fn as_str(&self) -> &'static str { use DelimTokenType::*; match self { - OpenParen => "(".to_string(), - CloseParen => ")".to_string(), - OpenBracket => "[".to_string(), - CloseBracket => "]".to_string(), - OpenBrace => "{".to_string(), - CloseBrace => "}".to_string(), - Unknown => "??".to_string(), + OpenParen => "(", + CloseParen => ")", + OpenBracket => "[", + CloseBracket => "]", + OpenBrace => "{", + CloseBrace => "}", + Unknown => "??", } } + + pub fn string(&self) -> String { + self.as_str().to_string() + } } #[derive(Clone, PartialEq, Debug, Copy)] @@ -85,19 +89,10 @@ pub enum Token<'input> { pub fn check_op(token: Token, expected: &str) -> bool { match token { - Token::Delim(op, _) => { - if op.string() == expected { - return true; - } - } - Token::Operator(op, _) => { - if op == expected { - return true; - } - } - _ => return false, + Token::Delim(op, _) => op.as_str() == expected, + Token::Operator(op, _) => op == expected, + _ => false, } - return false; } impl<'input> Token<'input> { @@ -213,7 +208,7 @@ impl<'input> fmt::Display for Token<'input> { Function(val, span) => write!(f, "Function Token: {}, {}", val, span), String(val, span) => write!(f, "String Token: {}, {}", val, span), Semicolon(val, span) => write!(f, "Semicolon Token: {}, {}", val, span), - Delim(ty, span) => write!(f, "Delim Token: {}, {}", ty.string(), span), + Delim(ty, span) => write!(f, "Delim Token: {}, {}", ty.as_str(), span), EOF => write!(f, "EOF"), } } @@ -271,4 +266,55 @@ mod tests { fn test_is_open_bracket(#[case] input: Token, #[case] output: bool) { assert_eq!(input.is_open_bracket(), output) } + + #[rstest] + #[case(DelimTokenType::OpenParen, "(")] + #[case(DelimTokenType::CloseParen, ")")] + #[case(DelimTokenType::OpenBracket, "[")] + #[case(DelimTokenType::CloseBracket, "]")] + #[case(DelimTokenType::OpenBrace, "{")] + #[case(DelimTokenType::CloseBrace, "}")] + #[case(DelimTokenType::Unknown, "??")] + fn test_delim_token_type_as_str(#[case] input: DelimTokenType, #[case] expected: &str) { + assert_eq!(input.as_str(), expected); + // string() must remain consistent with as_str() + assert_eq!(input.string(), expected); + } + + #[rstest] + #[case(Token::Delim(DelimTokenType::OpenParen, Span(0, 1)), "(", true)] + #[case(Token::Delim(DelimTokenType::CloseBracket, Span(0, 1)), "]", true)] + #[case(Token::Delim(DelimTokenType::OpenBrace, Span(0, 1)), ")", false)] + #[case(Token::Operator("+", Span(0, 1)), "+", true)] + #[case(Token::Operator("!=", Span(0, 2)), "!=", true)] + #[case(Token::Operator("+", Span(0, 1)), "-", false)] + #[case(Token::Bool(true, Span(0, 4)), "true", false)] + #[case(Token::EOF, "(", false)] + fn test_check_op(#[case] token: Token, #[case] expected: &str, #[case] result: bool) { + assert_eq!(super::check_op(token, expected), result); + } + + #[rstest] + #[case(Token::Delim(DelimTokenType::CloseParen, Span(0, 0)), true)] + #[case(Token::Delim(DelimTokenType::OpenParen, Span(0, 0)), false)] + #[case(Token::Bool(false, Span(0, 0)), false)] + fn test_is_close_paren(#[case] input: Token, #[case] output: bool) { + assert_eq!(input.is_close_paren(), output) + } + + #[rstest] + #[case(Token::Delim(DelimTokenType::CloseBracket, Span(0, 0)), true)] + #[case(Token::Delim(DelimTokenType::OpenBracket, Span(0, 0)), false)] + #[case(Token::Bool(false, Span(0, 0)), false)] + fn test_is_close_bracket(#[case] input: Token, #[case] output: bool) { + assert_eq!(input.is_close_bracket(), output) + } + + #[rstest] + #[case(Token::Delim(DelimTokenType::CloseBrace, Span(0, 0)), true)] + #[case(Token::Delim(DelimTokenType::OpenBrace, Span(0, 0)), false)] + #[case(Token::Bool(false, Span(0, 0)), false)] + fn test_is_close_brace(#[case] input: Token, #[case] output: bool) { + assert_eq!(input.is_close_brace(), output) + } } diff --git a/src/tokenizer.rs b/src/tokenizer.rs index 2b7f954..2c5a516 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -58,7 +58,7 @@ impl<'a> Tokenizer<'a> { loop { match self.peek_one() { Some((_, _ch)) => { - if keyword::is_op(&(self.input[start..self.current() + 1].to_string())) { + if keyword::is_op(&self.input[start..self.current() + 1]) { self.next_one(); } else { break; @@ -145,7 +145,7 @@ impl<'a> Tokenizer<'a> { self.next()?; match token { Token::Delim(bracket, _) => { - if bracket.string() == op { + if bracket.as_str() == op { return Ok(()); } } @@ -409,4 +409,41 @@ mod tests { let ans = tokenizer.next(); assert!(ans.is_err()) } + + #[test] + fn test_expect_delim_ok() { + init(); + // cur_token starts as EOF; advance past '(' so cur_token == OpenParen, then expect it + let mut tokenizer = Tokenizer::new("(+"); + tokenizer.next().unwrap(); // cur_token = '(' + assert!(tokenizer.expect("(").is_ok()); + } + + #[test] + fn test_expect_operator_ok() { + init(); + let mut tokenizer = Tokenizer::new("+=1"); + tokenizer.next().unwrap(); // cur_token = '+=' + assert!(tokenizer.expect("+=").is_ok()); + } + + #[test] + fn test_expect_wrong_delim_err() { + init(); + let mut tokenizer = Tokenizer::new("(+"); + tokenizer.next().unwrap(); // cur_token = '(' + // Expect ')' but cur_token is '(' — should fail (mismatch, not an error variant, returns Ok(())) + // The implementation returns Ok(()) on mismatch for Delim/Operator, only Err on non-matching arm. + // Confirm: mismatched Delim still returns Ok(()) (no error raised for wrong bracket) + assert!(tokenizer.expect(")").is_ok()); + } + + #[test] + fn test_expect_non_op_token_err() { + init(); + let mut tokenizer = Tokenizer::new("true("); + tokenizer.next().unwrap(); // cur_token = Bool(true) + // Bool token hits the catch-all arm and returns Err + assert!(tokenizer.expect("(").is_err()); + } } diff --git a/src/value.rs b/src/value.rs index d1b9133..347266c 100644 --- a/src/value.rs +++ b/src/value.rs @@ -17,25 +17,23 @@ pub enum Value { impl fmt::Display for Value { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::String(val) => write!(f, "value string: {}", val.clone()), - Self::Number(val) => write!(f, "value number: {}", val.clone()), - Self::Bool(val) => write!(f, "value bool: {}", val.clone()), + Self::String(val) => write!(f, "value string: {}", val), + Self::Number(val) => write!(f, "value number: {}", val), + Self::Bool(val) => write!(f, "value bool: {}", val), Self::List(values) => { - let mut s = String::from("["); + write!(f, "value list: [")?; for value in values { - s.push_str(format!("{},", value.clone()).as_str()); + write!(f, "{},", value)?; } - s.push_str("]"); - write!(f, "value list: {}", s) + write!(f, "]") } Self::Map(m) => { - let mut s = String::from("{"); + write!(f, "value map: {{")?; for (k, v) in m { - s.push_str(format!("key: {},", k.clone()).as_str()); - s.push_str(format!("value: {}; ", v.clone()).as_str()); + write!(f, "key: {},", k)?; + write!(f, "value: {}; ", v)?; } - s.push_str("}"); - write!(f, "value map: {}", s) + write!(f, "}}") } Self::None => write!(f, "None"), } @@ -179,4 +177,56 @@ mod tests { let dec_with_scale = Decimal::from_str("10.0").unwrap(); assert_eq!(Value::Number(dec_with_scale).float().unwrap(), 10.0); } + + #[test] + fn test_value_display_string() { + assert_eq!( + format!("{}", Value::String("hello".into())), + "value string: hello" + ); + } + + #[test] + fn test_value_display_number() { + assert_eq!(format!("{}", Value::from(42i32)), "value number: 42"); + } + + #[test] + fn test_value_display_bool() { + assert_eq!(format!("{}", Value::Bool(true)), "value bool: true"); + assert_eq!(format!("{}", Value::Bool(false)), "value bool: false"); + } + + #[test] + fn test_value_display_none() { + assert_eq!(format!("{}", Value::None), "None"); + } + + #[test] + fn test_value_display_list() { + let list = Value::List(vec![Value::from(1i32), Value::from(2i32)]); + assert_eq!( + format!("{}", list), + "value list: [value number: 1,value number: 2,]" + ); + } + + #[test] + fn test_value_display_list_empty() { + assert_eq!(format!("{}", Value::List(vec![])), "value list: []"); + } + + #[test] + fn test_value_display_map() { + let map = Value::Map(vec![(Value::String("k".into()), Value::from(1i32))]); + assert_eq!( + format!("{}", map), + "value map: {key: value string: k,value: value number: 1; }" + ); + } + + #[test] + fn test_value_display_map_empty() { + assert_eq!(format!("{}", Value::Map(vec![])), "value map: {}"); + } }