use nom::{
IResult, Parser,
branch::alt,
character::complete::{char, multispace0},
combinator::{peek, value},
sequence::preceded,
};
use crate::ast::UnaryOp;
pub fn unary_op(input: &str) -> IResult<&str, UnaryOp> {
alt((
value(UnaryOp::Minus, char('-')),
value(UnaryOp::Plus, char('+')),
))
.parse(input)
}
#[allow(dead_code)]
pub(crate) fn starts_with_unary(input: &str) -> bool {
starts_with_unary_parser(input).is_ok()
}
fn starts_with_unary_parser(input: &str) -> IResult<&str, char> {
preceded(multispace0, peek(alt((char('-'), char('+'))))).parse(input)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_unary_op_minus() {
let (rest, op) = unary_op("-").unwrap();
assert!(rest.is_empty());
assert_eq!(op, UnaryOp::Minus);
}
#[test]
fn test_unary_op_plus() {
let (rest, op) = unary_op("+").unwrap();
assert!(rest.is_empty());
assert_eq!(op, UnaryOp::Plus);
}
#[test]
fn test_unary_op_with_remaining() {
let (rest, op) = unary_op("-foo").unwrap();
assert_eq!(rest, "foo");
assert_eq!(op, UnaryOp::Minus);
let (rest, op) = unary_op("+123").unwrap();
assert_eq!(rest, "123");
assert_eq!(op, UnaryOp::Plus);
}
#[test]
fn test_unary_op_invalid() {
assert!(unary_op("*").is_err());
assert!(unary_op("/").is_err());
assert!(unary_op("foo").is_err());
assert!(unary_op("").is_err());
}
#[test]
fn test_starts_with_unary() {
assert!(starts_with_unary("-foo"));
assert!(starts_with_unary("+foo"));
assert!(starts_with_unary(" -foo"));
assert!(starts_with_unary(" +foo"));
assert!(!starts_with_unary("foo"));
assert!(!starts_with_unary("*foo"));
assert!(!starts_with_unary(""));
}
#[test]
fn test_unary_op_display() {
assert_eq!(UnaryOp::Minus.as_str(), "-");
assert_eq!(UnaryOp::Plus.as_str(), "+");
assert_eq!(UnaryOp::Minus.to_string(), "-");
assert_eq!(UnaryOp::Plus.to_string(), "+");
}
}