<commit_before>use crate::context::Context;
use crate::tera::Tera;

#[test]
fn can_remove_whitespace_basic() {
    let mut context = Context::new();
    context.insert("numbers", &vec![1, 2, 3]);

    let inputs = vec![
        ("  {%- for n in numbers %}{{n}}{% endfor -%} ", "123"),
        ("{%- for n in numbers %} {{n}}{%- endfor -%} ", " 1 2 3"),
        ("{%- for n in numbers -%}\n {{n}}\n {%- endfor -%} ", "123"),
        ("{%- if true -%}\n {{numbers}}\n {%- endif -%} ", "[1, 2, 3]"),
        ("{%- if false -%}\n {{numbers}}\n {% else %} Nope{%- endif -%} ", " Nope"),
        ("  {%- if false -%}\n {{numbers}}\n {% else -%} Nope {%- endif -%} ", "Nope"),
        ("  {%- if false -%}\n {{numbers}}\n {% elif true -%} Nope {%- endif -%} ", "Nope"),
        ("  {%- if false -%}\n {{numbers}}\n {% elif false -%} Nope {% else %} else {%- endif -%} ", " else"),
        ("  {%- set var = 2 -%} {{var}}", "2"),
        ("  {% set var = 2 -%} {{var}}", "  2"),
        ("  {% raw -%}{{2}} {% endraw -%} ", "  {{2}}"),
        ("  {% filter upper -%} hey {%- endfilter -%} ", "  HEY"),
        ("  {{ \"hello\" -}} ", "  hello"),
        ("  {{- \"hello\" }} ", "hello "),
        ("  {{- \"hello\" -}} ", "hello"),
    ];

    for (input, expected) in inputs {
        let mut tera = Tera::default();
        tera.add_raw_template("tpl", input).unwrap();
        assert_eq!(tera.render("tpl", &context).unwrap(), expected);
    }
}

#[test]
fn can_remove_whitespace_include() {
    let mut context = Context::new();
    context.insert("numbers", &vec![1, 2, 3]);

    let inputs = vec![
        (r#"Hi {%- include "include" -%} "#, "HiIncluded"),
        (r#"Hi {% include "include" -%} "#, "Hi Included"),
        (r#"Hi {% include "include" %} "#, "Hi Included "),
    ];

    for (input, expected) in inputs {
        let mut tera = Tera::default();
        tera.add_raw_templates(vec![("include", "Included"), ("tpl", input)]).unwrap();
        assert_eq!(tera.render("tpl", &context).unwrap(), expected);
    }
}

#[test]
fn can_remove_whitespace_macros() {
    let mut context = Context::new();
    context.insert("numbers", &vec![1, 2, 3]);

    let inputs = vec![
        (r#" {%- import "macros" as macros -%} {{macros::hey()}}"#, "Hey!"),
        (r#" {% import "macros" as macros %} {{macros::hey()}}"#, "Hey!"),
        (r#" {%- import "macros" as macros %} {%- set hey = macros::hey() -%} {{hey}}"#, "Hey!"),
    ];

    for (input, expected) in inputs {
        let mut tera = Tera::default();
        tera.add_raw_templates(vec![
            ("macros", "{% macro hey() -%} Hey! {%- endmacro %}"),
            ("tpl", input),
        ])
        .unwrap();
        assert_eq!(tera.render("tpl", &context).unwrap(), expected);
    }
}

#[test]
fn can_remove_whitespace_inheritance() {
    let mut context = Context::new();
    context.insert("numbers", &vec![1, 2, 3]);

    let inputs = vec![
        (r#"{%- extends "base" -%} {% block content %}{{super()}}{% endblock %}"#, " Hey! "),
        (r#"{%- extends "base" -%} {% block content -%}{{super()}}{%- endblock %}"#, " Hey! "),
        (r#"{%- extends "base" %} {%- block content -%}{{super()}}{%- endblock -%} "#, " Hey! "),
    ];

    for (input, expected) in inputs {
        let mut tera = Tera::default();
        tera.add_raw_templates(vec![
            ("base", "{% block content %} Hey! {% endblock %}"),
            ("tpl", input),
        ])
        .unwrap();
        assert_eq!(tera.render("tpl", &context).unwrap(), expected);
    }
}
<commit_msg>Add renderer ws test for filter section<commit_after>use crate::context::Context;
use crate::tera::Tera;

#[test]
fn can_remove_whitespace_basic() {
    let mut context = Context::new();
    context.insert("numbers", &vec![1, 2, 3]);

    let inputs = vec![
        ("  {%- for n in numbers %}{{n}}{% endfor -%} ", "123"),
        ("{%- for n in numbers %} {{n}}{%- endfor -%} ", " 1 2 3"),
        ("{%- for n in numbers -%}\n {{n}}\n {%- endfor -%} ", "123"),
        ("{%- if true -%}\n {{numbers}}\n {%- endif -%} ", "[1, 2, 3]"),
        ("{%- if false -%}\n {{numbers}}\n {% else %} Nope{%- endif -%} ", " Nope"),
        ("  {%- if false -%}\n {{numbers}}\n {% else -%} Nope {%- endif -%} ", "Nope"),
        ("  {%- if false -%}\n {{numbers}}\n {% elif true -%} Nope {%- endif -%} ", "Nope"),
        ("  {%- if false -%}\n {{numbers}}\n {% elif false -%} Nope {% else %} else {%- endif -%} ", " else"),
        ("  {%- set var = 2 -%} {{var}}", "2"),
        ("  {% set var = 2 -%} {{var}}", "  2"),
        ("  {% raw -%}{{2}} {% endraw -%} ", "  {{2}}"),
        ("  {% filter upper -%} hey {%- endfilter -%} ", "  HEY"),
        ("  {{ \"hello\" -}} ", "  hello"),
        ("  {{- \"hello\" }} ", "hello "),
        ("  {{- \"hello\" -}} ", "hello"),
    ];

    for (input, expected) in inputs {
        let mut tera = Tera::default();
        tera.add_raw_template("tpl", input).unwrap();
        assert_eq!(tera.render("tpl", &context).unwrap(), expected);
    }
}

#[test]
fn can_remove_whitespace_include() {
    let mut context = Context::new();
    context.insert("numbers", &vec![1, 2, 3]);

    let inputs = vec![
        (r#"Hi {%- include "include" -%} "#, "HiIncluded"),
        (r#"Hi {% include "include" -%} "#, "Hi Included"),
        (r#"Hi {% include "include" %} "#, "Hi Included "),
    ];

    for (input, expected) in inputs {
        let mut tera = Tera::default();
        tera.add_raw_templates(vec![("include", "Included"), ("tpl", input)]).unwrap();
        assert_eq!(tera.render("tpl", &context).unwrap(), expected);
    }
}

#[test]
fn can_remove_whitespace_macros() {
    let mut context = Context::new();
    context.insert("numbers", &vec![1, 2, 3]);

    let inputs = vec![
        (r#" {%- import "macros" as macros -%} {{macros::hey()}}"#, "Hey!"),
        (r#" {% import "macros" as macros %} {{macros::hey()}}"#, "Hey!"),
        (r#" {%- import "macros" as macros %} {%- set hey = macros::hey() -%} {{hey}}"#, "Hey!"),
    ];

    for (input, expected) in inputs {
        let mut tera = Tera::default();
        tera.add_raw_templates(vec![
            ("macros", "{% macro hey() -%} Hey! {%- endmacro %}"),
            ("tpl", input),
        ])
        .unwrap();
        assert_eq!(tera.render("tpl", &context).unwrap(), expected);
    }
}

#[test]
fn can_remove_whitespace_inheritance() {
    let mut context = Context::new();
    context.insert("numbers", &vec![1, 2, 3]);

    let inputs = vec![
        (r#"{%- extends "base" -%} {% block content %}{{super()}}{% endblock %}"#, " Hey! "),
        (r#"{%- extends "base" -%} {% block content -%}{{super()}}{%- endblock %}"#, " Hey! "),
        (r#"{%- extends "base" %} {%- block content -%}{{super()}}{%- endblock -%} "#, " Hey! "),
    ];

    for (input, expected) in inputs {
        let mut tera = Tera::default();
        tera.add_raw_templates(vec![
            ("base", "{% block content %} Hey! {% endblock %}"),
            ("tpl", input),
        ])
        .unwrap();
        assert_eq!(tera.render("tpl", &context).unwrap(), expected);
    }
}

// https://github.com/Keats/tera/issues/475
#[test]
fn works_with_filter_section() {
    let mut context = Context::new();
    context.insert("d", "d");
    let input = r#"{% filter upper %}  {{ "c" }}   d{% endfilter %}"#;
    let res = Tera::one_off(input, &context, true).unwrap();
    assert_eq!(res, "  C   D");
}<|endoftext|>


<commit_before>// GPU Command Assembly Parser

use std::string::String;
use super::parsetools::ParseTools;

#[derive(Debug)]
pub enum ParseError { SyntaxError, DelimiterNotEnclosed }
pub type ParseResult<T> = Result<T, ParseError>;
#[derive(Debug, PartialEq)]
pub enum ExpressionNode<'a>
{
	Number(u64), Floating(f64), ConstantRef(&'a [char]),
	Negated(Box<ExpressionNode<'a>>),
	Add(Box<ExpressionNode<'a>>, Box<ExpressionNode<'a>>),
	Sub(Box<ExpressionNode<'a>>, Box<ExpressionNode<'a>>),
	Mul(Box<ExpressionNode<'a>>, Box<ExpressionNode<'a>>),
	Div(Box<ExpressionNode<'a>>, Box<ExpressionNode<'a>>),
	Mod(Box<ExpressionNode<'a>>, Box<ExpressionNode<'a>>),
	And(Box<ExpressionNode<'a>>, Box<ExpressionNode<'a>>),
	Or(Box<ExpressionNode<'a>>, Box<ExpressionNode<'a>>),
	Xor(Box<ExpressionNode<'a>>, Box<ExpressionNode<'a>>)
}

fn is_space(chr: char) -> bool { chr == ' ' || chr == '\t' }
fn split_of_ident(ch: char) -> bool
{
	is_space(ch) || ch == '\n' || ch == '#' || ch == ',' || ch == '.'
}
pub fn parse_define(line: &[char]) -> ParseResult<(String, String)>
{
	if line[..7] != ['.', 'd', 'e', 'f', 'i', 'n', 'e'] { Err(ParseError::SyntaxError) }
	else if line[7] != ' ' && line[7] != '\t' { Err(ParseError::SyntaxError) }
	else
	{
		let (name, rest) = (&line[7..]).skip_while(is_space).take_until(is_space);
		let value = rest.skip_while(is_space);
		Ok((name.clone_as_string(), value.clone_as_string()))
	}
}
pub fn parse_primary_terms(input: &[char]) -> (ParseResult<ExpressionNode>, &[char])
{
	if input.is_front_of('(')
	{
		// Nested Expression
		let (inner, rest) = parse_expression(input.drop(1).skip_while(is_space));
		match inner
		{
			Err(_) => (inner, rest),
			Ok(_) =>
			{
				let rest = rest.skip_while(is_space);
				if rest.is_front_of(')') { (inner, &rest[1..]) }
				else { (Err(ParseError::DelimiterNotEnclosed), rest) }
			}
		}
	}
	else if input.is_front_of('-')
	{
		// Negated
		let (inner, rest) = parse_primary_terms(input.drop(1).skip_while(is_space));
		(inner.map(|ner| ExpressionNode::Negated(Box::new(ner))), rest)
	}
	else if input.is_front(|&c| '0' <= c && c <= '9')
	{
		// Numeric
		let mut num_ipart = input[0] as u64 - '0' as u64;
		let mut rest = &input[1..];
		while rest.is_front(|&c| '0' <= c && c <= '9')
		{
			num_ipart = num_ipart * 10 + (rest[0] as u64 - '0' as u64);
			rest = &rest[1..];
		}
		(if rest.is_front_of('.')
		{
			// fp
			let mut num_fpart = 0.0f64;
			let mut divs_fpart = 10.0f64;
			rest = &rest[1..];
			while rest.is_front(|&c| '0' <= c && c <= '9')
			{
				num_fpart += (rest[0] as u64 - '0' as u64) as f64 / divs_fpart;
				divs_fpart *= 10.0;
				rest = &rest[1..];
			}
			Ok(ExpressionNode::Floating(num_ipart as f64 + num_fpart))
		}
		else { Ok(ExpressionNode::Number(num_ipart)) }, rest)
	}
	else
	{
		// ConstantRef
		let (cref_name, rest) = input.take_until(split_of_ident);
		(if cref_name.is_empty() { Err(ParseError::SyntaxError) }
		else { Ok(ExpressionNode::ConstantRef(cref_name)) }, rest)
	}
}
macro_rules!CombinateBinaryExpressionParser
{
	($name: ident = $parent_term: path { $($op: expr: $node_variant: path),* }) =>
	{
		pub fn $name(input: &[char]) -> (ParseResult<ExpressionNode>, &[char])
		{
			let (lhs, rest) = $parent_term(input);
			match lhs
			{
				Err(_) => (lhs, rest),
				Ok(e) =>
				{
					fn recursive<'a>(current_expr: ExpressionNode<'a>, rest: &'a [char])
						-> (ParseResult<ExpressionNode<'a>>, &'a [char])
					{
						let rest = rest.skip_while(is_space);
						$(
							if rest.is_front_of($op)
							{
								let rest = rest.drop(1).skip_while(is_space);
								let (rhs, rest_r) = $parent_term(rest);
								match rhs
								{
									Err(_) => (rhs, rest_r),
									Ok(er) => recursive($node_variant(Box::new(current_expr), Box::new(er)), rest_r)
								}
							}
						)else*
						else { (Ok(current_expr), rest) }
					}
					recursive(e, rest)
				}
			}
		}
	}
}
CombinateBinaryExpressionParser!(parse_muldiv_expr = parse_primary_terms
{
	'*': ExpressionNode::Mul,
	'/': ExpressionNode::Div,
	'%': ExpressionNode::Mod
})
pub fn parse_addsub_expr(input: &[char]) -> (ParseResult<ExpressionNode>, &[char])
{
	let (lhs, rest) = parse_muldiv_expr(input);
	match lhs
	{
		Err(_) => (lhs, rest),
		Ok(e) =>
		{
			fn recursive<'a>(current_expr: ExpressionNode<'a>, rest: &'a [char])
				-> (ParseResult<ExpressionNode<'a>>, &'a [char])
			{
				let rest = rest.skip_while(is_space);
				if rest.is_front_of('+')
				{
					let rest = rest.drop(1).skip_while(is_space);
					let (rhs, rest_r) = parse_muldiv_expr(rest);
					match rhs
					{
						Err(_) => (rhs, rest_r),
						Ok(er) => recursive(ExpressionNode::Add(Box::new(current_expr), Box::new(er)), rest_r)
					}
				}
				else if rest.is_front_of('-')
				{
					let rest = rest.drop(1).skip_while(is_space);
					let (rhs, rest_r) = parse_muldiv_expr(rest);
					match rhs
					{
						Err(_) => (rhs, rest_r),
						Ok(er) => recursive(ExpressionNode::Sub(Box::new(current_expr), Box::new(er)), rest_r)
					}
				}
				else { (Ok(current_expr), rest) }
			}
			recursive(e, rest)
		}
	}
}
pub fn parse_bit_expr(input: &[char]) -> (ParseResult<ExpressionNode>, &[char])
{
	let (lhs, rest) = parse_addsub_expr(input);
	match lhs
	{
		Err(_) => (lhs, rest),
		Ok(e) =>
		{
			fn recursive<'a>(current_expr: ExpressionNode<'a>, rest: &'a [char])
				-> (ParseResult<ExpressionNode<'a>>, &'a [char])
			{
				let rest = rest.skip_while(is_space);
				if rest.is_front_of('&')
				{
					let rest = rest.drop(1).skip_while(is_space);
					let (rhs, rest_r) = parse_addsub_expr(rest);
					match rhs
					{
						Err(_) => (rhs, rest_r),
						Ok(er) => recursive(ExpressionNode::And(Box::new(current_expr), Box::new(er)), rest_r)
					}
				}
				else if rest.is_front_of('|')
				{
					let rest = rest.drop(1).skip_while(is_space);
					let (rhs, rest_r) = parse_addsub_expr(rest);
					match rhs
					{
						Err(_) => (rhs, rest_r),
						Ok(er) => recursive(ExpressionNode::Or(Box::new(current_expr), Box::new(er)), rest_r)
					}
				}
				else if rest.is_front_of('^')
				{
					let rest = rest.drop(1).skip_while(is_space);
					let (rhs, rest_r) = parse_addsub_expr(rest);
					match rhs
					{
						Err(_) => (rhs, rest_r),
						Ok(er) => recursive(ExpressionNode::Xor(Box::new(current_expr), Box::new(er)), rest_r)
					}
				}
				else { (Ok(current_expr), rest) }
			}
			recursive(e, rest)
		}
	}
}
pub fn parse_expression(input: &[char]) -> (ParseResult<ExpressionNode>, &[char])
{
	parse_bit_expr(input)
}

#[cfg(test)]
mod test
{
	use itertools::Itertools;

	#[test] fn parse_define()
	{
		let testcase = ".define DEFAULT_BITS	2";
		assert_eq!(super::parse_define(&testcase.chars().collect_vec()).unwrap(), ("DEFAULT_BITS".to_owned(), "2".to_owned()));
	}
	#[test] fn parse_primary_terms()
	{
		let testcase = "PS_RENDER_BACKGROUND,";
		let testcase_collect = testcase.chars().collect_vec();
		let res = super::parse_primary_terms(&testcase_collect);
		assert_eq!(res.0.unwrap(), super::ExpressionNode::ConstantRef(&testcase_collect[..20]));
		assert_eq!(res.1, &testcase_collect[20..]);
		let testcase = "2.0";
		let testcase_collect = testcase.chars().collect_vec();
		let res = super::parse_primary_terms(&testcase_collect);
		assert_eq!(res.0.unwrap(), super::ExpressionNode::Floating(2.0));
		let testcase = "-6";
		let testcase_collect = testcase.chars().collect_vec();
		let res = super::parse_primary_terms(&testcase_collect);
		assert_eq!(res.0.unwrap(), super::ExpressionNode::Negated(Box::new(super::ExpressionNode::Number(6))));
	}
	#[test] fn parse_expression()
	{
		let testcase = "2 + 3";
		let testcase_collect = testcase.chars().collect_vec();
		let res = super::parse_expression(&testcase_collect);
		assert_eq!(res.0.unwrap(), super::ExpressionNode::Add(Box::new(super::ExpressionNode::Number(2)), Box::new(super::ExpressionNode::Number(3))));
		let testcase = "TOP | TRANSFER + 2, ";
		let testcase_collect = testcase.chars().collect_vec();
		let res = super::parse_expression(&testcase_collect);
		assert_eq!(res.0.unwrap(), super::ExpressionNode::Or(
			Box::new(super::ExpressionNode::ConstantRef(&testcase_collect[..3])),
			Box::new(super::ExpressionNode::Add(
				Box::new(super::ExpressionNode::ConstantRef(&testcase_collect[6..14])),
				Box::new(super::ExpressionNode::Number(2))
			))
		));
		assert_eq!(res.1, &testcase_collect[18..]);
	}
}
<commit_msg>improved binary expression parser with macros<commit_after>// GPU Command Assembly Parser

use std::string::String;
use super::parsetools::ParseTools;

#[derive(Debug)]
pub enum ParseError { SyntaxError, DelimiterNotEnclosed }
pub type ParseResult<T> = Result<T, ParseError>;
#[derive(Debug, PartialEq)]
pub enum ExpressionNode<'a>
{
	Number(u64), Floating(f64), ConstantRef(&'a [char]),
	Negated(Box<ExpressionNode<'a>>),
	Add(Box<ExpressionNode<'a>>, Box<ExpressionNode<'a>>),
	Sub(Box<ExpressionNode<'a>>, Box<ExpressionNode<'a>>),
	Mul(Box<ExpressionNode<'a>>, Box<ExpressionNode<'a>>),
	Div(Box<ExpressionNode<'a>>, Box<ExpressionNode<'a>>),
	Mod(Box<ExpressionNode<'a>>, Box<ExpressionNode<'a>>),
	And(Box<ExpressionNode<'a>>, Box<ExpressionNode<'a>>),
	Or(Box<ExpressionNode<'a>>, Box<ExpressionNode<'a>>),
	Xor(Box<ExpressionNode<'a>>, Box<ExpressionNode<'a>>)
}

fn is_space(chr: char) -> bool { chr == ' ' || chr == '\t' }
fn split_of_ident(ch: char) -> bool
{
	is_space(ch) || ch == '\n' || ch == '#' || ch == ',' || ch == '.'
}
pub fn parse_define(line: &[char]) -> ParseResult<(String, String)>
{
	if line[..7] != ['.', 'd', 'e', 'f', 'i', 'n', 'e'] { Err(ParseError::SyntaxError) }
	else if line[7] != ' ' && line[7] != '\t' { Err(ParseError::SyntaxError) }
	else
	{
		let (name, rest) = (&line[7..]).skip_while(is_space).take_until(is_space);
		let value = rest.skip_while(is_space);
		Ok((name.clone_as_string(), value.clone_as_string()))
	}
}
pub fn parse_primary_terms(input: &[char]) -> (ParseResult<ExpressionNode>, &[char])
{
	if input.is_front_of('(')
	{
		// Nested Expression
		let (inner, rest) = parse_expression(input.drop(1).skip_while(is_space));
		match inner
		{
			Err(_) => (inner, rest),
			Ok(_) =>
			{
				let rest = rest.skip_while(is_space);
				if rest.is_front_of(')') { (inner, &rest[1..]) }
				else { (Err(ParseError::DelimiterNotEnclosed), rest) }
			}
		}
	}
	else if input.is_front_of('-')
	{
		// Negated
		let (inner, rest) = parse_primary_terms(input.drop(1).skip_while(is_space));
		(inner.map(|ner| ExpressionNode::Negated(Box::new(ner))), rest)
	}
	else if input.is_front(|&c| '0' <= c && c <= '9')
	{
		// Numeric
		let mut num_ipart = input[0] as u64 - '0' as u64;
		let mut rest = &input[1..];
		while rest.is_front(|&c| '0' <= c && c <= '9')
		{
			num_ipart = num_ipart * 10 + (rest[0] as u64 - '0' as u64);
			rest = &rest[1..];
		}
		(if rest.is_front_of('.')
		{
			// fp
			let mut num_fpart = 0.0f64;
			let mut divs_fpart = 10.0f64;
			rest = &rest[1..];
			while rest.is_front(|&c| '0' <= c && c <= '9')
			{
				num_fpart += (rest[0] as u64 - '0' as u64) as f64 / divs_fpart;
				divs_fpart *= 10.0;
				rest = &rest[1..];
			}
			Ok(ExpressionNode::Floating(num_ipart as f64 + num_fpart))
		}
		else { Ok(ExpressionNode::Number(num_ipart)) }, rest)
	}
	else
	{
		// ConstantRef
		let (cref_name, rest) = input.take_until(split_of_ident);
		(if cref_name.is_empty() { Err(ParseError::SyntaxError) }
		else { Ok(ExpressionNode::ConstantRef(cref_name)) }, rest)
	}
}
macro_rules!CombinateBinaryExpressionParser
{
	($name: ident = $parent_term: path { $($op: expr => $node_variant: path),* }) =>
	{
		pub fn $name(input: &[char]) -> (ParseResult<ExpressionNode>, &[char])
		{
			let (lhs, rest) = $parent_term(input);
			match lhs
			{
				Err(_) => (lhs, rest),
				Ok(e) =>
				{
					fn recursive<'a>(current_expr: ExpressionNode<'a>, rest: &'a [char])
						-> (ParseResult<ExpressionNode<'a>>, &'a [char])
					{
						let rest = rest.skip_while(is_space);
						$(
							if rest.is_front_of($op)
							{
								let rest = rest.drop(1).skip_while(is_space);
								let (rhs, rest_r) = $parent_term(rest);
								match rhs
								{
									Err(_) => (rhs, rest_r),
									Ok(er) => recursive($node_variant(Box::new(current_expr), Box::new(er)), rest_r)
								}
							}
						)else*
						else { (Ok(current_expr), rest) }
					}
					recursive(e, rest)
				}
			}
		}
	}
}
CombinateBinaryExpressionParser!(parse_muldiv_expr = parse_primary_terms
{
	'*' => ExpressionNode::Mul, '/' => ExpressionNode::Div, '%' => ExpressionNode::Mod
});
CombinateBinaryExpressionParser!(parse_addsub_expr = parse_muldiv_expr
{
	'+' => ExpressionNode::Add, '-' => ExpressionNode::Sub
});
CombinateBinaryExpressionParser!(parse_bit_expr = parse_addsub_expr
{
	'&' => ExpressionNode::And, '|' => ExpressionNode::Or, '^' => ExpressionNode::Xor
});
pub fn parse_expression(input: &[char]) -> (ParseResult<ExpressionNode>, &[char])
{
	parse_bit_expr(input)
}

#[cfg(test)]
mod test
{
	use itertools::Itertools;

	#[test] fn parse_define()
	{
		let testcase = ".define DEFAULT_BITS	2";
		assert_eq!(super::parse_define(&testcase.chars().collect_vec()).unwrap(), ("DEFAULT_BITS".to_owned(), "2".to_owned()));
	}
	#[test] fn parse_primary_terms()
	{
		let testcase = "PS_RENDER_BACKGROUND,";
		let testcase_collect = testcase.chars().collect_vec();
		let res = super::parse_primary_terms(&testcase_collect);
		assert_eq!(res.0.unwrap(), super::ExpressionNode::ConstantRef(&testcase_collect[..20]));
		assert_eq!(res.1, &testcase_collect[20..]);
		let testcase = "2.0";
		let testcase_collect = testcase.chars().collect_vec();
		let res = super::parse_primary_terms(&testcase_collect);
		assert_eq!(res.0.unwrap(), super::ExpressionNode::Floating(2.0));
		let testcase = "-6";
		let testcase_collect = testcase.chars().collect_vec();
		let res = super::parse_primary_terms(&testcase_collect);
		assert_eq!(res.0.unwrap(), super::ExpressionNode::Negated(Box::new(super::ExpressionNode::Number(6))));
	}
	#[test] fn parse_expression()
	{
		let testcase = "2 + 3";
		let testcase_collect = testcase.chars().collect_vec();
		let res = super::parse_expression(&testcase_collect);
		assert_eq!(res.0.unwrap(), super::ExpressionNode::Add(Box::new(super::ExpressionNode::Number(2)), Box::new(super::ExpressionNode::Number(3))));
		let testcase = "TOP | TRANSFER + 2, ";
		let testcase_collect = testcase.chars().collect_vec();
		let res = super::parse_expression(&testcase_collect);
		assert_eq!(res.0.unwrap(), super::ExpressionNode::Or(
			Box::new(super::ExpressionNode::ConstantRef(&testcase_collect[..3])),
			Box::new(super::ExpressionNode::Add(
				Box::new(super::ExpressionNode::ConstantRef(&testcase_collect[6..14])),
				Box::new(super::ExpressionNode::Number(2))
			))
		));
		assert_eq!(res.1, &testcase_collect[18..]);
	}
}
<|endoftext|>


<commit_before>// Copyright (c) 2018 Chef Software Inc. and/or applicable contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// NOTE: All this code is basically copied verbatim from its previous home in
// the Launcher module. Once all the service-related functionality that we're
// going to move over to the Supervisor has been moved, we can take a look at
// perhaps refactoring some of this a bit.

use crate::sys::ShutdownMethod;
use habitat_core::os::process::{is_alive, signal, Pid, Signal};
use libc::{self, pid_t};
use std::ops::Neg;
use time::{Duration, SteadyTime};

/// Kill a service process.
pub fn kill(pid: Pid) -> ShutdownMethod {
    let process = Process::new(pid as u32);
    process.kill()
}

///////////////////////////////////////////////////////////////////////
// Private Code

// TODO (CM): We may not want this struct in the end... keeping it for
// now to keep some parity with the Windows implementation. Once we
// pull over all the "service" functionality from the Launcher, we can
// re-evaluate.
struct Process {
    pid: pid_t,
}

impl Process {
    fn new(pid: u32) -> Self {
        Process { pid: pid as pid_t }
    }

    /// Attempt to gracefully terminate a proccess and then forcefully
    /// kill it after 8 seconds if it has not terminated.
    fn kill(&self) -> ShutdownMethod {
        let mut pid_to_kill = self.pid;
        // check the group of the process being killed
        // if it is the root process of the process group
        // we send our signals to the entire process group
        // to prevent orphaned processes.
        let pgid = unsafe { libc::getpgid(self.pid) };
        if self.pid == pgid {
            debug!(
                "pid to kill {} is the process group root. Sending signal to process group.",
                self.pid
            );
            // sending a signal to the negative pid sends it to the
            // entire process group instead just the single pid
            pid_to_kill = self.pid.neg();
        }

        // JW TODO: Determine if the error represents a case where the
        // process was already exited before we return out and assume
        // so.
        if signal(pid_to_kill, Signal::TERM).is_err() {
            return ShutdownMethod::AlreadyExited;
        }

        let stop_time = SteadyTime::now() + Duration::seconds(8);
        loop {
            if !is_alive(pid_to_kill) {
                return ShutdownMethod::GracefulTermination;
            }
            if SteadyTime::now() >= stop_time {
                break;
            }
        }

        match signal(pid_to_kill, Signal::KILL) {
            Ok(_) => ShutdownMethod::Killed,
            Err(_) => {
                // JW TODO: Determine if the error represents a case
                // where the process was already exited before we
                // return out and assume so.
                ShutdownMethod::GracefulTermination
            }
        }
    }
}
<commit_msg>[PR Feedback] Remove unnecessary PID casting<commit_after>// Copyright (c) 2018 Chef Software Inc. and/or applicable contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// NOTE: All this code is basically copied verbatim from its previous home in
// the Launcher module. Once all the service-related functionality that we're
// going to move over to the Supervisor has been moved, we can take a look at
// perhaps refactoring some of this a bit.

use crate::sys::ShutdownMethod;
use habitat_core::os::process::{is_alive, signal, Pid, Signal};
use libc::{self, pid_t};
use std::ops::Neg;
use time::{Duration, SteadyTime};

/// Kill a service process.
pub fn kill(pid: Pid) -> ShutdownMethod {
    let process = Process::new(pid);
    process.kill()
}

///////////////////////////////////////////////////////////////////////
// Private Code

// TODO (CM): We may not want this struct in the end... keeping it for
// now to keep some parity with the Windows implementation. Once we
// pull over all the "service" functionality from the Launcher, we can
// re-evaluate.
struct Process {
    pid: pid_t,
}

impl Process {
    fn new(pid: Pid) -> Self {
        Process { pid: pid }
    }

    /// Attempt to gracefully terminate a proccess and then forcefully
    /// kill it after 8 seconds if it has not terminated.
    fn kill(&self) -> ShutdownMethod {
        let mut pid_to_kill = self.pid;
        // check the group of the process being killed
        // if it is the root process of the process group
        // we send our signals to the entire process group
        // to prevent orphaned processes.
        let pgid = unsafe { libc::getpgid(self.pid) };
        if self.pid == pgid {
            debug!(
                "pid to kill {} is the process group root. Sending signal to process group.",
                self.pid
            );
            // sending a signal to the negative pid sends it to the
            // entire process group instead just the single pid
            pid_to_kill = self.pid.neg();
        }

        // JW TODO: Determine if the error represents a case where the
        // process was already exited before we return out and assume
        // so.
        if signal(pid_to_kill, Signal::TERM).is_err() {
            return ShutdownMethod::AlreadyExited;
        }

        let stop_time = SteadyTime::now() + Duration::seconds(8);
        loop {
            if !is_alive(pid_to_kill) {
                return ShutdownMethod::GracefulTermination;
            }
            if SteadyTime::now() >= stop_time {
                break;
            }
        }

        match signal(pid_to_kill, Signal::KILL) {
            Ok(_) => ShutdownMethod::Killed,
            Err(_) => {
                // JW TODO: Determine if the error represents a case
                // where the process was already exited before we
                // return out and assume so.
                ShutdownMethod::GracefulTermination
            }
        }
    }
}
<|endoftext|>


<commit_before>//! Error types.

use std::error::{self, Error};
use std::fmt;

use cairo;
use cssparser::{self, BasicParseError, BasicParseErrorKind, ParseErrorKind, ToCss};
use glib;
use markup5ever::QualName;

use crate::allowed_url::Fragment;
use crate::node::RsvgNode;

/// A short-lived error.
///
/// The lifetime of the error is the same as the `cssparser::ParserInput` that
/// was used to create a `cssparser::Parser`.  That is, it is the lifetime of
/// the string data that is being parsed.
///
/// The code flow will sometimes require preserving this error as a long-lived struct;
/// see the `impl<'i, O> AttributeResultExt<O> for Result<O, ParseError<'i>>` for that
/// purpose.
pub type CssParseError<'i> = cssparser::ParseError<'i, ValueErrorKind>;

/// A simple error which refers to an attribute's value
#[derive(Debug, Clone, PartialEq)]
pub enum ValueErrorKind {
    /// A property with the specified name was not found
    UnknownProperty,

    /// The value could not be parsed
    Parse(String),

    // The value could be parsed, but is invalid
    Value(String),
}

impl ValueErrorKind {
    pub fn parse_error(s: &str) -> ValueErrorKind {
        ValueErrorKind::Parse(s.to_string())
    }

    pub fn value_error(s: &str) -> ValueErrorKind {
        ValueErrorKind::Value(s.to_string())
    }
}

impl fmt::Display for ValueErrorKind {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match *self {
            ValueErrorKind::UnknownProperty => write!(f, "unknown property name"),

            ValueErrorKind::Parse(ref s) => write!(f, "parse error: {}", s),

            ValueErrorKind::Value(ref s) => write!(f, "invalid value: {}", s),
        }
    }
}

/// A complete error for an attribute and its erroneous value
#[derive(Debug, Clone, PartialEq)]
pub struct NodeError {
    pub attr: QualName,
    pub err: ValueErrorKind,
}

impl error::Error for NodeError {
    fn description(&self) -> &str {
        match self.err {
            ValueErrorKind::UnknownProperty => "unknown property",
            ValueErrorKind::Parse(_) => "parse error",
            ValueErrorKind::Value(_) => "invalid attribute value",
        }
    }
}

impl fmt::Display for NodeError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{:?}: {}", self.attr.expanded(), self.err)
    }
}

impl<'a> From<BasicParseError<'a>> for ValueErrorKind {
    fn from(e: BasicParseError<'_>) -> ValueErrorKind {
        let BasicParseError { kind, location: _ } = e;

        let msg = match kind {
            BasicParseErrorKind::UnexpectedToken(_) => "unexpected token",
            BasicParseErrorKind::EndOfInput => "unexpected end of input",
            BasicParseErrorKind::AtRuleInvalid(_) => "invalid @-rule",
            BasicParseErrorKind::AtRuleBodyInvalid => "invalid @-rule body",
            BasicParseErrorKind::QualifiedRuleInvalid => "invalid qualified rule",
        };

        ValueErrorKind::parse_error(msg)
    }
}

#[derive(Debug, Clone, PartialEq)]
pub enum DefsLookupErrorKind {
    HrefError(HrefError),
    CannotLookupExternalReferences,
    NotFound,
}

#[derive(Debug, Clone, PartialEq)]
pub enum RenderingError {
    Cairo(cairo::Status),
    CircularReference,
    InstancingLimit,
    InvalidId(DefsLookupErrorKind),
    InvalidHref,
    OutOfMemory,
    HandleIsNotLoaded,
}

impl From<cairo::Status> for RenderingError {
    fn from(e: cairo::Status) -> RenderingError {
        assert!(e != cairo::Status::Success);

        RenderingError::Cairo(e)
    }
}

pub enum AcquireError {
    LinkNotFound(Fragment),
    InvalidLinkType(Fragment),
    CircularReference(RsvgNode),
    MaxReferencesExceeded,
}

impl fmt::Display for AcquireError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match *self {
            AcquireError::LinkNotFound(ref frag) => write!(f, "link not found: {}", frag),

            AcquireError::InvalidLinkType(ref frag) => {
                write!(f, "link {} is to object of invalid type", frag)
            }

            AcquireError::CircularReference(ref node) => {
                write!(f, "circular reference in node {}", node)
            }

            AcquireError::MaxReferencesExceeded => {
                write!(f, "maximum number of references exceeded")
            }
        }
    }
}

/// Helper for converting `Result<O, E>` into `Result<O, NodeError>`
///
/// A `NodeError` requires a `QualName` that corresponds to the attribute to which the
/// error refers, plus the actual `ValueErrorKind` that describes the error.  However,
/// parsing functions for attribute value types will want to return their own kind of
/// error, instead of `ValueErrorKind`.  If that particular error type has an `impl
/// From<FooError> for ValueErrorKind`, then this trait helps assign attribute values in
/// `set_atts()` methods as follows:
///
/// ```ignore
/// use error::AttributeResultExt;
///
/// // fn parse_foo(...) -> Result<Foo, FooError>
///
/// // It is assumed that there is an impl From<FooError> for ValueErrorKind
///
/// self.foo = parse_foo(value).attribute(local_name!("foo"))?;
/// ```
///
/// The call to `.attribute(attr)` converts the `Result` from `parse_foo()` into a full
/// `NodeError` with the provided `attr`.
pub trait AttributeResultExt<O> {
    fn attribute(self, attr: QualName) -> Result<O, NodeError>;
}

impl<O, E: Into<ValueErrorKind>> AttributeResultExt<O> for Result<O, E> {
    fn attribute(self, attr: QualName) -> Result<O, NodeError> {
        self.map_err(|e| e.into())
            .map_err(|err| NodeError { attr, err })
    }
}

/// Turns a short-lived `ParseError` into a long-lived `NodeError`
impl<'i, O> AttributeResultExt<O> for Result<O, CssParseError<'i>> {
    fn attribute(self, attr: QualName) -> Result<O, NodeError> {
        self.map_err(|e| {
            // FIXME: eventually, here we'll want to preserve the location information

            let CssParseError {
                kind,
                location: _location,
            } = e;

            match kind {
                ParseErrorKind::Basic(BasicParseErrorKind::UnexpectedToken(tok)) => {
                    let mut s = String::from("unexpected token '");
                    tok.to_css(&mut s).unwrap(); // FIXME: what do we do with a fmt::Error?
                    s.push_str("'");

                    NodeError {
                        attr,
                        err: ValueErrorKind::Parse(s),
                    }
                },

                ParseErrorKind::Basic(BasicParseErrorKind::EndOfInput) => NodeError {
                    attr,
                    err: ValueErrorKind::parse_error("unexpected end of input"),
                },

                ParseErrorKind::Basic(_) => unreachable!(
                    "attribute parsers should not return errors for CSS rules"
                ),

                ParseErrorKind::Custom(err) => NodeError { attr, err },
            }
        })
    }
}

/// Errors returned when creating an `Href` out of a string
#[derive(Debug, Clone, PartialEq)]
pub enum HrefError {
    /// The href is an invalid URI or has empty components.
    ParseError,

    /// A fragment identifier ("`#foo`") is not allowed here
    ///
    /// For example, the SVG `<image>` element only allows referencing
    /// resources without fragment identifiers like
    /// `xlink:href="foo.png"`.
    FragmentForbidden,

    /// A fragment identifier ("`#foo`") was required but not found.  For example,
    /// the SVG `<use>` element requires one, as in `<use xlink:href="foo.svg#bar">`.
    FragmentRequired,
}

impl From<HrefError> for ValueErrorKind {
    fn from(e: HrefError) -> ValueErrorKind {
        match e {
            HrefError::ParseError => ValueErrorKind::parse_error("url parse error"),
            HrefError::FragmentForbidden => {
                ValueErrorKind::value_error("fragment identifier not allowed")
            }
            HrefError::FragmentRequired => {
                ValueErrorKind::value_error("fragment identifier required")
            }
        }
    }
}

#[derive(Debug, Clone)]
pub enum LoadingError {
    NoDataPassedToParser,
    XmlParseError(String),
    // Could not parse data: URL
    CouldNotCreateXmlParser,
    BadUrl,
    BadDataUrl,
    BadStylesheet,
    BadCss,
    Cairo(cairo::Status),
    EmptyData,
    SvgHasNoElements,
    RootElementIsNotSvg,
    Glib(glib::Error),
    Unknown,
}

impl error::Error for LoadingError {
    fn description(&self) -> &str {
        match *self {
            LoadingError::NoDataPassedToParser => "no data passed to parser",
            LoadingError::CouldNotCreateXmlParser => "could not create XML parser",
            LoadingError::XmlParseError(_) => "XML parse error",
            LoadingError::BadUrl => "invalid URL",
            LoadingError::BadDataUrl => "invalid data: URL",
            LoadingError::BadStylesheet => "invalid stylesheet",
            LoadingError::BadCss => "invalid CSS",
            LoadingError::Cairo(_) => "cairo error",
            LoadingError::EmptyData => "empty data",
            LoadingError::SvgHasNoElements => "SVG has no elements",
            LoadingError::RootElementIsNotSvg => "root element is not <svg>",
            LoadingError::Glib(ref e) => e.description(),
            LoadingError::Unknown => "unknown error",
        }
    }
}

impl fmt::Display for LoadingError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match *self {
            LoadingError::Cairo(status) => write!(f, "cairo error: {:?}", status),
            LoadingError::XmlParseError(ref s) => write!(f, "XML parse error: {}", s),
            LoadingError::NoDataPassedToParser
            | LoadingError::CouldNotCreateXmlParser
            | LoadingError::BadUrl
            | LoadingError::BadDataUrl
            | LoadingError::BadStylesheet
            | LoadingError::BadCss
            | LoadingError::EmptyData
            | LoadingError::SvgHasNoElements
            | LoadingError::RootElementIsNotSvg
            | LoadingError::Glib(_)
            | LoadingError::Unknown => write!(f, "{}", self.description()),
        }
    }
}

impl error::Error for RenderingError {
    fn description(&self) -> &str {
        match *self {
            RenderingError::Cairo(_) => "cairo error",
            RenderingError::CircularReference => "circular reference",
            RenderingError::InstancingLimit => "instancing limit",
            RenderingError::InvalidId(_) => "invalid id",
            RenderingError::InvalidHref => "invalid href",
            RenderingError::OutOfMemory => "out of memory",
            RenderingError::HandleIsNotLoaded => "SVG data is not loaded into handle",
        }
    }
}

impl fmt::Display for RenderingError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match *self {
            RenderingError::Cairo(ref status) => write!(f, "cairo error: {:?}", status),
            RenderingError::InvalidId(ref id) => write!(f, "invalid id: {:?}", id),
            RenderingError::CircularReference
            | RenderingError::InstancingLimit
            | RenderingError::InvalidHref
            | RenderingError::OutOfMemory
            | RenderingError::HandleIsNotLoaded => write!(f, "{}", self.description()),
        }
    }
}

impl From<cairo::Status> for LoadingError {
    fn from(e: cairo::Status) -> LoadingError {
        assert!(e != cairo::Status::Success);

        LoadingError::Cairo(e)
    }
}

impl From<glib::Error> for LoadingError {
    fn from(e: glib::Error) -> LoadingError {
        LoadingError::Glib(e)
    }
}

#[cfg(test)]
pub fn is_parse_error<T>(r: &Result<T, ValueErrorKind>) -> bool {
    match *r {
        Err(ValueErrorKind::Parse(_)) => true,
        _ => false,
    }
}

#[cfg(test)]
pub fn is_value_error<T>(r: &Result<T, ValueErrorKind>) -> bool {
    match *r {
        Err(ValueErrorKind::Value(_)) => true,
        _ => false,
    }
}
<commit_msg>Introduce a ParseError enum, temporarily with variants for CssParseError and ValueErrorKind<commit_after>//! Error types.

use std::error::{self, Error};
use std::fmt;

use cairo;
use cssparser::{self, BasicParseError, BasicParseErrorKind, ParseErrorKind, ToCss};
use glib;
use markup5ever::QualName;

use crate::allowed_url::Fragment;
use crate::node::RsvgNode;

/// A short-lived error.
///
/// The lifetime of the error is the same as the `cssparser::ParserInput` that
/// was used to create a `cssparser::Parser`.  That is, it is the lifetime of
/// the string data that is being parsed.
///
/// The code flow will sometimes require preserving this error as a long-lived struct;
/// see the `impl<'i, O> AttributeResultExt<O> for Result<O, ParseError<'i>>` for that
/// purpose.
pub type CssParseError<'i> = cssparser::ParseError<'i, ValueErrorKind>;

pub enum ParseError<'i> {
    P(CssParseError<'i>),
    V(ValueErrorKind),
}

impl<'i> From<CssParseError<'i>> for ParseError<'i> {
    fn from(p: CssParseError<'i>) -> ParseError {
        ParseError::P(p)
    }
}

impl<'i> From<ValueErrorKind> for ParseError<'i> {
    fn from (v: ValueErrorKind) -> ParseError<'i> {
        ParseError::V(v)
    }
}

/// A simple error which refers to an attribute's value
#[derive(Debug, Clone, PartialEq)]
pub enum ValueErrorKind {
    /// A property with the specified name was not found
    UnknownProperty,

    /// The value could not be parsed
    Parse(String),

    // The value could be parsed, but is invalid
    Value(String),
}

impl ValueErrorKind {
    pub fn parse_error(s: &str) -> ValueErrorKind {
        ValueErrorKind::Parse(s.to_string())
    }

    pub fn value_error(s: &str) -> ValueErrorKind {
        ValueErrorKind::Value(s.to_string())
    }
}

impl fmt::Display for ValueErrorKind {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match *self {
            ValueErrorKind::UnknownProperty => write!(f, "unknown property name"),

            ValueErrorKind::Parse(ref s) => write!(f, "parse error: {}", s),

            ValueErrorKind::Value(ref s) => write!(f, "invalid value: {}", s),
        }
    }
}

/// A complete error for an attribute and its erroneous value
#[derive(Debug, Clone, PartialEq)]
pub struct NodeError {
    pub attr: QualName,
    pub err: ValueErrorKind,
}

impl error::Error for NodeError {
    fn description(&self) -> &str {
        match self.err {
            ValueErrorKind::UnknownProperty => "unknown property",
            ValueErrorKind::Parse(_) => "parse error",
            ValueErrorKind::Value(_) => "invalid attribute value",
        }
    }
}

impl fmt::Display for NodeError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{:?}: {}", self.attr.expanded(), self.err)
    }
}

impl<'a> From<BasicParseError<'a>> for ValueErrorKind {
    fn from(e: BasicParseError<'_>) -> ValueErrorKind {
        let BasicParseError { kind, location: _ } = e;

        let msg = match kind {
            BasicParseErrorKind::UnexpectedToken(_) => "unexpected token",
            BasicParseErrorKind::EndOfInput => "unexpected end of input",
            BasicParseErrorKind::AtRuleInvalid(_) => "invalid @-rule",
            BasicParseErrorKind::AtRuleBodyInvalid => "invalid @-rule body",
            BasicParseErrorKind::QualifiedRuleInvalid => "invalid qualified rule",
        };

        ValueErrorKind::parse_error(msg)
    }
}

#[derive(Debug, Clone, PartialEq)]
pub enum DefsLookupErrorKind {
    HrefError(HrefError),
    CannotLookupExternalReferences,
    NotFound,
}

#[derive(Debug, Clone, PartialEq)]
pub enum RenderingError {
    Cairo(cairo::Status),
    CircularReference,
    InstancingLimit,
    InvalidId(DefsLookupErrorKind),
    InvalidHref,
    OutOfMemory,
    HandleIsNotLoaded,
}

impl From<cairo::Status> for RenderingError {
    fn from(e: cairo::Status) -> RenderingError {
        assert!(e != cairo::Status::Success);

        RenderingError::Cairo(e)
    }
}

pub enum AcquireError {
    LinkNotFound(Fragment),
    InvalidLinkType(Fragment),
    CircularReference(RsvgNode),
    MaxReferencesExceeded,
}

impl fmt::Display for AcquireError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match *self {
            AcquireError::LinkNotFound(ref frag) => write!(f, "link not found: {}", frag),

            AcquireError::InvalidLinkType(ref frag) => {
                write!(f, "link {} is to object of invalid type", frag)
            }

            AcquireError::CircularReference(ref node) => {
                write!(f, "circular reference in node {}", node)
            }

            AcquireError::MaxReferencesExceeded => {
                write!(f, "maximum number of references exceeded")
            }
        }
    }
}

/// Helper for converting `Result<O, E>` into `Result<O, NodeError>`
///
/// A `NodeError` requires a `QualName` that corresponds to the attribute to which the
/// error refers, plus the actual `ValueErrorKind` that describes the error.  However,
/// parsing functions for attribute value types will want to return their own kind of
/// error, instead of `ValueErrorKind`.  If that particular error type has an `impl
/// From<FooError> for ValueErrorKind`, then this trait helps assign attribute values in
/// `set_atts()` methods as follows:
///
/// ```ignore
/// use error::AttributeResultExt;
///
/// // fn parse_foo(...) -> Result<Foo, FooError>
///
/// // It is assumed that there is an impl From<FooError> for ValueErrorKind
///
/// self.foo = parse_foo(value).attribute(local_name!("foo"))?;
/// ```
///
/// The call to `.attribute(attr)` converts the `Result` from `parse_foo()` into a full
/// `NodeError` with the provided `attr`.
pub trait AttributeResultExt<O> {
    fn attribute(self, attr: QualName) -> Result<O, NodeError>;
}

impl<O, E: Into<ValueErrorKind>> AttributeResultExt<O> for Result<O, E> {
    fn attribute(self, attr: QualName) -> Result<O, NodeError> {
        self.map_err(|e| e.into())
            .map_err(|err| NodeError { attr, err })
    }
}

/// Turns a short-lived `ParseError` into a long-lived `NodeError`
impl<'i, O> AttributeResultExt<O> for Result<O, CssParseError<'i>> {
    fn attribute(self, attr: QualName) -> Result<O, NodeError> {
        self.map_err(|e| {
            // FIXME: eventually, here we'll want to preserve the location information

            let CssParseError {
                kind,
                location: _location,
            } = e;

            match kind {
                ParseErrorKind::Basic(BasicParseErrorKind::UnexpectedToken(tok)) => {
                    let mut s = String::from("unexpected token '");
                    tok.to_css(&mut s).unwrap(); // FIXME: what do we do with a fmt::Error?
                    s.push_str("'");

                    NodeError {
                        attr,
                        err: ValueErrorKind::Parse(s),
                    }
                },

                ParseErrorKind::Basic(BasicParseErrorKind::EndOfInput) => NodeError {
                    attr,
                    err: ValueErrorKind::parse_error("unexpected end of input"),
                },

                ParseErrorKind::Basic(_) => unreachable!(
                    "attribute parsers should not return errors for CSS rules"
                ),

                ParseErrorKind::Custom(err) => NodeError { attr, err },
            }
        })
    }
}

/// Errors returned when creating an `Href` out of a string
#[derive(Debug, Clone, PartialEq)]
pub enum HrefError {
    /// The href is an invalid URI or has empty components.
    ParseError,

    /// A fragment identifier ("`#foo`") is not allowed here
    ///
    /// For example, the SVG `<image>` element only allows referencing
    /// resources without fragment identifiers like
    /// `xlink:href="foo.png"`.
    FragmentForbidden,

    /// A fragment identifier ("`#foo`") was required but not found.  For example,
    /// the SVG `<use>` element requires one, as in `<use xlink:href="foo.svg#bar">`.
    FragmentRequired,
}

impl From<HrefError> for ValueErrorKind {
    fn from(e: HrefError) -> ValueErrorKind {
        match e {
            HrefError::ParseError => ValueErrorKind::parse_error("url parse error"),
            HrefError::FragmentForbidden => {
                ValueErrorKind::value_error("fragment identifier not allowed")
            }
            HrefError::FragmentRequired => {
                ValueErrorKind::value_error("fragment identifier required")
            }
        }
    }
}

#[derive(Debug, Clone)]
pub enum LoadingError {
    NoDataPassedToParser,
    XmlParseError(String),
    // Could not parse data: URL
    CouldNotCreateXmlParser,
    BadUrl,
    BadDataUrl,
    BadStylesheet,
    BadCss,
    Cairo(cairo::Status),
    EmptyData,
    SvgHasNoElements,
    RootElementIsNotSvg,
    Glib(glib::Error),
    Unknown,
}

impl error::Error for LoadingError {
    fn description(&self) -> &str {
        match *self {
            LoadingError::NoDataPassedToParser => "no data passed to parser",
            LoadingError::CouldNotCreateXmlParser => "could not create XML parser",
            LoadingError::XmlParseError(_) => "XML parse error",
            LoadingError::BadUrl => "invalid URL",
            LoadingError::BadDataUrl => "invalid data: URL",
            LoadingError::BadStylesheet => "invalid stylesheet",
            LoadingError::BadCss => "invalid CSS",
            LoadingError::Cairo(_) => "cairo error",
            LoadingError::EmptyData => "empty data",
            LoadingError::SvgHasNoElements => "SVG has no elements",
            LoadingError::RootElementIsNotSvg => "root element is not <svg>",
            LoadingError::Glib(ref e) => e.description(),
            LoadingError::Unknown => "unknown error",
        }
    }
}

impl fmt::Display for LoadingError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match *self {
            LoadingError::Cairo(status) => write!(f, "cairo error: {:?}", status),
            LoadingError::XmlParseError(ref s) => write!(f, "XML parse error: {}", s),
            LoadingError::NoDataPassedToParser
            | LoadingError::CouldNotCreateXmlParser
            | LoadingError::BadUrl
            | LoadingError::BadDataUrl
            | LoadingError::BadStylesheet
            | LoadingError::BadCss
            | LoadingError::EmptyData
            | LoadingError::SvgHasNoElements
            | LoadingError::RootElementIsNotSvg
            | LoadingError::Glib(_)
            | LoadingError::Unknown => write!(f, "{}", self.description()),
        }
    }
}

impl error::Error for RenderingError {
    fn description(&self) -> &str {
        match *self {
            RenderingError::Cairo(_) => "cairo error",
            RenderingError::CircularReference => "circular reference",
            RenderingError::InstancingLimit => "instancing limit",
            RenderingError::InvalidId(_) => "invalid id",
            RenderingError::InvalidHref => "invalid href",
            RenderingError::OutOfMemory => "out of memory",
            RenderingError::HandleIsNotLoaded => "SVG data is not loaded into handle",
        }
    }
}

impl fmt::Display for RenderingError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match *self {
            RenderingError::Cairo(ref status) => write!(f, "cairo error: {:?}", status),
            RenderingError::InvalidId(ref id) => write!(f, "invalid id: {:?}", id),
            RenderingError::CircularReference
            | RenderingError::InstancingLimit
            | RenderingError::InvalidHref
            | RenderingError::OutOfMemory
            | RenderingError::HandleIsNotLoaded => write!(f, "{}", self.description()),
        }
    }
}

impl From<cairo::Status> for LoadingError {
    fn from(e: cairo::Status) -> LoadingError {
        assert!(e != cairo::Status::Success);

        LoadingError::Cairo(e)
    }
}

impl From<glib::Error> for LoadingError {
    fn from(e: glib::Error) -> LoadingError {
        LoadingError::Glib(e)
    }
}

#[cfg(test)]
pub fn is_parse_error<T>(r: &Result<T, ValueErrorKind>) -> bool {
    match *r {
        Err(ValueErrorKind::Parse(_)) => true,
        _ => false,
    }
}

#[cfg(test)]
pub fn is_value_error<T>(r: &Result<T, ValueErrorKind>) -> bool {
    match *r {
        Err(ValueErrorKind::Value(_)) => true,
        _ => false,
    }
}
<|endoftext|>


<commit_before>use std::iter::{Fuse, Iterator};

use core::{BytePos, ByteRange};

pub struct StmtIndicesIter<'a,I>
    where I: Iterator<Item = ByteRange>
{
    src: &'a str,
    it: I,
    pos: BytePos,
    end: BytePos
}

impl<'a,I> Iterator for StmtIndicesIter<'a,I>
    where I: Iterator<Item = ByteRange>
{
    type Item = ByteRange;

    #[inline]
    fn next(&mut self) -> Option<Self::Item> {
        let src_bytes = self.src.as_bytes();
        let mut enddelim = b';';
        let mut bracelevel = 0isize;
        let mut parenlevel = 0isize;
        let mut bracketlevel = 0isize;
        let mut start = self.pos;
        let mut pos = self.pos;

        // loop on all code_chunks until we find a relevant open/close pattern
        loop {
            // do we need the next chunk?
            if self.end == pos {
                // get the next chunk of code
                match self.it.next() {
                    Some(ch_range) => {
                        self.end = ch_range.end;
                        if start == pos {
                            start = ch_range.start;
                        }
                        pos = ch_range.start;
                    }
                    None => {
                        // no more chunks. finished
                        self.pos = pos;
                        if start < self.end {
                            return Some(ByteRange::new(start, self.end));
                        } else {
                            return None;
                        }
                    }
                }
            }

            if start == pos {
                // if this is a new stmt block, skip the whitespace
                for &b in &src_bytes[pos.0..self.end.0] {
                    match b {
                        b' ' | b'\r' | b'\n' | b'\t' => {
                            pos += BytePos(1);
                        },
                        _ => { break; }
                    }
                }
                start = pos;
                // test attribute   #[foo = bar]
                if pos < self.end && src_bytes[pos.0] == b'#' {
                    enddelim = b']'
                };
            }

            // iterate through the chunk, looking for stmt end
            for &b in &src_bytes[pos.0..self.end.0] {
                pos += BytePos(1);
                match b {
                    b'(' => { parenlevel += 1; },
                    b')' => { parenlevel -= 1; },
                    b'[' => { bracketlevel += 1; },
                    b']' => { bracketlevel -= 1; },
                    b'{' => {
                        // if we are top level and stmt is not a 'use' or 'let' then
                        // closebrace finishes the stmt
                        if bracelevel == 0 && parenlevel == 0
                            && !(is_a_use_stmt(src_bytes, start, pos) || is_a_let_stmt(src_bytes, start, pos)) {
                            enddelim = b'}';
                        }
                        bracelevel += 1;
                    },
                    b'}' => {
                        // have we reached the end of the scope?
                        if bracelevel == 0 {
                            self.pos = pos;
                            return None;
                        }
                        bracelevel -= 1;
                    },
                    b'!' => {
                        // macro if followed by at least one space or (
                        // FIXME: test with boolean 'not' expression
                        if parenlevel == 0 && bracelevel == 0
                            && pos < self.end && (pos - start).0 > 1 {
                            match src_bytes[pos.0] {
                                b' ' | b'\r' | b'\n' | b'\t' | b'('  => {
                                    enddelim = b')';
                                },
                                _ => {}
                            }
                        }
                    }
                    _ => {}
                }
                if parenlevel < 0 || bracelevel < 0 || bracketlevel < 0
                    || (enddelim == b && bracelevel == 0 && parenlevel == 0 && bracketlevel == 0)
                {
                    self.pos = pos;
                    return Some(ByteRange::new(start, pos));
                }
            }
        }
    }
}

fn is_a_use_stmt(src_bytes: &[u8], start: BytePos, pos: BytePos) -> bool {
    let whitespace = b" {\t\r\n";
    (pos.0 > 3 && &src_bytes[start.0..start.0 + 3] == b"use" &&
     whitespace.contains(&src_bytes[start.0 + 3])) ||
    (pos.0 > 7 && &src_bytes[start.0..(start.0 + 7)] == b"pub use" &&
     whitespace.contains(&src_bytes[start.0 + 7]))
}

fn is_a_let_stmt(src_bytes: &[u8], start: BytePos, pos: BytePos) -> bool {
    let whitespace = b" {\t\r\n";
    pos.0 > 3 && &src_bytes[start.0..start.0 + 3] == b"let"
        && whitespace.contains(&src_bytes[start.0 + 3])
}

impl<'a, I> StmtIndicesIter<'a,I>
    where I: Iterator<Item = ByteRange>
{
    pub fn from_parts(src: &str, it: I) -> Fuse<StmtIndicesIter<I>> {
        StmtIndicesIter{ src, it, pos: BytePos::ZERO, end: BytePos::ZERO }.fuse()
    }
}

#[cfg(test)]
mod test {
    use std::iter::Fuse;

    use codecleaner;
    use testutils::{rejustify, slice};

    use super::*;

    fn iter_stmts(src: &str) -> Fuse<StmtIndicesIter<codecleaner::CodeIndicesIter>> {
        let it = codecleaner::code_chunks(src);
        StmtIndicesIter{ src: src, it: it, pos: BytePos::ZERO, end: BytePos::ZERO }.fuse()
    }

    #[test]
    fn iterates_single_use_stmts() {
        let src = rejustify("
            use std::Foo; // a comment
            use std::Bar;
        ");

        let mut it = iter_stmts(src.as_ref());
        assert_eq!("use std::Foo;", slice(&src, it.next().unwrap()));
        assert_eq!("use std::Bar;", slice(&src, it.next().unwrap()));
    }

    #[test]
    fn iterates_array_stmts() {
        let src = rejustify("
            let a: [i32; 2] = [1, 2];
            let b = [[0], [1], [2]];
            let c = ([1, 2, 3])[1];
        ");

        let mut it = iter_stmts(src.as_ref());
        assert_eq!("let a: [i32; 2] = [1, 2];", slice(&src, it.next().unwrap()));
        assert_eq!("let b = [[0], [1], [2]];", slice(&src, it.next().unwrap()));
        assert_eq!("let c = ([1, 2, 3])[1];", slice(&src, it.next().unwrap()));
    }

    #[test]
    fn iterates_use_stmt_over_two_lines() {
        let src = rejustify("
        use std::{Foo,
                  Bar}; // a comment
        ");
        let mut it = iter_stmts(src.as_ref());
        assert_eq!("use std::{Foo,
              Bar};", slice(&src, it.next().unwrap()));
    }

    #[test]
    fn iterates_use_stmt_without_the_prefix() {
        let src = rejustify("
        pub use {Foo,
                 Bar}; // this is also legit apparently
        ");
        let mut it = iter_stmts(src.as_ref());
        assert_eq!("pub use {Foo,
             Bar};", slice(&src, it.next().unwrap())
        );
    }

    #[test]
    fn iterates_while_stmt() {
        let src = rejustify("
            while self.pos < 3 { }
        ");
        let mut it = iter_stmts(src.as_ref());
        assert_eq!("while self.pos < 3 { }", slice(&src, it.next().unwrap()));
    }

    #[test]
    fn iterates_lambda_arg() {
        let src = rejustify("
            myfn(|n|{});
        ");
        let mut it = iter_stmts(src.as_ref());
        assert_eq!("myfn(|n|{});", slice(&src, it.next().unwrap()));
    }

    #[test]
    fn iterates_macro() {
        let src = "
        mod foo;
        macro_rules! otry(
            ($e:expr) => (match $e { Some(e) => e, None => return })
        )
        mod bar;
        ";
        let mut it = iter_stmts(src.as_ref());
        assert_eq!("mod foo;", slice(&src, it.next().unwrap()));
        assert_eq!("macro_rules! otry(
            ($e:expr) => (match $e { Some(e) => e, None => return })
        )", slice(&src, it.next().unwrap()));
        assert_eq!("mod bar;", slice(&src, it.next().unwrap()));
    }

    #[test]
    fn iterates_macro_invocation() {
        let src = "
            mod foo;
            local_data_key!(local_stdout: Box<Writer + Send>)  // no ';'
            mod bar;
        ";
        let mut it = iter_stmts(src.as_ref());
        assert_eq!("mod foo;", slice(&src, it.next().unwrap()));
        assert_eq!("local_data_key!(local_stdout: Box<Writer + Send>)", slice(&src, it.next().unwrap()));
        assert_eq!("mod bar;", slice(&src, it.next().unwrap()));
    }

    #[test]
    fn iterates_if_else_stmt() {
        let src = "
            if self.pos < 3 { } else { }
        ";
        let mut it = iter_stmts(src.as_ref());
        assert_eq!("if self.pos < 3 { }", slice(&src, it.next().unwrap()));
        assert_eq!("else { }", slice(&src, it.next().unwrap()));
    }

    #[test]
    fn iterates_inner_scope() {
        let src = &"
        while(self.pos < 3 {
            let a = 35;
            return a + 35;  // should iterate this
        }
        {
            b = foo;       // but not this
        }
        "[29..];

        let mut it = iter_stmts(src.as_ref());

        assert_eq!("let a = 35;", slice(&src, it.next().unwrap()));
        assert_eq!("return a + 35;", slice(&src, it.next().unwrap()));
        assert_eq!(None, it.next());
    }

    #[test]
    fn iterates_module_attribute() {
        let src = rejustify("
            #![license = \"BSD\"]
            #[test]
        ");
        let mut it = iter_stmts(src.as_ref());
        assert_eq!("#![license = \"BSD\"]", slice(&src, it.next().unwrap()));
        assert_eq!("#[test]", slice(&src, it.next().unwrap()));
    }

    #[test]
    fn iterates_half_open_subscope_if_is_the_last_thing() {
        let src = "
            let something = 35;
            while self.pos < 3 {
            let a = 35;
            return a + 35;  // should iterate this
        ";

        let mut it = iter_stmts(src.as_ref());
        assert_eq!("let something = 35;", slice(&src, it.next().unwrap()));
        assert_eq!("while self.pos < 3 {
            let a = 35;
            return a + 35;  // should iterate this
        ", slice(&src, it.next().unwrap()));
    }

}
<commit_msg>Add regression test for codeiter<commit_after>use std::iter::{Fuse, Iterator};

use core::{BytePos, ByteRange};

pub struct StmtIndicesIter<'a,I>
    where I: Iterator<Item = ByteRange>
{
    src: &'a str,
    it: I,
    pos: BytePos,
    end: BytePos
}

impl<'a,I> Iterator for StmtIndicesIter<'a,I>
    where I: Iterator<Item = ByteRange>
{
    type Item = ByteRange;

    #[inline]
    fn next(&mut self) -> Option<Self::Item> {
        let src_bytes = self.src.as_bytes();
        let mut enddelim = b';';
        let mut bracelevel = 0isize;
        let mut parenlevel = 0isize;
        let mut bracketlevel = 0isize;
        let mut start = self.pos;
        let mut pos = self.pos;

        // loop on all code_chunks until we find a relevant open/close pattern
        loop {
            // do we need the next chunk?
            if self.end == pos {
                // get the next chunk of code
                match self.it.next() {
                    Some(ch_range) => {
                        self.end = ch_range.end;
                        if start == pos {
                            start = ch_range.start;
                        }
                        pos = ch_range.start;
                    }
                    None => {
                        // no more chunks. finished
                        self.pos = pos;
                        if start < self.end {
                            return Some(ByteRange::new(start, self.end));
                        } else {
                            return None;
                        }
                    }
                }
            }

            if start == pos {
                // if this is a new stmt block, skip the whitespace
                for &b in &src_bytes[pos.0..self.end.0] {
                    match b {
                        b' ' | b'\r' | b'\n' | b'\t' => {
                            pos += BytePos(1);
                        },
                        _ => { break; }
                    }
                }
                start = pos;
                // test attribute   #[foo = bar]
                if pos < self.end && src_bytes[pos.0] == b'#' {
                    enddelim = b']'
                };
            }

            // iterate through the chunk, looking for stmt end
            for &b in &src_bytes[pos.0..self.end.0] {
                pos += BytePos(1);
                match b {
                    b'(' => { parenlevel += 1; },
                    b')' => { parenlevel -= 1; },
                    b'[' => { bracketlevel += 1; },
                    b']' => { bracketlevel -= 1; },
                    b'{' => {
                        // if we are top level and stmt is not a 'use' or 'let' then
                        // closebrace finishes the stmt
                        if bracelevel == 0 && parenlevel == 0
                            && !(is_a_use_stmt(src_bytes, start, pos) || is_a_let_stmt(src_bytes, start, pos)) {
                            enddelim = b'}';
                        }
                        bracelevel += 1;
                    },
                    b'}' => {
                        // have we reached the end of the scope?
                        if bracelevel == 0 {
                            self.pos = pos;
                            return None;
                        }
                        bracelevel -= 1;
                    },
                    b'!' => {
                        // macro if followed by at least one space or (
                        // FIXME: test with boolean 'not' expression
                        if parenlevel == 0 && bracelevel == 0
                            && pos < self.end && (pos - start).0 > 1 {
                            match src_bytes[pos.0] {
                                b' ' | b'\r' | b'\n' | b'\t' | b'('  => {
                                    enddelim = b')';
                                },
                                _ => {}
                            }
                        }
                    }
                    _ => {}
                }
                if parenlevel < 0 || bracelevel < 0 || bracketlevel < 0
                    || (enddelim == b && bracelevel == 0 && parenlevel == 0 && bracketlevel == 0)
                {
                    self.pos = pos;
                    return Some(ByteRange::new(start, pos));
                }
            }
        }
    }
}

fn is_a_use_stmt(src_bytes: &[u8], start: BytePos, pos: BytePos) -> bool {
    let whitespace = b" {\t\r\n";
    (pos.0 > 3 && &src_bytes[start.0..start.0 + 3] == b"use" &&
     whitespace.contains(&src_bytes[start.0 + 3])) ||
    (pos.0 > 7 && &src_bytes[start.0..(start.0 + 7)] == b"pub use" &&
     whitespace.contains(&src_bytes[start.0 + 7]))
}

fn is_a_let_stmt(src_bytes: &[u8], start: BytePos, pos: BytePos) -> bool {
    let whitespace = b" {\t\r\n";
    pos.0 > 3 && &src_bytes[start.0..start.0 + 3] == b"let"
        && whitespace.contains(&src_bytes[start.0 + 3])
}

impl<'a, I> StmtIndicesIter<'a,I>
    where I: Iterator<Item = ByteRange>
{
    pub fn from_parts(src: &str, it: I) -> Fuse<StmtIndicesIter<I>> {
        StmtIndicesIter{ src, it, pos: BytePos::ZERO, end: BytePos::ZERO }.fuse()
    }
}

#[cfg(test)]
mod test {
    use std::iter::Fuse;

    use codecleaner;
    use testutils::{rejustify, slice};

    use super::*;

    fn iter_stmts(src: &str) -> Fuse<StmtIndicesIter<codecleaner::CodeIndicesIter>> {
        let it = codecleaner::code_chunks(src);
        StmtIndicesIter{ src: src, it: it, pos: BytePos::ZERO, end: BytePos::ZERO }.fuse()
    }

    #[test]
    fn iterates_single_use_stmts() {
        let src = rejustify("
            use std::Foo; // a comment
            use std::Bar;
        ");

        let mut it = iter_stmts(src.as_ref());
        assert_eq!("use std::Foo;", slice(&src, it.next().unwrap()));
        assert_eq!("use std::Bar;", slice(&src, it.next().unwrap()));
    }

    #[test]
    fn iterates_array_stmts() {
        let src = rejustify("
            let a: [i32; 2] = [1, 2];
            let b = [[0], [1], [2]];
            let c = ([1, 2, 3])[1];
        ");

        let mut it = iter_stmts(src.as_ref());
        assert_eq!("let a: [i32; 2] = [1, 2];", slice(&src, it.next().unwrap()));
        assert_eq!("let b = [[0], [1], [2]];", slice(&src, it.next().unwrap()));
        assert_eq!("let c = ([1, 2, 3])[1];", slice(&src, it.next().unwrap()));
    }

    #[test]
    fn iterates_use_stmt_over_two_lines() {
        let src = rejustify("
        use std::{Foo,
                  Bar}; // a comment
        ");
        let mut it = iter_stmts(src.as_ref());
        assert_eq!("use std::{Foo,
              Bar};", slice(&src, it.next().unwrap()));
    }

    #[test]
    fn iterates_use_stmt_without_the_prefix() {
        let src = rejustify("
        pub use {Foo,
                 Bar}; // this is also legit apparently
        ");
        let mut it = iter_stmts(src.as_ref());
        assert_eq!("pub use {Foo,
             Bar};", slice(&src, it.next().unwrap())
        );
    }

    #[test]
    fn iterates_while_stmt() {
        let src = rejustify("
            while self.pos < 3 { }
        ");
        let mut it = iter_stmts(src.as_ref());
        assert_eq!("while self.pos < 3 { }", slice(&src, it.next().unwrap()));
    }

    #[test]
    fn iterates_lambda_arg() {
        let src = rejustify("
            myfn(|n|{});
        ");
        let mut it = iter_stmts(src.as_ref());
        assert_eq!("myfn(|n|{});", slice(&src, it.next().unwrap()));
    }

    #[test]
    fn iterates_macro() {
        let src = "
        mod foo;
        macro_rules! otry(
            ($e:expr) => (match $e { Some(e) => e, None => return })
        )
        mod bar;
        ";
        let mut it = iter_stmts(src.as_ref());
        assert_eq!("mod foo;", slice(&src, it.next().unwrap()));
        assert_eq!("macro_rules! otry(
            ($e:expr) => (match $e { Some(e) => e, None => return })
        )", slice(&src, it.next().unwrap()));
        assert_eq!("mod bar;", slice(&src, it.next().unwrap()));
    }

    #[test]
    fn iterates_macro_invocation() {
        let src = "
            mod foo;
            local_data_key!(local_stdout: Box<Writer + Send>)  // no ';'
            mod bar;
        ";
        let mut it = iter_stmts(src.as_ref());
        assert_eq!("mod foo;", slice(&src, it.next().unwrap()));
        assert_eq!("local_data_key!(local_stdout: Box<Writer + Send>)", slice(&src, it.next().unwrap()));
        assert_eq!("mod bar;", slice(&src, it.next().unwrap()));
    }

    #[test]
    fn iterates_if_else_stmt() {
        let src = "
            if self.pos < 3 { } else { }
        ";
        let mut it = iter_stmts(src.as_ref());
        assert_eq!("if self.pos < 3 { }", slice(&src, it.next().unwrap()));
        assert_eq!("else { }", slice(&src, it.next().unwrap()));
    }

    #[test]
    fn iterates_inner_scope() {
        let src = &"
        while(self.pos < 3 {
            let a = 35;
            return a + 35;  // should iterate this
        }
        {
            b = foo;       // but not this
        }
        "[29..];

        let mut it = iter_stmts(src.as_ref());

        assert_eq!("let a = 35;", slice(&src, it.next().unwrap()));
        assert_eq!("return a + 35;", slice(&src, it.next().unwrap()));
        assert_eq!(None, it.next());
    }

    #[test]
    fn iterates_module_attribute() {
        let src = rejustify("
            #![license = \"BSD\"]
            #[test]
        ");
        let mut it = iter_stmts(src.as_ref());
        assert_eq!("#![license = \"BSD\"]", slice(&src, it.next().unwrap()));
        assert_eq!("#[test]", slice(&src, it.next().unwrap()));
    }

    #[test]
    fn iterates_half_open_subscope_if_is_the_last_thing() {
        let src = "
            let something = 35;
            while self.pos < 3 {
            let a = 35;
            return a + 35;  // should iterate this
        ";

        let mut it = iter_stmts(src.as_ref());
        assert_eq!("let something = 35;", slice(&src, it.next().unwrap()));
        assert_eq!("while self.pos < 3 {
            let a = 35;
            return a + 35;  // should iterate this
        ", slice(&src, it.next().unwrap()));
    }

    #[test]
    fn iterates_ndarray() {
        let src = "
            let a = [[f64; 5]; 5];
            pub struct Matrix44f(pub [[f64; 4]; 4]);
        ";
        let mut it = iter_stmts(src.as_ref());
        assert_eq!("let a = [[f64; 5]; 5];", slice(&src, it.next().unwrap()));
        assert_eq!("pub struct Matrix44f(pub [[f64; 4]; 4]);", slice(&src, it.next().unwrap()));
    }
}
<|endoftext|>


<commit_before>use time::{SteadyTime, Duration};
use std::collections::BTreeMap;
use std::cmp::Ordering;
use std::fmt;
use std::fmt::Debug;
use std::cmp::PartialEq;

#[derive(Clone)]
struct Task<Token> where Token: Clone {
    interval: Duration,
    run_offset: Duration,
    token: Token,
    bond: TaskBond
}

#[derive(Clone, Debug)]
enum TaskBond {
    OneOff,
    Perpetual
}

impl<Token> fmt::Debug for Task<Token> where Token: Clone {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self.bond {
            TaskBond::Perpetual => write!(f, "Task(every {}ms)", self.interval.num_milliseconds()),
            TaskBond::OneOff => write!(f, "Task(after {}ms)", self.interval.num_milliseconds())
        }
    }
}

impl<Token> Task<Token> where Token: Clone {
    fn new(interval: Duration, run_offset: Duration, bond: TaskBond, token: Token) -> Task<Token> {
        assert!(interval > Duration::seconds(0)); // negative interval would make schedule go back in time!
        Task {
            interval: interval,
            run_offset: run_offset,
            bond: bond,
            token: token
        }
    }

    //TODO: OneOff task should not have this
    fn next(self) -> Task<Token> {
        Task {
            run_offset: self.run_offset + self.interval,
            .. self
        }
    }

    fn schedule(&self) -> Duration {
        self.run_offset + self.interval
    }
}

pub trait TimeSource {
    // Duration since this TimeSource was crated
    fn now(&self) -> Duration;
}

pub trait FastForward {
    fn fast_forward(&mut self, duration: Duration);
}

#[allow(dead_code)]
struct SteadyTimeSource {
    offset: SteadyTime
}

impl SteadyTimeSource {
    fn new() -> SteadyTimeSource {
        SteadyTimeSource {
            offset: SteadyTime::now()
        }
    }
}

impl TimeSource for SteadyTimeSource {
    fn now(&self) -> Duration {
        SteadyTime::now() - self.offset
    }
}

// TODO: should that be u64?
type TimePoint = u32;

pub enum SchedulerAction {
    None,
    Wait(Duration),
    Skip(Vec<TimePoint>),
    Yield(TimePoint)
}

pub enum Schedule<Token> {
    NextIn(Duration),
    Missed(Vec<Token>),
    Current(Vec<Token>)
}

impl<Token> PartialEq for Schedule<Token> where Token: PartialEq<Token> {
    fn eq(&self, other: &Self) -> bool {
        match self {
            &Schedule::NextIn(ref duration) => if let &Schedule::NextIn(ref other_duration) = other {
                duration == other_duration
            } else {
                false
            },
            &Schedule::Missed(ref tokens) => if let &Schedule::Missed(ref other_tokens) = other {
                tokens == other_tokens
            } else {
                false
            },
            &Schedule::Current(ref tokens) => if let &Schedule::Current(ref other_tokens) = other {
                tokens == other_tokens
            } else {
                false
            }
        }
    }
}

impl<Token> Debug for Schedule<Token> where Token: Debug {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            &Schedule::NextIn(ref duration) => write!(f, "Schedule::NextIn({}ms)", duration.num_milliseconds()),
            &Schedule::Missed(ref tokens) => write!(f, "Schedule::Missed({:?})", tokens),
            &Schedule::Current(ref tokens) => write!(f, "Schedule::Current({:?})", tokens),
        }
    }
}

pub struct Scheduler<Token, TS> where TS: TimeSource, Token: Clone {
    time_point_interval: Duration,
    tasks: BTreeMap<TimePoint, Vec<Task<Token>>>,
    time_source: TS
}

impl<Token, TS> Scheduler<Token, TS> where TS: TimeSource, Token: Clone {
    //TODO: way to specify how many durations it is OK to consider task current
    pub fn new(time_point_interval: Duration, time_source: TS) -> Scheduler<Token, TS> where TS: TimeSource {
        assert!(time_point_interval > Duration::seconds(0));
        Scheduler {
            time_point_interval: time_point_interval,
            tasks: BTreeMap::new(),
            time_source: time_source
        }
    }

    //TODO: with_time_source()

    fn schedule(&mut self, task: Task<Token>) {
        let time_point = self.to_time_point(task.schedule());
        self.tasks.entry(time_point).or_insert(Vec::new()).push(task);
    }

    pub fn after(&mut self, duration: Duration, token: Token) {
        let task = Task::new(duration, self.time_source.now(), TaskBond::OneOff, token);
        self.schedule(task);
    }

    pub fn every(&mut self, duration: Duration, token: Token) {
        let task = Task::new(duration, self.time_source.now(), TaskBond::Perpetual, token);
        self.schedule(task);
    }

    // TODO: we may miss some time points if wait waits longer
    fn next_action(&self) -> SchedulerAction {
        let now = self.time_source.now();
        let current_time_point = self.to_time_point(now);

        match self.tasks.iter().next() {
            None => SchedulerAction::None,
            Some((&time_point, _)) => {
                match time_point.cmp(&current_time_point) {
                    Ordering::Greater => SchedulerAction::Wait((self.to_duration(time_point)) - now),
                    //TODO: make task Eq for sorting by time
                    Ordering::Less => SchedulerAction::Skip(self.tasks.iter().take_while(|&(&time_point, &_)| time_point < current_time_point).map(|(time_point, _)| time_point.clone()).collect()),
                    Ordering::Equal => SchedulerAction::Yield(time_point)
                }
            }
        }
    }

    pub fn next(&mut self) -> Option<Schedule<Token>> {
        match self.next_action() {
            SchedulerAction::None => None,
            SchedulerAction::Wait(duration) => {
                Some(Schedule::NextIn(duration))
            },
            SchedulerAction::Skip(time_points) => {
                Some(Schedule::Missed(self.consume(time_points)))
            },
            SchedulerAction::Yield(time_point) => {
                Some(Schedule::Current(self.consume(vec![time_point])))
            }
        }
    }

    fn consume(&mut self, time_points: Vec<TimePoint>) -> Vec<Token> {
        let mut tasks: Vec<Task<Token>> = time_points.iter().flat_map(|time_point|
                self.tasks.remove(&time_point).unwrap()
            ).collect();

        tasks.sort_by(|a, b| a.run_offset.cmp(&b.run_offset));
        let tokens = tasks.iter().map(|ref task| task.token.clone()).collect();

        for task in tasks {
            match task.bond {
                TaskBond::Perpetual => self.schedule(task.next()),
                TaskBond::OneOff => ()
            };
        }
        tokens
    }

    fn to_time_point(&self, duration: Duration) -> TimePoint {
        let interval = self.time_point_interval.num_microseconds().unwrap();
        let duration = duration.num_microseconds().unwrap();
        assert!(duration >= 0);

        (duration / interval) as TimePoint
    }

    fn to_duration(&self, time_point: TimePoint) -> Duration {
        self.time_point_interval * (time_point as i32)
    }
}

impl<Token, TS> FastForward for Scheduler<Token, TS> where TS: TimeSource + FastForward, Token: Clone {
    fn fast_forward(&mut self, duration: Duration) {
        self.time_source.fast_forward(duration);
    }
}

#[cfg(test)]
mod task {
    use time::Duration;
    use super::{Task, TaskBond, TimeSource, Scheduler, Schedule, FastForward};

    struct MockTimeSource {
        current_time: Duration
    }

    impl MockTimeSource {
        fn new() -> MockTimeSource {
            MockTimeSource {
                current_time: Duration::seconds(0)
            }
        }
    }

    impl FastForward for MockTimeSource {
        fn fast_forward(&mut self, duration: Duration) {
            self.current_time = self.current_time + duration;
        }
    }

    impl TimeSource for MockTimeSource {
        fn now(&self) -> Duration {
            self.current_time
        }
    }

    #[test]
    fn getting_next_schedule() {
        let now = Duration::seconds(0);
        let interval = Duration::seconds(1);
        let task = Task::new(interval, now, TaskBond::OneOff, 42);
        println!("task: {:?}", task);
        assert_eq!(task.schedule(), now + interval);
        assert_eq!(task.next().next().schedule(), now + interval * 3);
    }

    #[test]
    fn scheduler_to_time_point() {
        let scheduler: Scheduler<(),_> = Scheduler::new(Duration::seconds(1), MockTimeSource::new());
        assert_eq!(scheduler.to_time_point(Duration::seconds(0)), 0);
        assert_eq!(scheduler.to_time_point(Duration::seconds(1)), 1);
        assert_eq!(scheduler.to_time_point(Duration::seconds(2)), 2);
        assert_eq!(scheduler.to_time_point(Duration::milliseconds(2000)), 2);
        assert_eq!(scheduler.to_time_point(Duration::milliseconds(100)), 0);
        assert_eq!(scheduler.to_time_point(Duration::milliseconds(1100)), 1);
        assert_eq!(scheduler.to_time_point(Duration::milliseconds(1500)), 1);
        assert_eq!(scheduler.to_time_point(Duration::milliseconds(1800)), 1);
        assert_eq!(scheduler.to_time_point(Duration::milliseconds(2800)), 2);
    }

    #[test]
    fn scheduler_after() {
        let mut scheduler = Scheduler::new(Duration::seconds(1), MockTimeSource::new());

        scheduler.after(Duration::seconds(1), 1i32);
        assert_eq!(scheduler.next(), Option::Some(Schedule::NextIn(Duration::seconds(1))));
        assert_eq!(scheduler.next(), Option::Some(Schedule::NextIn(Duration::seconds(1))));

        scheduler.fast_forward(Duration::milliseconds(100));
        assert_eq!(scheduler.next(), Option::Some(Schedule::NextIn(Duration::milliseconds(900))));
        scheduler.fast_forward(Duration::milliseconds(900));
        assert_eq!(scheduler.next(), Option::Some(Schedule::Current(vec![1])));
        assert_eq!(scheduler.next(), Option::None);
    }

    #[test]
    fn scheduler_every() {
        let mut scheduler = Scheduler::new(Duration::seconds(1), MockTimeSource::new());

        scheduler.every(Duration::seconds(1), 1i32);
        assert_eq!(scheduler.next(), Option::Some(Schedule::NextIn(Duration::seconds(1))));
        assert_eq!(scheduler.next(), Option::Some(Schedule::NextIn(Duration::seconds(1))));

        scheduler.fast_forward(Duration::milliseconds(100));
        assert_eq!(scheduler.next(), Option::Some(Schedule::NextIn(Duration::milliseconds(900))));
        scheduler.fast_forward(Duration::milliseconds(900));
        assert_eq!(scheduler.next(), Option::Some(Schedule::Current(vec![1])));
        assert_eq!(scheduler.next(), Option::Some(Schedule::NextIn(Duration::seconds(1))));

        scheduler.fast_forward(Duration::milliseconds(600));
        assert_eq!(scheduler.next(), Option::Some(Schedule::NextIn(Duration::milliseconds(400))));
        scheduler.fast_forward(Duration::milliseconds(500));
        assert_eq!(scheduler.next(), Option::Some(Schedule::Current(vec![1])));
        assert_eq!(scheduler.next(), Option::Some(Schedule::NextIn(Duration::milliseconds(900))));
    }
}

<commit_msg>with_time_source and cleanup<commit_after>use time::{SteadyTime, Duration};
use std::collections::BTreeMap;
use std::cmp::Ordering;
use std::fmt;
use std::fmt::Debug;
use std::cmp::PartialEq;

#[derive(Clone)]
struct Task<Token> where Token: Clone {
    interval: Duration,
    run_offset: Duration,
    token: Token,
    bond: TaskBond
}

#[derive(Clone, Debug)]
enum TaskBond {
    OneOff,
    Perpetual
}

impl<Token> fmt::Debug for Task<Token> where Token: Clone {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self.bond {
            TaskBond::Perpetual => write!(f, "Task(every {}ms)", self.interval.num_milliseconds()),
            TaskBond::OneOff => write!(f, "Task(after {}ms)", self.interval.num_milliseconds())
        }
    }
}

impl<Token> Task<Token> where Token: Clone {
    fn new(interval: Duration, run_offset: Duration, bond: TaskBond, token: Token) -> Task<Token> {
        assert!(interval > Duration::seconds(0)); // negative interval would make schedule go back in time!
        Task {
            interval: interval,
            run_offset: run_offset,
            bond: bond,
            token: token
        }
    }

    //TODO: OneOff task should not have this
    fn next(self) -> Task<Token> {
        Task {
            run_offset: self.run_offset + self.interval,
            .. self
        }
    }

    fn schedule(&self) -> Duration {
        self.run_offset + self.interval
    }
}

pub trait TimeSource {
    // Duration since this TimeSource was crated
    fn now(&self) -> Duration;
}

pub trait FastForward {
    fn fast_forward(&mut self, duration: Duration);
}

pub struct SteadyTimeSource {
    offset: SteadyTime
}

impl SteadyTimeSource {
    fn new() -> SteadyTimeSource {
        SteadyTimeSource {
            offset: SteadyTime::now()
        }
    }
}

impl TimeSource for SteadyTimeSource {
    fn now(&self) -> Duration {
        SteadyTime::now() - self.offset
    }
}

// TODO: should that be u64?
type TimePoint = u32;

pub enum SchedulerAction {
    None,
    Wait(Duration),
    Skip(Vec<TimePoint>),
    Yield(TimePoint)
}

pub enum Schedule<Token> {
    NextIn(Duration),
    Missed(Vec<Token>),
    Current(Vec<Token>)
}

impl<Token> PartialEq for Schedule<Token> where Token: PartialEq<Token> {
    fn eq(&self, other: &Self) -> bool {
        match self {
            &Schedule::NextIn(ref duration) => if let &Schedule::NextIn(ref other_duration) = other {
                duration == other_duration
            } else {
                false
            },
            &Schedule::Missed(ref tokens) => if let &Schedule::Missed(ref other_tokens) = other {
                tokens == other_tokens
            } else {
                false
            },
            &Schedule::Current(ref tokens) => if let &Schedule::Current(ref other_tokens) = other {
                tokens == other_tokens
            } else {
                false
            }
        }
    }
}

impl<Token> Debug for Schedule<Token> where Token: Debug {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            &Schedule::NextIn(ref duration) => write!(f, "Schedule::NextIn({}ms)", duration.num_milliseconds()),
            &Schedule::Missed(ref tokens) => write!(f, "Schedule::Missed({:?})", tokens),
            &Schedule::Current(ref tokens) => write!(f, "Schedule::Current({:?})", tokens),
        }
    }
}

pub struct Scheduler<Token, TS> where TS: TimeSource, Token: Clone {
    time_point_interval: Duration,
    tasks: BTreeMap<TimePoint, Vec<Task<Token>>>,
    time_source: TS
}

impl<Token> Scheduler<Token, SteadyTimeSource> where Token: Clone {
    //TODO: way to specify how many durations it is OK to consider task current
    pub fn new(time_point_interval: Duration) -> Scheduler<Token, SteadyTimeSource> {
        Scheduler::with_time_source(time_point_interval, SteadyTimeSource::new())
    }

}

impl<Token, TS> Scheduler<Token, TS> where TS: TimeSource, Token: Clone {
    pub fn with_time_source(time_point_interval: Duration, time_source: TS) -> Scheduler<Token, TS> {
        assert!(time_point_interval > Duration::seconds(0));
        Scheduler {
            time_point_interval: time_point_interval,
            tasks: BTreeMap::new(),
            time_source: time_source
        }
    }

    fn schedule(&mut self, task: Task<Token>) {
        let time_point = self.to_time_point(task.schedule());
        self.tasks.entry(time_point).or_insert(Vec::new()).push(task);
    }

    pub fn after(&mut self, duration: Duration, token: Token) {
        let task = Task::new(duration, self.time_source.now(), TaskBond::OneOff, token);
        self.schedule(task);
    }

    pub fn every(&mut self, duration: Duration, token: Token) {
        let task = Task::new(duration, self.time_source.now(), TaskBond::Perpetual, token);
        self.schedule(task);
    }

    // TODO: we may miss some time points if wait waits longer
    fn next_action(&self) -> SchedulerAction {
        let now = self.time_source.now();
        let current_time_point = self.to_time_point(now);

        match self.tasks.iter().next() {
            None => SchedulerAction::None,
            Some((&time_point, _)) => {
                match time_point.cmp(&current_time_point) {
                    Ordering::Greater => SchedulerAction::Wait((self.to_duration(time_point)) - now),
                    //TODO: make task Eq for sorting by time
                    Ordering::Less => SchedulerAction::Skip(self.tasks.iter().take_while(|&(&time_point, &_)| time_point < current_time_point).map(|(time_point, _)| time_point.clone()).collect()),
                    Ordering::Equal => SchedulerAction::Yield(time_point)
                }
            }
        }
    }

    pub fn next(&mut self) -> Option<Schedule<Token>> {
        match self.next_action() {
            SchedulerAction::None => None,
            SchedulerAction::Wait(duration) => {
                Some(Schedule::NextIn(duration))
            },
            SchedulerAction::Skip(time_points) => {
                Some(Schedule::Missed(self.consume(time_points)))
            },
            SchedulerAction::Yield(time_point) => {
                Some(Schedule::Current(self.consume(vec![time_point])))
            }
        }
    }

    fn consume(&mut self, time_points: Vec<TimePoint>) -> Vec<Token> {
        let mut tasks: Vec<Task<Token>> = time_points.iter().flat_map(|time_point|
                self.tasks.remove(&time_point).unwrap()
            ).collect();

        tasks.sort_by(|a, b| a.run_offset.cmp(&b.run_offset));
        let tokens = tasks.iter().map(|ref task| task.token.clone()).collect();

        for task in tasks {
            match task.bond {
                TaskBond::Perpetual => self.schedule(task.next()),
                TaskBond::OneOff => ()
            };
        }
        tokens
    }

    fn to_time_point(&self, duration: Duration) -> TimePoint {
        let interval = self.time_point_interval.num_microseconds().unwrap();
        let duration = duration.num_microseconds().unwrap();
        assert!(duration >= 0);

        (duration / interval) as TimePoint
    }

    fn to_duration(&self, time_point: TimePoint) -> Duration {
        self.time_point_interval * (time_point as i32)
    }
}

impl<Token, TS> FastForward for Scheduler<Token, TS> where TS: TimeSource + FastForward, Token: Clone {
    fn fast_forward(&mut self, duration: Duration) {
        self.time_source.fast_forward(duration);
    }
}

#[cfg(test)]
mod task {
    use super::*;
    use super::{Task, TaskBond};
    use time::Duration;

    struct MockTimeSource {
        current_time: Duration
    }

    impl MockTimeSource {
        fn new() -> MockTimeSource {
            MockTimeSource {
                current_time: Duration::seconds(0)
            }
        }
    }

    impl FastForward for MockTimeSource {
        fn fast_forward(&mut self, duration: Duration) {
            self.current_time = self.current_time + duration;
        }
    }

    impl TimeSource for MockTimeSource {
        fn now(&self) -> Duration {
            self.current_time
        }
    }

    #[test]
    fn getting_next_schedule() {
        let now = Duration::seconds(0);
        let interval = Duration::seconds(1);
        let task = Task::new(interval, now, TaskBond::OneOff, 42);
        println!("task: {:?}", task);
        assert_eq!(task.schedule(), now + interval);
        assert_eq!(task.next().next().schedule(), now + interval * 3);
    }

    #[test]
    fn scheduler_to_time_point() {
        let scheduler: Scheduler<(), _> = Scheduler::new(Duration::seconds(1));
        assert_eq!(scheduler.to_time_point(Duration::seconds(0)), 0);
        assert_eq!(scheduler.to_time_point(Duration::seconds(1)), 1);
        assert_eq!(scheduler.to_time_point(Duration::seconds(2)), 2);
        assert_eq!(scheduler.to_time_point(Duration::milliseconds(2000)), 2);
        assert_eq!(scheduler.to_time_point(Duration::milliseconds(100)), 0);
        assert_eq!(scheduler.to_time_point(Duration::milliseconds(1100)), 1);
        assert_eq!(scheduler.to_time_point(Duration::milliseconds(1500)), 1);
        assert_eq!(scheduler.to_time_point(Duration::milliseconds(1800)), 1);
        assert_eq!(scheduler.to_time_point(Duration::milliseconds(2800)), 2);
    }

    #[test]
    fn scheduler_after() {
        let mut scheduler = Scheduler::with_time_source(Duration::seconds(1), MockTimeSource::new());

        scheduler.after(Duration::seconds(1), 1i32);
        assert_eq!(scheduler.next(), Option::Some(Schedule::NextIn(Duration::seconds(1))));
        assert_eq!(scheduler.next(), Option::Some(Schedule::NextIn(Duration::seconds(1))));

        scheduler.fast_forward(Duration::milliseconds(100));
        assert_eq!(scheduler.next(), Option::Some(Schedule::NextIn(Duration::milliseconds(900))));
        scheduler.fast_forward(Duration::milliseconds(900));
        assert_eq!(scheduler.next(), Option::Some(Schedule::Current(vec![1])));
        assert_eq!(scheduler.next(), Option::None);
    }

    #[test]
    fn scheduler_every() {
        let mut scheduler = Scheduler::with_time_source(Duration::seconds(1), MockTimeSource::new());

        scheduler.every(Duration::seconds(1), 1i32);
        assert_eq!(scheduler.next(), Option::Some(Schedule::NextIn(Duration::seconds(1))));
        assert_eq!(scheduler.next(), Option::Some(Schedule::NextIn(Duration::seconds(1))));

        scheduler.fast_forward(Duration::milliseconds(100));
        assert_eq!(scheduler.next(), Option::Some(Schedule::NextIn(Duration::milliseconds(900))));
        scheduler.fast_forward(Duration::milliseconds(900));
        assert_eq!(scheduler.next(), Option::Some(Schedule::Current(vec![1])));
        assert_eq!(scheduler.next(), Option::Some(Schedule::NextIn(Duration::seconds(1))));

        scheduler.fast_forward(Duration::milliseconds(600));
        assert_eq!(scheduler.next(), Option::Some(Schedule::NextIn(Duration::milliseconds(400))));
        scheduler.fast_forward(Duration::milliseconds(500));
        assert_eq!(scheduler.next(), Option::Some(Schedule::Current(vec![1])));
        assert_eq!(scheduler.next(), Option::Some(Schedule::NextIn(Duration::milliseconds(900))));
    }
}

<|endoftext|>