Skip to content
Snippets Groups Projects
primitives.rs 3.73 KiB
Newer Older
Louis's avatar
Louis committed
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,
Louis's avatar
Louis committed
			token_type: ScriptTokenType::Identifier(value.fragment().to_string()),
Louis's avatar
Louis committed
		},
	))
}

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| {
Louis's avatar
Louis committed
				assert_eq!(
					token.token_type,
					ScriptTokenType::Identifier(expected.to_string())
				)