use crate::lexer::{raw_decimal, raw_false, raw_true, ScriptToken, ScriptTokenType, Span}; use nom::branch::alt; use nom::bytes::complete::tag; use nom::character::complete::{alpha1, alphanumeric1, one_of}; use nom::combinator::{opt, recognize}; use nom::multi::many0_count; use nom::sequence::{pair, separated_pair}; use nom::{error_position, IResult}; use nom_locate::position; pub fn token_ident(input: Span) -> IResult<Span, ScriptToken> { let (input, pos) = position(input)?; let (input, value) = recognize(pair( alt((alpha1, tag("_"))), many0_count(alt((alphanumeric1, tag("_")))), ))(input)?; Ok(( input, ScriptToken { position: pos, token_type: ScriptTokenType::Identifier(value.fragment().to_string()), }, )) } pub fn token_int(input: Span) -> IResult<Span, ScriptToken> { let (input, pos) = position(input)?; let (input, sign) = opt(one_of("+-"))(input)?; let (input, value) = raw_decimal(input)?; format!("{}{}", sign.map(String::from).unwrap_or_default(), value) .parse::<i64>() .map(|value| { ( input, ScriptToken { token_type: ScriptTokenType::Integer(value), position: pos, }, ) }) .map_err(|_| nom::Err::Failure(error_position!(pos, nom::error::ErrorKind::Digit))) } pub fn token_float(input: Span) -> IResult<Span, ScriptToken> { let (input, pos) = position(input)?; let (input, sign) = opt(one_of("+-"))(input)?; let (input, (before, after)) = separated_pair(opt(raw_decimal), tag("."), opt(raw_decimal))(input)?; let formatted_number = format!( "{}{}.{}", sign.map(String::from).unwrap_or_default(), before .map(|s| s.fragment().to_owned()) .unwrap_or_else(|| String::from("0")), after .map(|s| s.fragment().to_owned()) .unwrap_or_else(|| String::from("0")) ); formatted_number .parse::<f64>() .map(|value| { ( input, ScriptToken { token_type: ScriptTokenType::Float(value), position: pos, }, ) }) .map_err(|_| nom::Err::Failure(error_position!(pos, nom::error::ErrorKind::Digit))) } pub fn token_boolean(input: Span) -> IResult<Span, ScriptToken> { let (input, pos) = position(input)?; let (input, value) = alt((raw_true, raw_false))(input)?; Ok(( input, ScriptToken { position: pos, token_type: ScriptTokenType::Boolean(value), }, )) } #[cfg(test)] mod parsing_tests { use super::*; use crate::map_result; #[test] fn parse_integer() { let positive_cases = [ ("1234", 1234), ("-1234", -1234), ("0", 0), ("1_000_000", 1000000), ("-12_34", -1234), ]; for (program, expected) in positive_cases { map_result!(token_int(Span::new(program)), |_, token: ScriptToken| { assert_eq!(token.token_type, ScriptTokenType::Integer(expected)) }); } } #[test] fn parse_floats() { let positive_cases = [ ("12.34", 12.34), ("-12.34", -12.34), ("0.", 0.), (".0", 0.), (".0.", 0.), (".0.1.2", 0.), ("1_000_000.1_23", 1000000.123), ("-12_34.0_0_0", -1234.0), ]; for (program, expected) in positive_cases { map_result!(token_float(Span::new(program)), |_, token: ScriptToken| { assert_eq!(token.token_type, ScriptTokenType::Float(expected)) }); } } #[test] fn parse_bools() { let positive_cases = [("true", true), ("false", false)]; for (program, expected) in positive_cases { map_result!( token_boolean(Span::new(program)), |_, token: ScriptToken| { assert_eq!(token.token_type, ScriptTokenType::Boolean(expected)) } ); } } #[test] fn parse_identifier() { let positive_cases = ["BarBaz", "Foo", "foo", "foasd123", "_adad"]; for expected in positive_cases { map_result!(token_ident(Span::new(expected)), |_, token: ScriptToken| { assert_eq!( token.token_type, ScriptTokenType::Identifier(expected.to_string()) ) }); } } }