<commit_before>use sass::value_part::ValuePart;
use sass::number_value::NumberValue;
use sass::op::Op;
use value_tokenizer::ValueTokenizer;

use std::borrow::Cow::Borrowed;
use std::collections::HashMap;
extern crate collections;

#[derive(Debug)]
pub struct Evaluator<'a, T> {
    value_tokens: T,
    value_stack: Vec<ValuePart<'a>>,
    op_stack: Vec<Op>,
    paren_level: i32,
}

impl<'a> Evaluator<'a, ValueTokenizer<'a>> {
    pub fn new_from_string(original: &'a str) -> Evaluator<'a, ValueTokenizer<'a>>
    {
        Evaluator {
            value_tokens: ValueTokenizer::new(&original),
            value_stack: Vec::new(),
            op_stack: Vec::new(),
            paren_level: 0,
        }
    }
}

impl<'a> Evaluator<'a, collections::vec::IntoIter<ValuePart<'a>>> {
    pub fn new(value_tokens: Vec<ValuePart<'a>>) -> Evaluator<'a, collections::vec::IntoIter<ValuePart<'a>>>
    {
        Evaluator {
            value_tokens: value_tokens.into_iter(),
            value_stack: Vec::new(),
            op_stack: Vec::new(),
            paren_level: 0,
        }
    }
}

impl<'a, T> Evaluator<'a, T>
where T: Iterator<Item = ValuePart<'a>>
{
    pub fn evaluate(&mut self, variables: &HashMap<String, ValuePart<'a>>) -> ValuePart<'a> {
        let mut last_was_an_operator = true;

        while let Some(part) = self.value_tokens.next() {
            match part {
                ValuePart::Variable(name) => {
                    match variables.get(&(*name).to_string()) {
                        Some(v) => self.value_stack.push(v.clone()),
                        None => self.value_stack.push(ValuePart::String(name)),
                    }
                    last_was_an_operator = false;
                },
                s @ ValuePart::String(..) => {
                    self.value_stack.push(s);
                    last_was_an_operator = false;
                },
                n @ ValuePart::Number(..) => {
                    if last_was_an_operator {
                        self.value_stack.push(n);
                    } else {
                        while !self.op_stack.is_empty() && self.op_stack.last() != Some(&Op::LeftParen) {
                            self.math_machine();
                        }
                        let list_starter = self.value_stack.pop().expect("No expected list starter on the value stack!");
                        let list_parts = match list_starter {
                            ValuePart::List(mut v) => {
                                v.push(n);
                                v
                            },
                            other => vec![other, n],
                        };
                        self.value_stack.push(ValuePart::List(list_parts));
                    }
                    last_was_an_operator = false;
                },
                ValuePart::Operator(ref o) => {
                    if *o == Op::RightParen {
                        let mut last_operator = self.op_stack.last().unwrap_or(&Op::LeftParen).clone();
                        while last_operator != Op::LeftParen {
                            self.math_machine();
                            last_operator = self.op_stack.last().unwrap_or(&Op::LeftParen).clone();
                        }
                        self.op_stack.pop();
                        last_was_an_operator = false;
                        self.paren_level -= 1;
                    } else if *o == Op::LeftParen {
                        self.op_stack.push(*o);
                        last_was_an_operator = true;
                        self.paren_level += 1;
                    } else {
                        if let Some(&last_operator) = self.op_stack.last() {
                            if last_operator.same_or_greater_precedence(*o) {
                                self.math_machine();
                            }
                        }
                        self.op_stack.push(*o);
                        last_was_an_operator = true;
                    }
                },
                other => panic!("Got unexpected ValuePart {:?}", other),
            }
        }

        while !self.op_stack.is_empty() {
            self.math_machine();
        }

        if self.value_stack.len() > 1 {
            // TODO: figure out how to make borrowck like not cloning this, it's cool i swear
            ValuePart::List(self.value_stack.clone())
        } else {
            self.value_stack.pop().unwrap_or(ValuePart::Number(NumberValue::from_scalar(0.0)))
        }
    }

    fn math_machine(&mut self) {
        let op = self.op_stack.pop().unwrap_or(Op::Plus);
        let second = self.value_stack.pop().unwrap_or(ValuePart::Number(NumberValue::from_scalar(0.0)));
        let first  = self.value_stack.pop().unwrap_or(ValuePart::Number(NumberValue::from_scalar(0.0)));
        self.value_stack.push(op.apply(first, second, self.paren_level));
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use sass::value_part::ValuePart;
    use sass::number_value::NumberValue;
    use sass::op::Op;
    use std::collections::HashMap;
    use std::borrow::Cow::Borrowed;

    #[test]
    fn it_subtitutes_variable_values() {
        let mut vars = HashMap::new();
        vars.insert("$bar".to_string(), ValuePart::Number(NumberValue::from_scalar(4.0)));
        vars.insert("$quux".to_string(), ValuePart::List(vec![
            ValuePart::Number(NumberValue::with_units(3.0, Borrowed("px"))),
            ValuePart::Number(NumberValue::with_units(10.0, Borrowed("px"))),
        ]));

        let answer = Evaluator::new_from_string("foo $bar 199.82 baz $quux").evaluate(&vars);

        assert_eq!(
            ValuePart::List(vec![
                ValuePart::String(Borrowed("foo")),
                ValuePart::List(vec![
                    ValuePart::String(Borrowed("4")),
                    ValuePart::Number(NumberValue::from_scalar(199.82))
                ]),
                ValuePart::String(Borrowed("baz")),
                ValuePart::String(Borrowed("3px 10px"))
            ]),
            answer
        );
    }

    #[test]
    fn it_divides_if_value_came_from_a_variable() {
        let mut vars = HashMap::new();
        vars.insert("$three".to_string(), ValuePart::Number(NumberValue::from_scalar(3.0)));

        let answer = Evaluator::new_from_string("15 / $three").evaluate(&vars);

        assert_eq!(
            ValuePart::Number(NumberValue::from_scalar(5.0)),
            answer
        );
    }

    #[test]
    fn it_adds() {
        let answer = Evaluator::new(vec![
            ValuePart::Number(NumberValue::from_scalar(1.0)),
            ValuePart::Operator(Op::Plus),
            ValuePart::Number(NumberValue::from_scalar(2.0)),
        ]).evaluate(&HashMap::new());
        assert_eq!(ValuePart::Number(NumberValue::computed(3.0)), answer);
    }

    #[test]
    fn it_divides_and_adds_with_the_right_precedence() {
        let answer = Evaluator::new(vec![
            ValuePart::Number(NumberValue::from_scalar(3.0)),
            ValuePart::Operator(Op::Plus),
            ValuePart::Number(NumberValue::from_scalar(3.0)),
            ValuePart::Operator(Op::Slash),
            ValuePart::Number(NumberValue::from_scalar(4.0)),
        ]).evaluate(&HashMap::new());
        assert_eq!(ValuePart::Number(NumberValue::computed(3.75)), answer);
    }

    #[test]
    fn it_gets_the_right_precedence_with_parens() {
        let answer = Evaluator::new(vec![
            ValuePart::Operator(Op::LeftParen),
            ValuePart::Number(NumberValue::from_scalar(3.0)),
            ValuePart::Operator(Op::Plus),
            ValuePart::Number(NumberValue::from_scalar(3.0)),
            ValuePart::Operator(Op::RightParen),
            ValuePart::Operator(Op::Slash),
            ValuePart::Number(NumberValue::from_scalar(4.0)),
        ]).evaluate(&HashMap::new());
        assert_eq!(ValuePart::Number(NumberValue::computed(1.5)), answer);
    }

    #[test]
    fn it_does_string_concat_when_adding_to_list() {
        let answer = Evaluator::new(vec![
            ValuePart::Number(NumberValue::from_scalar(2.0)),
            ValuePart::Operator(Op::Plus),
            ValuePart::Operator(Op::LeftParen),
            ValuePart::Number(NumberValue::from_scalar(3.0)),
            ValuePart::Number(NumberValue::from_scalar(4.0)),
            ValuePart::Operator(Op::RightParen),
        ]).evaluate(&HashMap::new());

        assert_eq!(ValuePart::List(vec![
            ValuePart::String(Borrowed("23")),
            ValuePart::Number(NumberValue::from_scalar(4.0))
        ]), answer);
    }

    #[test]
    fn it_does_string_concat_when_adding_to_list_in_a_list() {
        let answer = Evaluator::new(vec![
            ValuePart::Operator(Op::LeftParen),
            ValuePart::Number(NumberValue::from_scalar(2.0)),
            ValuePart::Operator(Op::Plus),
            ValuePart::Operator(Op::LeftParen),
            ValuePart::Number(NumberValue::from_scalar(3.0)),
            ValuePart::Number(NumberValue::from_scalar(4.0)),
            ValuePart::Operator(Op::RightParen),
            ValuePart::Number(NumberValue::from_scalar(5.0)),
            ValuePart::Operator(Op::RightParen),
        ]).evaluate(&HashMap::new());

        assert_eq!(ValuePart::List(vec![
            ValuePart::String(Borrowed("23")),
            ValuePart::Number(NumberValue::from_scalar(4.0)),
            ValuePart::Number(NumberValue::from_scalar(5.0)),
        ]), answer);
    }

    #[test]
    fn it_divides_because_parens_and_string_concats_because_list() {
        let answer = Evaluator::new(vec![
            ValuePart::Number(NumberValue::from_scalar(1.0)),
            ValuePart::Operator(Op::Plus),
            ValuePart::Operator(Op::LeftParen),
            ValuePart::Number(NumberValue::from_scalar(5.0)),
            ValuePart::Operator(Op::Slash),
            ValuePart::Number(NumberValue::from_scalar(10.0)),
            ValuePart::Number(NumberValue::from_scalar(2.0)),
            ValuePart::Number(NumberValue::from_scalar(3.0)),
            ValuePart::Operator(Op::RightParen),
        ]).evaluate(&HashMap::new());

        assert_eq!(ValuePart::List(vec![
            ValuePart::String(Borrowed("10.5")),
            ValuePart::Number(NumberValue::from_scalar(2.0)),
            ValuePart::Number(NumberValue::from_scalar(3.0)),
        ]), answer);
    }

    #[test]
    fn it_does_not_divide_when_slash_is_separating() {
        let answer = Evaluator::new(vec![
            ValuePart::Number(NumberValue::from_scalar(15.0)),
            ValuePart::Operator(Op::Slash),
            ValuePart::Number(NumberValue::from_scalar(3.0)),
            ValuePart::Operator(Op::Slash),
            ValuePart::Number(NumberValue::from_scalar(5.0)),
        ]).evaluate(&HashMap::new());

        assert_eq!(ValuePart::List(vec![
            ValuePart::Number(NumberValue::from_scalar(15.0)),
            ValuePart::Operator(Op::Slash),
            ValuePart::Number(NumberValue::from_scalar(3.0)),
            ValuePart::Operator(Op::Slash),
            ValuePart::Number(NumberValue::from_scalar(5.0)),
        ]), answer);
    }

    #[test]
    fn it_does_divide_when_other_math_is_involved() {
        let answer = Evaluator::new(vec![
            ValuePart::Number(NumberValue::from_scalar(1.0)),
            ValuePart::Operator(Op::Slash),
            ValuePart::Number(NumberValue::from_scalar(2.0)),
            ValuePart::Operator(Op::Plus),
            ValuePart::Number(NumberValue::from_scalar(1.0)),
            ValuePart::Operator(Op::Slash),
            ValuePart::Number(NumberValue::from_scalar(2.0)),
        ]).evaluate(&HashMap::new());

        assert_eq!(ValuePart::Number(NumberValue::computed(1.0)), answer);
    }
}<commit_msg>fix tests<commit_after>use sass::value_part::ValuePart;
use sass::number_value::NumberValue;
use sass::op::Op;
use value_tokenizer::ValueTokenizer;

use std::borrow::Cow::Borrowed;
use std::collections::HashMap;
extern crate collections;

#[derive(Debug)]
pub struct Evaluator<'a, T> {
    value_tokens: T,
    value_stack: Vec<ValuePart<'a>>,
    op_stack: Vec<Op>,
    paren_level: i32,
}

impl<'a> Evaluator<'a, ValueTokenizer<'a>> {
    pub fn new_from_string(original: &'a str) -> Evaluator<'a, ValueTokenizer<'a>>
    {
        Evaluator {
            value_tokens: ValueTokenizer::new(&original),
            value_stack: Vec::new(),
            op_stack: Vec::new(),
            paren_level: 0,
        }
    }
}

impl<'a> Evaluator<'a, collections::vec::IntoIter<ValuePart<'a>>> {
    pub fn new(value_tokens: Vec<ValuePart<'a>>) -> Evaluator<'a, collections::vec::IntoIter<ValuePart<'a>>>
    {
        Evaluator {
            value_tokens: value_tokens.into_iter(),
            value_stack: Vec::new(),
            op_stack: Vec::new(),
            paren_level: 0,
        }
    }
}

impl<'a, T> Evaluator<'a, T>
where T: Iterator<Item = ValuePart<'a>>
{
    pub fn evaluate(&mut self, variables: &HashMap<String, ValuePart<'a>>) -> ValuePart<'a> {
        let mut last_was_an_operator = true;

        while let Some(part) = self.value_tokens.next() {
            match part {
                ValuePart::Variable(name) => {
                    match variables.get(&(*name).to_string()) {
                        Some(v) => self.value_stack.push(v.clone()),
                        None => self.value_stack.push(ValuePart::String(name)),
                    }
                    last_was_an_operator = false;
                },
                s @ ValuePart::String(..) => {
                    self.value_stack.push(s);
                    last_was_an_operator = false;
                },
                n @ ValuePart::Number(..) => {
                    if last_was_an_operator {
                        self.value_stack.push(n);
                    } else {
                        while !self.op_stack.is_empty() && self.op_stack.last() != Some(&Op::LeftParen) {
                            self.math_machine();
                        }
                        let list_starter = self.value_stack.pop().expect("No expected list starter on the value stack!");
                        let list_parts = match list_starter {
                            ValuePart::List(mut v) => {
                                v.push(n);
                                v
                            },
                            other => vec![other, n],
                        };
                        self.value_stack.push(ValuePart::List(list_parts));
                    }
                    last_was_an_operator = false;
                },
                ValuePart::Operator(ref o) => {
                    if *o == Op::RightParen {
                        let mut last_operator = self.op_stack.last().unwrap_or(&Op::LeftParen).clone();
                        while last_operator != Op::LeftParen {
                            self.math_machine();
                            last_operator = self.op_stack.last().unwrap_or(&Op::LeftParen).clone();
                        }
                        self.op_stack.pop();
                        last_was_an_operator = false;
                        self.paren_level -= 1;
                    } else if *o == Op::LeftParen {
                        self.op_stack.push(*o);
                        last_was_an_operator = true;
                        self.paren_level += 1;
                    } else {
                        if let Some(&last_operator) = self.op_stack.last() {
                            if last_operator.same_or_greater_precedence(*o) {
                                self.math_machine();
                            }
                        }
                        self.op_stack.push(*o);
                        last_was_an_operator = true;
                    }
                },
                other => panic!("Got unexpected ValuePart {:?}", other),
            }
        }

        while !self.op_stack.is_empty() {
            self.math_machine();
        }

        if self.value_stack.len() > 1 {
            // TODO: figure out how to make borrowck like not cloning this, it's cool i swear
            ValuePart::List(self.value_stack.clone())
        } else {
            self.value_stack.pop().unwrap_or(ValuePart::Number(NumberValue::from_scalar(0.0)))
        }
    }

    fn math_machine(&mut self) {
        let op = self.op_stack.pop().unwrap_or(Op::Plus);
        let second = self.value_stack.pop().unwrap_or(ValuePart::Number(NumberValue::from_scalar(0.0)));
        let first  = self.value_stack.pop().unwrap_or(ValuePart::Number(NumberValue::from_scalar(0.0)));
        self.value_stack.push(op.apply(first, second, self.paren_level));
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use sass::value_part::ValuePart;
    use sass::number_value::NumberValue;
    use sass::op::Op;
    use std::collections::HashMap;
    use std::borrow::Cow::Borrowed;

    #[test]
    fn it_subtitutes_variable_values() {
        let mut vars = HashMap::new();
        vars.insert("$bar".to_string(), ValuePart::Number(NumberValue::from_scalar(4.0)));
        vars.insert("$quux".to_string(), ValuePart::List(vec![
            ValuePart::Number(NumberValue::with_units(3.0, Borrowed("px"))),
            ValuePart::Number(NumberValue::with_units(10.0, Borrowed("px"))),
        ]));

        let answer = Evaluator::new_from_string("foo $bar 199.82 baz $quux").evaluate(&vars);

        assert_eq!(
            ValuePart::List(vec![
                ValuePart::String(Borrowed("foo")),
                ValuePart::List(vec![
                    ValuePart::Number(NumberValue::from_scalar(4.0)),
                    ValuePart::Number(NumberValue::from_scalar(199.82)),
                ]),
                ValuePart::String(Borrowed("baz")),
                ValuePart::List(vec![
                    ValuePart::Number(NumberValue::with_units(3.0, Borrowed("px"))),
                    ValuePart::Number(NumberValue::with_units(10.0, Borrowed("px"))),
                ]),
            ]),
            answer
        );
    }

    #[test]
    fn it_divides_if_value_came_from_a_variable() {
        let mut vars = HashMap::new();
        vars.insert("$three".to_string(), ValuePart::Number(NumberValue::from_scalar(3.0)));

        let answer = Evaluator::new_from_string("15 / $three").evaluate(&vars);

        assert_eq!(
            ValuePart::Number(NumberValue::from_scalar(5.0)),
            answer
        );
    }

    #[test]
    fn it_adds() {
        let answer = Evaluator::new(vec![
            ValuePart::Number(NumberValue::from_scalar(1.0)),
            ValuePart::Operator(Op::Plus),
            ValuePart::Number(NumberValue::from_scalar(2.0)),
        ]).evaluate(&HashMap::new());
        assert_eq!(ValuePart::Number(NumberValue::computed(3.0)), answer);
    }

    #[test]
    fn it_divides_and_adds_with_the_right_precedence() {
        let answer = Evaluator::new(vec![
            ValuePart::Number(NumberValue::from_scalar(3.0)),
            ValuePart::Operator(Op::Plus),
            ValuePart::Number(NumberValue::from_scalar(3.0)),
            ValuePart::Operator(Op::Slash),
            ValuePart::Number(NumberValue::from_scalar(4.0)),
        ]).evaluate(&HashMap::new());
        assert_eq!(ValuePart::Number(NumberValue::computed(3.75)), answer);
    }

    #[test]
    fn it_gets_the_right_precedence_with_parens() {
        let answer = Evaluator::new(vec![
            ValuePart::Operator(Op::LeftParen),
            ValuePart::Number(NumberValue::from_scalar(3.0)),
            ValuePart::Operator(Op::Plus),
            ValuePart::Number(NumberValue::from_scalar(3.0)),
            ValuePart::Operator(Op::RightParen),
            ValuePart::Operator(Op::Slash),
            ValuePart::Number(NumberValue::from_scalar(4.0)),
        ]).evaluate(&HashMap::new());
        assert_eq!(ValuePart::Number(NumberValue::computed(1.5)), answer);
    }

    #[test]
    fn it_does_string_concat_when_adding_to_list() {
        let answer = Evaluator::new(vec![
            ValuePart::Number(NumberValue::from_scalar(2.0)),
            ValuePart::Operator(Op::Plus),
            ValuePart::Operator(Op::LeftParen),
            ValuePart::Number(NumberValue::from_scalar(3.0)),
            ValuePart::Number(NumberValue::from_scalar(4.0)),
            ValuePart::Operator(Op::RightParen),
        ]).evaluate(&HashMap::new());

        assert_eq!(ValuePart::List(vec![
            ValuePart::String(Borrowed("23")),
            ValuePart::Number(NumberValue::from_scalar(4.0))
        ]), answer);
    }

    #[test]
    fn it_does_string_concat_when_adding_to_list_in_a_list() {
        let answer = Evaluator::new(vec![
            ValuePart::Operator(Op::LeftParen),
            ValuePart::Number(NumberValue::from_scalar(2.0)),
            ValuePart::Operator(Op::Plus),
            ValuePart::Operator(Op::LeftParen),
            ValuePart::Number(NumberValue::from_scalar(3.0)),
            ValuePart::Number(NumberValue::from_scalar(4.0)),
            ValuePart::Operator(Op::RightParen),
            ValuePart::Number(NumberValue::from_scalar(5.0)),
            ValuePart::Operator(Op::RightParen),
        ]).evaluate(&HashMap::new());

        assert_eq!(ValuePart::List(vec![
            ValuePart::String(Borrowed("23")),
            ValuePart::Number(NumberValue::from_scalar(4.0)),
            ValuePart::Number(NumberValue::from_scalar(5.0)),
        ]), answer);
    }

    #[test]
    fn it_divides_because_parens_and_string_concats_because_list() {
        let answer = Evaluator::new(vec![
            ValuePart::Number(NumberValue::from_scalar(1.0)),
            ValuePart::Operator(Op::Plus),
            ValuePart::Operator(Op::LeftParen),
            ValuePart::Number(NumberValue::from_scalar(5.0)),
            ValuePart::Operator(Op::Slash),
            ValuePart::Number(NumberValue::from_scalar(10.0)),
            ValuePart::Number(NumberValue::from_scalar(2.0)),
            ValuePart::Number(NumberValue::from_scalar(3.0)),
            ValuePart::Operator(Op::RightParen),
        ]).evaluate(&HashMap::new());

        assert_eq!(ValuePart::List(vec![
            ValuePart::String(Borrowed("10.5")),
            ValuePart::Number(NumberValue::from_scalar(2.0)),
            ValuePart::Number(NumberValue::from_scalar(3.0)),
        ]), answer);
    }

    #[test]
    fn it_does_not_divide_when_slash_is_separating() {
        let answer = Evaluator::new(vec![
            ValuePart::Number(NumberValue::from_scalar(15.0)),
            ValuePart::Operator(Op::Slash),
            ValuePart::Number(NumberValue::from_scalar(3.0)),
            ValuePart::Operator(Op::Slash),
            ValuePart::Number(NumberValue::from_scalar(5.0)),
        ]).evaluate(&HashMap::new());

        assert_eq!(ValuePart::List(vec![
            ValuePart::Number(NumberValue::from_scalar(15.0)),
            ValuePart::Operator(Op::Slash),
            ValuePart::Number(NumberValue::from_scalar(3.0)),
            ValuePart::Operator(Op::Slash),
            ValuePart::Number(NumberValue::from_scalar(5.0)),
        ]), answer);
    }

    #[test]
    fn it_does_divide_when_other_math_is_involved() {
        let answer = Evaluator::new(vec![
            ValuePart::Number(NumberValue::from_scalar(1.0)),
            ValuePart::Operator(Op::Slash),
            ValuePart::Number(NumberValue::from_scalar(2.0)),
            ValuePart::Operator(Op::Plus),
            ValuePart::Number(NumberValue::from_scalar(1.0)),
            ValuePart::Operator(Op::Slash),
            ValuePart::Number(NumberValue::from_scalar(2.0)),
        ]).evaluate(&HashMap::new());

        assert_eq!(ValuePart::Number(NumberValue::computed(1.0)), answer);
    }
}<|endoftext|>


<commit_before>use json::JsonValue;

use Year;
use Degree;
use Course;
use FromJson;
use get_json;

const base_url: &'static str = "https://www2.htw-dresden.de/~app/API/GetExams.php";

/// Returns a list of `Exam`s for a given student.
///
/// # Arguments
///
/// * `year` - Year
/// * `course` - Course identifier
/// * `degree` - Degree
///
/// # Example
///
/// ```
/// use htwdresden::Degree;
/// use htwdresden::exams::student_exams;
///
/// let exams = student_exams(2016, 121, Degree::Bachelor);
/// ```
pub fn student_exams(year: Year, course: Course, degree: Degree) -> Option<Vec<Exam>> {
    let url = format!("{base}?StgJhr={year}&Stg={course}&AbSc={degree}",
                      base = base_url,
                      year = year,
                      course = course,
                      degree = degree.short());

    let json = get_json(&url);
    let exams = Exam::mult_from_json(json);
    Some(exams)
}

/// Returns a list of `Exam`s for a given professor.
///
/// # Arguments
///
/// * `prof` - Professor name
///
/// # Example
///
/// ```
/// use htwdresden::exams::prof_exams;
///
/// let exams = prof_exams("Rennekamp");
/// ```
pub fn prof_exams(prof: &str) -> Option<Vec<Exam>> {
    let url = format!("{base}?Prof={prof}", base = base_url, prof = prof);
    let json = get_json(&url);
    let exams = Exam::mult_from_json(json);
    Some(exams)
}

#[derive(Debug)]
pub struct Exam {
    pub title: String,
    pub exam_type: String,
    pub study_branch: String,
    pub day: String,
    pub start_time: String,
    pub end_time: String,
    pub examiner: String,
    pub next_chance: String,
    pub rooms: Vec<String>,
}

impl FromJson for Exam {
    fn from_json(json: JsonValue) -> Exam {
        Exam {
            // wat o.O
            title: String::from(json["Title"].as_str().unwrap()),
            exam_type: String::from(json["ExamType"].as_str().unwrap()),
            study_branch: String::from(json["StudyBranch"].as_str().unwrap()),
            day: String::from(json["Day"].as_str().unwrap()),
            start_time: String::from(json["StartTime"].as_str().unwrap()),
            end_time: String::from(json["EndTime"].as_str().unwrap()),
            examiner: String::from(json["Examiner"].as_str().unwrap()),
            next_chance: String::from(json["NextChance"].as_str().unwrap()),
            rooms: vec![String::new()], // TODO
        }
    }
}
<commit_msg>rename BASE_URL<commit_after>use json::JsonValue;

use Year;
use Degree;
use Course;
use FromJson;
use get_json;

const BASE_URL: &'static str = "https://www2.htw-dresden.de/~app/API/GetExams.php";

/// Returns a list of `Exam`s for a given student.
///
/// # Arguments
///
/// * `year` - Year
/// * `course` - Course identifier
/// * `degree` - Degree
///
/// # Example
///
/// ```
/// use htwdresden::Degree;
/// use htwdresden::exams::student_exams;
///
/// let exams = student_exams(2016, 121, Degree::Bachelor);
/// ```
pub fn student_exams(year: Year, course: Course, degree: Degree) -> Option<Vec<Exam>> {
    let url = format!("{base}?StgJhr={year}&Stg={course}&AbSc={degree}",
                      base = BASE_URL,
                      year = year,
                      course = course,
                      degree = degree.short());

    let json = get_json(&url);
    let exams = Exam::mult_from_json(json);
    Some(exams)
}

/// Returns a list of `Exam`s for a given professor.
///
/// # Arguments
///
/// * `prof` - Professor name
///
/// # Example
///
/// ```
/// use htwdresden::exams::prof_exams;
///
/// let exams = prof_exams("Rennekamp");
/// ```
pub fn prof_exams(prof: &str) -> Option<Vec<Exam>> {
    let url = format!("{base}?Prof={prof}", base = BASE_URL, prof = prof);
    let json = get_json(&url);
    let exams = Exam::mult_from_json(json);
    Some(exams)
}

#[derive(Debug)]
pub struct Exam {
    pub title: String,
    pub exam_type: String,
    pub study_branch: String,
    pub day: String,
    pub start_time: String,
    pub end_time: String,
    pub examiner: String,
    pub next_chance: String,
    pub rooms: Vec<String>,
}

impl FromJson for Exam {
    fn from_json(json: JsonValue) -> Exam {
        Exam {
            // wat o.O
            title: String::from(json["Title"].as_str().unwrap()),
            exam_type: String::from(json["ExamType"].as_str().unwrap()),
            study_branch: String::from(json["StudyBranch"].as_str().unwrap()),
            day: String::from(json["Day"].as_str().unwrap()),
            start_time: String::from(json["StartTime"].as_str().unwrap()),
            end_time: String::from(json["EndTime"].as_str().unwrap()),
            examiner: String::from(json["Examiner"].as_str().unwrap()),
            next_chance: String::from(json["NextChance"].as_str().unwrap()),
            rooms: vec![String::new()], // TODO
        }
    }
}
<|endoftext|>


<commit_before>use std::time::{Duration, Instant};
use std::thread::sleep;

pub struct GameLoop {
    frame_number: u64,
    fps: u32
}

impl GameLoop {
    pub fn new(fps: u32) -> GameLoop {
        GameLoop {
            frame_number: 0,
            fps: fps
        }
    }

    pub fn run<F>(&mut self, fun: F)
        where F: Fn(u64) -> bool {  //FIXME: Could be an enum or something
        let ns_per_frame : Duration = Duration::new(0, 1_000_000_000 / self.fps);
        'running: loop {
            let start = Instant::now();

            if fun(self.frame_number) {
                break 'running;
            }
            // Framerate cap
            let next_render_step = start + ns_per_frame;
            let now = Instant::now();
            if next_render_step >= now {
                sleep(next_render_step - now);
            }
            self.frame_number += 1;
        }
    }
}
<commit_msg>Allow mutable callbacks in gameloop<commit_after>use std::time::{Duration, Instant};
use std::thread::sleep;

pub struct GameLoop {
    frame_number: u64,
    fps: u32
}

impl GameLoop {
    pub fn new(fps: u32) -> GameLoop {
        GameLoop {
            frame_number: 0,
            fps: fps
        }
    }

    pub fn run<F>(&mut self, mut fun: F)
        where F: FnMut(u64) -> bool {  //FIXME: Could be an enum or something
        let ns_per_frame : Duration = Duration::new(0, 1_000_000_000 / self.fps);
        'running: loop {
            let start = Instant::now();

            if fun(self.frame_number) {
                break 'running;
            }
            // Framerate cap
            let next_render_step = start + ns_per_frame;
            let now = Instant::now();
            if next_render_step >= now {
                sleep(next_render_step - now);
            }
            self.frame_number += 1;
        }
    }
}
<|endoftext|>


<commit_before>extern crate rand;
use rand::{Rng, SeedableRng, XorShiftRng};

pub const GRID_HEIGHT: i16 = 900;
pub const GRID_WIDTH: i16 = 1440;

pub const BLOCK_SIZE: i16 = 1;

pub const WINDOW_HEIGHT: i16 = GRID_HEIGHT * BLOCK_SIZE;
pub const WINDOW_WIDTH: i16 = GRID_WIDTH * BLOCK_SIZE;

pub const RAIN_SPARSENESS: i16 = 5;

pub const X: bool = true;
pub const o: bool = false;

#[derive(Eq, PartialEq, Clone)]
pub struct Loc {
    pub x: i16,
    pub y: i16
}

pub struct Map {pub map: Box<[[bool; GRID_HEIGHT as usize]; GRID_WIDTH as usize]>}

pub struct GameState {
    pub particles: Vec<Loc>,
    pub obstacles: Vec<Loc>,
    pub indexes_to_remove: Vec<usize>,
    pub map: Map, //2D array to check occupancy
    pub max_x: i16,
    pub max_y: i16,
    pub rng: XorShiftRng
}

impl Map {
    fn is_occupied(&self, x: i16, y: i16) -> bool {
        //Not valid is not occupied (false) to allow particles to leave the grid.
        //To prevent double bound checking with GameState::is_valid(), unwrap get()
        //and return defailt false on None
        if let Some(arr) = self.map.get(x as usize) {
            *arr.get(y as usize).unwrap_or(&false)
        } else {
            false
        }
    }

    fn get_neighbours(&mut self, x: i16, y: i16) -> [bool; 8] {
        [self.is_occupied(x-1, y-1), self.is_occupied(x, y-1), self.is_occupied(x+1, y-1),
        self.is_occupied(x-1, y), self.is_occupied(x+1, y),
        self.is_occupied(x-1, y+1), self.is_occupied(x, y+1), self.is_occupied(x-1, y+1)]
    }

    fn add_coord_map(&mut self, x: i16, y: i16) {
        self.map[x as usize][y as usize] = true;
    }

    fn remove_coord_map(&mut self, x: i16, y: i16) {
        self.map[x as usize][y as usize] = false;
    }
}

impl GameState {
    //check if point in inside the grid
    pub fn is_valid(x: i16, y: i16) -> bool {
        if x<0 || x>=GRID_WIDTH || y<0 || y>=GRID_HEIGHT {false} else {true}
    }

    pub fn update(&mut self) {
        //MAIN LOGIC
        for (index, particle) in self.particles.iter_mut().enumerate() {
            let (x, y) = (particle.x, particle.y);
            let neighbours = self.map.get_neighbours(x, y);
            let (x_new, y_new) = match neighbours {
               /*above  side below*/
                [_,_,_, _,_, _,o,_] => ( x, y+1 ),
                [_,X,_, o,X, _,X,_] => ( x-1, y ),
                [_,X,_, X,o, _,X,_] => ( x+1, y ),
                [_,X,_, o,o, _,X,_] => ( if y&2 == 0 {x-1} else {x+1}, y ), //deterministic choice
                //[_,X,_, o,o, _,X,_] => ( if self.rng.gen::<i16>()&2 == 0 {x-1} else {x+1}, y ), //random choice
                                        _ => continue,
            };
            if GameState::is_valid(x_new, y_new) {
                self.map.remove_coord_map(x, y);
                self.map.add_coord_map(x_new, y_new);
                *particle = Loc {x: x_new, y: y_new};
            } else {
                self.map.map[x as usize][y as usize] = false;
                self.indexes_to_remove.push(index); //prepare list of particles to remove
            }
        }
        //must use .rev(), otherwise you can't remove the last element
        for index in self.indexes_to_remove.iter().rev() {
            self.particles.swap_remove(*index);
        }
        self.indexes_to_remove.clear();
    }//end update

    pub fn rain(&mut self) {
        for x in 1*(GRID_WIDTH/20)..19*(GRID_WIDTH/20) {
            if !self.map.is_occupied(x, 0) && self.rng.gen::<i16>()&RAIN_SPARSENESS == 0 {
                self.particles.push(Loc {x: x, y: 0});
                self.map.add_coord_map(x, 0);
            }
        }
    }

    fn remove_particle_list (&mut self, x: i16, y: i16) {
        let loc = Loc {x: x, y: y};
        self.particles.retain(|p|{ p != &loc});
    }

    fn remove_obstacle_list (&mut self, x: i16, y: i16) {
        let loc = Loc {x: x, y: y};
        self.obstacles.retain(|p|{ p != &loc});
    }

    fn add_particle (&mut self, x: i16, y: i16) {
        self.map.add_coord_map(x, y);
        let loc = Loc {x: x, y: y};
        self.particles.push(loc);
    }

    fn add_obstacle (&mut self, x: i16, y: i16) {
        self.map.add_coord_map(x, y);
        let loc = Loc {x: x, y: y};
        self.obstacles.push(loc);
    }

    fn remove_coord(&mut self, x: i16, y: i16) {
        self.map.remove_coord_map(x, y);
        self.remove_obstacle_list(x, y);
        self.remove_particle_list(x, y);
    }

    pub fn remove_square (&mut self, ux: i16, uy: i16, dx: i16, dy: i16) {
        if Self::is_valid(ux + dx, uy + dy) {
            for x in ux..ux+dx {
                for y in uy..uy+dy {
                    if self.map.is_occupied(x, y) {
                     self.remove_coord(x, y);
                    }
                }
            }
        }
    }

    pub fn paint_square_obstacles (&mut self, ux: i16, uy: i16, dx: i16, dy: i16) {
        if Self::is_valid(ux + dx, uy + dy) {
            for x in ux..ux+dx {
                for y in uy..uy+dy {
                    if !self.map.is_occupied(x, y) {
                        self.map.add_coord_map(x, y);
                        self.add_obstacle(x, y);
                    }
                }
            }
        }
    }

    pub fn new() -> GameState {
        GameState {
                particles: Vec::with_capacity(10_000),
                obstacles: Vec::with_capacity(10_000),
                indexes_to_remove: Vec::with_capacity(1000),
                map: Map { map: box [[false; GRID_HEIGHT as usize]; GRID_WIDTH as usize] },
                max_x: GRID_WIDTH,
                max_y: GRID_HEIGHT,
                rng: SeedableRng::from_seed([1, 2, 3, 4])
            }
    }

}//end impl GameState
<commit_msg>Speedup coord removal<commit_after>extern crate rand;
use rand::{Rng, SeedableRng, XorShiftRng};

pub const GRID_HEIGHT: i16 = 900;
pub const GRID_WIDTH: i16 = 1440;

pub const BLOCK_SIZE: i16 = 1;

pub const WINDOW_HEIGHT: i16 = GRID_HEIGHT * BLOCK_SIZE;
pub const WINDOW_WIDTH: i16 = GRID_WIDTH * BLOCK_SIZE;

pub const RAIN_SPARSENESS: i16 = 5;

pub const X: bool = true;
pub const o: bool = false;

#[derive(Eq, PartialEq, Clone)]
pub struct Loc {
    pub x: i16,
    pub y: i16
}

pub struct Map {pub map: Box<[[bool; GRID_HEIGHT as usize]; GRID_WIDTH as usize]>}

pub struct GameState {
    pub particles: Vec<Loc>,
    pub obstacles: Vec<Loc>,
    pub indexes_to_remove: Vec<usize>,
    pub map: Map, //2D array to check occupancy
    pub max_x: i16,
    pub max_y: i16,
    pub rng: XorShiftRng
}

impl Map {
    fn is_occupied(&self, x: i16, y: i16) -> bool {
        //Not valid is not occupied (false) to allow particles to leave the grid.
        //To prevent double bound checking with GameState::is_valid(), unwrap get()
        //and return defailt false on None
        if let Some(arr) = self.map.get(x as usize) {
            *arr.get(y as usize).unwrap_or(&false)
        } else {
            false
        }
    }

    fn get_neighbours(&mut self, x: i16, y: i16) -> [bool; 8] {
        [self.is_occupied(x-1, y-1), self.is_occupied(x, y-1), self.is_occupied(x+1, y-1),
        self.is_occupied(x-1, y), self.is_occupied(x+1, y),
        self.is_occupied(x-1, y+1), self.is_occupied(x, y+1), self.is_occupied(x-1, y+1)]
    }

    fn add_coord_map(&mut self, x: i16, y: i16) {
        self.map[x as usize][y as usize] = true;
    }

    fn remove_coord_map(&mut self, x: i16, y: i16) {
        self.map[x as usize][y as usize] = false;
    }
}

impl GameState {
    //check if point in inside the grid
    pub fn is_valid(x: i16, y: i16) -> bool {
        if x<0 || x>=GRID_WIDTH || y<0 || y>=GRID_HEIGHT {false} else {true}
    }

    pub fn update(&mut self) {
        //MAIN LOGIC
        for (index, particle) in self.particles.iter_mut().enumerate() {
            let (x, y) = (particle.x, particle.y);
            let neighbours = self.map.get_neighbours(x, y);
            let (x_new, y_new) = match neighbours {
               /*above  side below*/
                [_,_,_, _,_, _,o,_] => ( x, y+1 ),
                [_,X,_, o,X, _,X,_] => ( x-1, y ),
                [_,X,_, X,o, _,X,_] => ( x+1, y ),
                [_,X,_, o,o, _,X,_] => ( if y&2 == 0 {x-1} else {x+1}, y ), //deterministic choice
                //[_,X,_, o,o, _,X,_] => ( if self.rng.gen::<i16>()&2 == 0 {x-1} else {x+1}, y ), //random choice
                                        _ => continue,
            };
            if GameState::is_valid(x_new, y_new) {
                self.map.remove_coord_map(x, y);
                self.map.add_coord_map(x_new, y_new);
                *particle = Loc {x: x_new, y: y_new};
            } else {
                self.map.map[x as usize][y as usize] = false;
                self.indexes_to_remove.push(index); //prepare list of particles to remove
            }
        }
        //must use .rev(), otherwise you can't remove the last element
        for index in self.indexes_to_remove.iter().rev() {
            self.particles.swap_remove(*index);
        }
        self.indexes_to_remove.clear();
    }//end update

    pub fn rain(&mut self) {
        for x in 1*(GRID_WIDTH/20)..19*(GRID_WIDTH/20) {
            if !self.map.is_occupied(x, 0) && self.rng.gen::<i16>()&RAIN_SPARSENESS == 0 {
                self.particles.push(Loc {x: x, y: 0});
                self.map.add_coord_map(x, 0);
            }
        }
    }

    fn remove_particle_list (&mut self, x: i16, y: i16) {
        remove_indices_matching_loc(&mut self.particles,
                                    &mut self.indexes_to_remove,
                                    Loc {x: x, y: y})
    }

    fn remove_obstacle_list (&mut self, x: i16, y: i16) {
        remove_indices_matching_loc(&mut self.obstacles,
                                    &mut self.indexes_to_remove,
                                    Loc {x: x, y: y})
    }

    fn add_particle (&mut self, x: i16, y: i16) {
        self.map.add_coord_map(x, y);
        let loc = Loc {x: x, y: y};
        self.particles.push(loc);
    }

    fn add_obstacle (&mut self, x: i16, y: i16) {
        self.map.add_coord_map(x, y);
        let loc = Loc {x: x, y: y};
        self.obstacles.push(loc);
    }

    fn remove_coord(&mut self, x: i16, y: i16) {
        self.map.remove_coord_map(x, y);
        self.remove_obstacle_list(x, y);
        self.remove_particle_list(x, y);
    }

    pub fn remove_square (&mut self, ux: i16, uy: i16, dx: i16, dy: i16) {
        if Self::is_valid(ux + dx, uy + dy) {
            for x in ux..ux+dx {
                for y in uy..uy+dy {
                    if self.map.is_occupied(x, y) {
                        self.remove_coord(x, y);
                    }
                }
            }
        }
    }

    pub fn paint_square_obstacles (&mut self, ux: i16, uy: i16, dx: i16, dy: i16) {
        if Self::is_valid(ux + dx, uy + dy) {
            for x in ux..ux+dx {
                for y in uy..uy+dy {
                    if !self.map.is_occupied(x, y) {
                        self.map.add_coord_map(x, y);
                        self.add_obstacle(x, y);
                    }
                }
            }
        }
    }

    pub fn new() -> GameState {
        GameState {
                particles: Vec::with_capacity(10_000),
                obstacles: Vec::with_capacity(10_000),
                indexes_to_remove: Vec::with_capacity(1000),
                map: Map { map: box [[false; GRID_HEIGHT as usize]; GRID_WIDTH as usize] },
                max_x: GRID_WIDTH,
                max_y: GRID_HEIGHT,
                rng: SeedableRng::from_seed([1, 2, 3, 4])
            }
    }

}//end impl GameState

fn remove_indices_matching_loc(items: &mut Vec<Loc>,
                               indexes_to_remove: &mut Vec<usize>,
                               loc: Loc) {
    {
        let mut it = items.iter();

        while let Some(index) = it.position(|p| p == &loc) {
            indexes_to_remove.push(index);
        }
    }

    for index in indexes_to_remove.iter().rev() {
        items.swap_remove(*index);
    }

    indexes_to_remove.clear();
}<|endoftext|>


<commit_before>//! Functions for computing gradients of image intensities.

use crate::definitions::{HasBlack, Image};
use crate::filter::filter3x3;
use crate::map::{ChannelMap, WithChannel};
use image::{GenericImage, GenericImageView, GrayImage, Luma, Pixel};
use itertools::multizip;

/// Sobel filter for detecting vertical gradients.
///
/// Used by the [`vertical_sobel`](fn.vertical_sobel.html) function.
#[rustfmt::skip]
pub static VERTICAL_SOBEL: [i32; 9] = [
    -1, -2, -1,
     0,  0,  0,
     1,  2,  1];

/// Sobel filter for detecting horizontal gradients.
///
/// Used by the [`horizontal_sobel`](fn.horizontal_sobel.html) function.
#[rustfmt::skip]
pub static HORIZONTAL_SOBEL: [i32; 9] = [
    -1, 0, 1,
    -2, 0, 2,
    -1, 0, 1];

/// Scharr filter for detecting vertical gradients.
///
/// Used by the [`vertical_scharr`](fn.vertical_scharr.html) function.
#[rustfmt::skip]
pub static VERTICAL_SCHARR: [i32; 9] = [
    -3, -10,  -3,
     0,   0,   0,
     3,  10,   3];

/// Scharr filter for detecting horizontal gradients.
///
/// Used by the [`horizontal_scharr`](fn.horizontal_scharr.html) function.
#[rustfmt::skip]
pub static HORIZONTAL_SCHARR: [i32; 9] = [
     -3,  0,   3,
    -10,  0,  10,
     -3,  0,   3];

/// Prewitt filter for detecting vertical gradients.
///
/// Used by the [`vertical_prewitt`](fn.vertical_prewitt.html) function.
#[rustfmt::skip]
pub static VERTICAL_PREWITT: [i32; 9] = [
    -1, -1, -1,
     0,  0,  0,
     1,  1,  1];

/// Prewitt filter for detecting horizontal gradients.
///
/// Used by the [`horizontal_prewitt`](fn.horizontal_prewitt.html) function.
#[rustfmt::skip]
pub static HORIZONTAL_PREWITT: [i32; 9] = [
    -1, 0, 1,
    -1, 0, 1,
    -1, 0, 1];

/// Convolves an image with the [`HORIZONTAL_SOBEL`](static.HORIZONTAL_SOBEL.html)
/// kernel to detect horizontal gradients.
pub fn horizontal_sobel(image: &GrayImage) -> Image<Luma<i16>> {
    filter3x3(image, &HORIZONTAL_SOBEL)
}

/// Convolves an image with the [`VERTICAL_SOBEL`](static.VERTICAL_SOBEL.html)
/// kernel to detect vertical gradients.
pub fn vertical_sobel(image: &GrayImage) -> Image<Luma<i16>> {
    filter3x3(image, &VERTICAL_SOBEL)
}

/// Convolves an image with the [`HORIZONTAL_SCHARR`](static.HORIZONTAL_SCHARR.html)
/// kernel to detect horizontal gradients.
pub fn horizontal_scharr(image: &GrayImage) -> Image<Luma<i16>> {
    filter3x3(image, &HORIZONTAL_SCHARR)
}

/// Convolves an image with the [`VERTICAL_SCHARR`](static.VERTICAL_SCHARR.html)
/// kernel to detect vertical gradients.
pub fn vertical_scharr(image: &GrayImage) -> Image<Luma<i16>> {
    filter3x3(image, &VERTICAL_SCHARR)
}

/// Convolves an image with the [`HORIZONTAL_PREWITT`](static.HORIZONTAL_PREWITT.html)
/// kernel to detect horizontal gradients.
pub fn horizontal_prewitt(image: &GrayImage) -> Image<Luma<i16>> {
    filter3x3(image, &HORIZONTAL_PREWITT)
}

/// Convolves an image with the [`VERTICAL_PREWITT`](static.VERTICAL_PREWITT.html)
/// kernel to detect vertical gradients.
pub fn vertical_prewitt(image: &GrayImage) -> Image<Luma<i16>> {
    filter3x3(image, &VERTICAL_PREWITT)
}

/// Returns the magnitudes of gradients in an image using Sobel filters.
pub fn sobel_gradients(image: &GrayImage) -> Image<Luma<u16>> {
    gradients(image, &HORIZONTAL_SOBEL, &VERTICAL_SOBEL, |p| p)
}

/// Computes per-channel gradients using Sobel filters and calls `f`
/// to compute each output pixel.
///
/// # Examples
/// ```
/// # extern crate image;
/// # #[macro_use]
/// # extern crate imageproc;
/// # fn main() {
/// use imageproc::gradients::{sobel_gradient_map};
/// use image::Luma;
/// use std::cmp;
///
/// // A shallow horizontal gradient in the red
/// // channel, a steeper vertical gradient in the
/// // blue channel, constant in the green channel.
/// let input = rgb_image!(
///     [0, 5, 0], [1, 5, 0], [2, 5, 0];
///     [0, 5, 2], [1, 5, 2], [2, 5, 2];
///     [0, 5, 4], [1, 5, 4], [2, 5, 4]
/// );
///
/// // Computing independent per-channel gradients.
/// let channel_gradient = rgb_image!(type: u16,
///     [ 4,  0,  8], [ 8,  0,  8], [ 4,  0,  8];
///     [ 4,  0, 16], [ 8,  0, 16], [ 4,  0, 16];
///     [ 4,  0,  8], [ 8,  0,  8], [ 4,  0,  8]
/// );
///
/// assert_pixels_eq!(
///     sobel_gradient_map(&input, |p| p),
///     channel_gradient
/// );
///
/// // Defining the gradient of an RGB image to be the
/// // mean of its per-channel gradients.
/// let mean_gradient = gray_image!(type: u16,
///     4, 5, 4;
///     6, 8, 6;
///     4, 5, 4
/// );
///
/// assert_pixels_eq!(
///     sobel_gradient_map(&input, |p| {
///         let mean = (p[0] + p[1] + p[2]) / 3;
///         Luma([mean])
///     }),
///     mean_gradient
/// );
///
/// // Defining the gradient of an RGB image to be the pixelwise
/// // maximum of its per-channel gradients.
/// let max_gradient = gray_image!(type: u16,
///      8,  8,  8;
///     16, 16, 16;
///      8,  8,  8
/// );
///
/// assert_pixels_eq!(
///     sobel_gradient_map(&input, |p| {
///         let max = cmp::max(cmp::max(p[0], p[1]), p[2]);
///         Luma([max])
///     }),
///     max_gradient
/// );
/// # }
pub fn sobel_gradient_map<P, F, Q>(image: &Image<P>, f: F) -> Image<Q>
where
    P: Pixel<Subpixel = u8> + WithChannel<u16> + WithChannel<i16> + 'static,
    Q: Pixel + 'static,
    ChannelMap<P, u16>: HasBlack,
    F: Fn(ChannelMap<P, u16>) -> Q,
{
    gradients(image, &HORIZONTAL_SOBEL, &VERTICAL_SOBEL, f)
}

/// Returns the magnitudes of gradients in an image using Prewitt filters.
pub fn prewitt_gradients(image: &GrayImage) -> Image<Luma<u16>> {
    gradients(image, &HORIZONTAL_PREWITT, &VERTICAL_PREWITT, |p| p)
}

// TODO: Returns directions as well as magnitudes.
// TODO: Support filtering without allocating a fresh image - filtering functions could
// TODO: take some kind of pixel-sink. This would allow us to compute gradient magnitudes
// TODO: and directions without allocating intermediates for vertical and horizontal gradients.
fn gradients<P, F, Q>(
    image: &Image<P>,
    horizontal_kernel: &[i32; 9],
    vertical_kernel: &[i32; 9],
    f: F,
) -> Image<Q>
where
    P: Pixel<Subpixel = u8> + WithChannel<u16> + WithChannel<i16> + 'static,
    Q: Pixel + 'static,
    ChannelMap<P, u16>: HasBlack,
    F: Fn(ChannelMap<P, u16>) -> Q,
{
    let horizontal: Image<ChannelMap<P, i16>> = filter3x3::<_, _, i16>(image, horizontal_kernel);
    let vertical: Image<ChannelMap<P, i16>> = filter3x3::<_, _, i16>(image, vertical_kernel);

    let (width, height) = image.dimensions();
    let mut out = Image::<Q>::new(width, height);

    // This would be more concise using itertools::multizip over image pixels, but that increased runtime by around 20%
    for y in 0..height {
        for x in 0..width {
            unsafe {
                let h = horizontal.unsafe_get_pixel(x, y);
                let v = vertical.unsafe_get_pixel(x, y);
                let mut p = ChannelMap::<P, u16>::black();

                for (h, v, p) in multizip((h.channels(), v.channels(), p.channels_mut())) {
                    *p = gradient_magnitude(*h as f32, *v as f32);
                }

                out.unsafe_put_pixel(x, y, f(p));
            }
        }
    }

    out
}

#[inline]
fn gradient_magnitude(dx: f32, dy: f32) -> u16 {
    (dx.powi(2) + dy.powi(2)).sqrt() as u16
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::utils::gray_bench_image;
    use image::{ImageBuffer, Luma};
    use test::{black_box, Bencher};

    #[rustfmt::skip::macros(gray_image)]
    #[test]
    fn test_gradients_constant_image() {
        let image = ImageBuffer::from_pixel(5, 5, Luma([15u8]));
        let expected = ImageBuffer::from_pixel(5, 5, Luma([0i16]));
        assert_pixels_eq!(horizontal_sobel(&image), expected);
        assert_pixels_eq!(vertical_sobel(&image), expected);
        assert_pixels_eq!(horizontal_scharr(&image), expected);
        assert_pixels_eq!(vertical_scharr(&image), expected);
        assert_pixels_eq!(horizontal_prewitt(&image), expected);
        assert_pixels_eq!(vertical_prewitt(&image), expected);
    }

    #[test]
    fn test_horizontal_sobel_gradient_image() {
        let image = gray_image!(
            3, 2, 1;
            6, 5, 4;
            9, 8, 7);

        let expected = gray_image!(type: i16,
            -4, -8, -4;
            -4, -8, -4;
            -4, -8, -4);

        let filtered = horizontal_sobel(&image);
        assert_pixels_eq!(filtered, expected);
    }

    #[test]
    fn test_vertical_sobel_gradient_image() {
        let image = gray_image!(
            3, 6, 9;
            2, 5, 8;
            1, 4, 7);

        let expected = gray_image!(type: i16,
            -4, -4, -4;
            -8, -8, -8;
            -4, -4, -4);

        let filtered = vertical_sobel(&image);
        assert_pixels_eq!(filtered, expected);
    }

    #[test]
    fn test_horizontal_scharr_gradient_image() {
        let image = gray_image!(
            3, 2, 1;
            6, 5, 4;
            9, 8, 7);

        let expected = gray_image!(type: i16,
            -16, -32, -16;
            -16, -32, -16;
            -16, -32, -16);

        let filtered = horizontal_scharr(&image);
        assert_pixels_eq!(filtered, expected);
    }

    #[test]
    fn test_vertical_scharr_gradient_image() {
        let image = gray_image!(
            3, 6, 9;
            2, 5, 8;
            1, 4, 7);

        let expected = gray_image!(type: i16,
            -16, -16, -16;
            -32, -32, -32;
            -16, -16, -16);

        let filtered = vertical_scharr(&image);
        assert_pixels_eq!(filtered, expected);
    }

    #[test]
    fn test_horizontal_prewitt_gradient_image() {
        let image = gray_image!(
            3, 2, 1;
            6, 5, 4;
            9, 8, 7);

        let expected = gray_image!(type: i16,
            -3, -6, -3;
            -3, -6, -3;
            -3, -6, -3);

        let filtered = horizontal_prewitt(&image);
        assert_pixels_eq!(filtered, expected);
    }

    #[test]
    fn test_vertical_prewitt_gradient_image() {
        let image = gray_image!(
            3, 6, 9;
            2, 5, 8;
            1, 4, 7);

        let expected = gray_image!(type: i16,
            -3, -3, -3;
            -6, -6, -6;
            -3, -3, -3);

        let filtered = vertical_prewitt(&image);
        assert_pixels_eq!(filtered, expected);
    }

    #[bench]
    fn bench_sobel_gradients(b: &mut Bencher) {
        let image = gray_bench_image(500, 500);
        b.iter(|| {
            let gradients = sobel_gradients(&image);
            black_box(gradients);
        });
    }
}
<commit_msg>Add comment justifying use of unsafe in gradients (#373)<commit_after>//! Functions for computing gradients of image intensities.

use crate::definitions::{HasBlack, Image};
use crate::filter::filter3x3;
use crate::map::{ChannelMap, WithChannel};
use image::{GenericImage, GenericImageView, GrayImage, Luma, Pixel};
use itertools::multizip;

/// Sobel filter for detecting vertical gradients.
///
/// Used by the [`vertical_sobel`](fn.vertical_sobel.html) function.
#[rustfmt::skip]
pub static VERTICAL_SOBEL: [i32; 9] = [
    -1, -2, -1,
     0,  0,  0,
     1,  2,  1];

/// Sobel filter for detecting horizontal gradients.
///
/// Used by the [`horizontal_sobel`](fn.horizontal_sobel.html) function.
#[rustfmt::skip]
pub static HORIZONTAL_SOBEL: [i32; 9] = [
    -1, 0, 1,
    -2, 0, 2,
    -1, 0, 1];

/// Scharr filter for detecting vertical gradients.
///
/// Used by the [`vertical_scharr`](fn.vertical_scharr.html) function.
#[rustfmt::skip]
pub static VERTICAL_SCHARR: [i32; 9] = [
    -3, -10,  -3,
     0,   0,   0,
     3,  10,   3];

/// Scharr filter for detecting horizontal gradients.
///
/// Used by the [`horizontal_scharr`](fn.horizontal_scharr.html) function.
#[rustfmt::skip]
pub static HORIZONTAL_SCHARR: [i32; 9] = [
     -3,  0,   3,
    -10,  0,  10,
     -3,  0,   3];

/// Prewitt filter for detecting vertical gradients.
///
/// Used by the [`vertical_prewitt`](fn.vertical_prewitt.html) function.
#[rustfmt::skip]
pub static VERTICAL_PREWITT: [i32; 9] = [
    -1, -1, -1,
     0,  0,  0,
     1,  1,  1];

/// Prewitt filter for detecting horizontal gradients.
///
/// Used by the [`horizontal_prewitt`](fn.horizontal_prewitt.html) function.
#[rustfmt::skip]
pub static HORIZONTAL_PREWITT: [i32; 9] = [
    -1, 0, 1,
    -1, 0, 1,
    -1, 0, 1];

/// Convolves an image with the [`HORIZONTAL_SOBEL`](static.HORIZONTAL_SOBEL.html)
/// kernel to detect horizontal gradients.
pub fn horizontal_sobel(image: &GrayImage) -> Image<Luma<i16>> {
    filter3x3(image, &HORIZONTAL_SOBEL)
}

/// Convolves an image with the [`VERTICAL_SOBEL`](static.VERTICAL_SOBEL.html)
/// kernel to detect vertical gradients.
pub fn vertical_sobel(image: &GrayImage) -> Image<Luma<i16>> {
    filter3x3(image, &VERTICAL_SOBEL)
}

/// Convolves an image with the [`HORIZONTAL_SCHARR`](static.HORIZONTAL_SCHARR.html)
/// kernel to detect horizontal gradients.
pub fn horizontal_scharr(image: &GrayImage) -> Image<Luma<i16>> {
    filter3x3(image, &HORIZONTAL_SCHARR)
}

/// Convolves an image with the [`VERTICAL_SCHARR`](static.VERTICAL_SCHARR.html)
/// kernel to detect vertical gradients.
pub fn vertical_scharr(image: &GrayImage) -> Image<Luma<i16>> {
    filter3x3(image, &VERTICAL_SCHARR)
}

/// Convolves an image with the [`HORIZONTAL_PREWITT`](static.HORIZONTAL_PREWITT.html)
/// kernel to detect horizontal gradients.
pub fn horizontal_prewitt(image: &GrayImage) -> Image<Luma<i16>> {
    filter3x3(image, &HORIZONTAL_PREWITT)
}

/// Convolves an image with the [`VERTICAL_PREWITT`](static.VERTICAL_PREWITT.html)
/// kernel to detect vertical gradients.
pub fn vertical_prewitt(image: &GrayImage) -> Image<Luma<i16>> {
    filter3x3(image, &VERTICAL_PREWITT)
}

/// Returns the magnitudes of gradients in an image using Sobel filters.
pub fn sobel_gradients(image: &GrayImage) -> Image<Luma<u16>> {
    gradients(image, &HORIZONTAL_SOBEL, &VERTICAL_SOBEL, |p| p)
}

/// Computes per-channel gradients using Sobel filters and calls `f`
/// to compute each output pixel.
///
/// # Examples
/// ```
/// # extern crate image;
/// # #[macro_use]
/// # extern crate imageproc;
/// # fn main() {
/// use imageproc::gradients::{sobel_gradient_map};
/// use image::Luma;
/// use std::cmp;
///
/// // A shallow horizontal gradient in the red
/// // channel, a steeper vertical gradient in the
/// // blue channel, constant in the green channel.
/// let input = rgb_image!(
///     [0, 5, 0], [1, 5, 0], [2, 5, 0];
///     [0, 5, 2], [1, 5, 2], [2, 5, 2];
///     [0, 5, 4], [1, 5, 4], [2, 5, 4]
/// );
///
/// // Computing independent per-channel gradients.
/// let channel_gradient = rgb_image!(type: u16,
///     [ 4,  0,  8], [ 8,  0,  8], [ 4,  0,  8];
///     [ 4,  0, 16], [ 8,  0, 16], [ 4,  0, 16];
///     [ 4,  0,  8], [ 8,  0,  8], [ 4,  0,  8]
/// );
///
/// assert_pixels_eq!(
///     sobel_gradient_map(&input, |p| p),
///     channel_gradient
/// );
///
/// // Defining the gradient of an RGB image to be the
/// // mean of its per-channel gradients.
/// let mean_gradient = gray_image!(type: u16,
///     4, 5, 4;
///     6, 8, 6;
///     4, 5, 4
/// );
///
/// assert_pixels_eq!(
///     sobel_gradient_map(&input, |p| {
///         let mean = (p[0] + p[1] + p[2]) / 3;
///         Luma([mean])
///     }),
///     mean_gradient
/// );
///
/// // Defining the gradient of an RGB image to be the pixelwise
/// // maximum of its per-channel gradients.
/// let max_gradient = gray_image!(type: u16,
///      8,  8,  8;
///     16, 16, 16;
///      8,  8,  8
/// );
///
/// assert_pixels_eq!(
///     sobel_gradient_map(&input, |p| {
///         let max = cmp::max(cmp::max(p[0], p[1]), p[2]);
///         Luma([max])
///     }),
///     max_gradient
/// );
/// # }
pub fn sobel_gradient_map<P, F, Q>(image: &Image<P>, f: F) -> Image<Q>
where
    P: Pixel<Subpixel = u8> + WithChannel<u16> + WithChannel<i16> + 'static,
    Q: Pixel + 'static,
    ChannelMap<P, u16>: HasBlack,
    F: Fn(ChannelMap<P, u16>) -> Q,
{
    gradients(image, &HORIZONTAL_SOBEL, &VERTICAL_SOBEL, f)
}

/// Returns the magnitudes of gradients in an image using Prewitt filters.
pub fn prewitt_gradients(image: &GrayImage) -> Image<Luma<u16>> {
    gradients(image, &HORIZONTAL_PREWITT, &VERTICAL_PREWITT, |p| p)
}

// TODO: Returns directions as well as magnitudes.
// TODO: Support filtering without allocating a fresh image - filtering functions could
// TODO: take some kind of pixel-sink. This would allow us to compute gradient magnitudes
// TODO: and directions without allocating intermediates for vertical and horizontal gradients.
fn gradients<P, F, Q>(
    image: &Image<P>,
    horizontal_kernel: &[i32; 9],
    vertical_kernel: &[i32; 9],
    f: F,
) -> Image<Q>
where
    P: Pixel<Subpixel = u8> + WithChannel<u16> + WithChannel<i16> + 'static,
    Q: Pixel + 'static,
    ChannelMap<P, u16>: HasBlack,
    F: Fn(ChannelMap<P, u16>) -> Q,
{
    let horizontal: Image<ChannelMap<P, i16>> = filter3x3::<_, _, i16>(image, horizontal_kernel);
    let vertical: Image<ChannelMap<P, i16>> = filter3x3::<_, _, i16>(image, vertical_kernel);

    let (width, height) = image.dimensions();
    let mut out = Image::<Q>::new(width, height);

    // This would be more concise using itertools::multizip over image pixels, but that increased runtime by around 20%
    for y in 0..height {
        for x in 0..width {
            // JUSTIFICATION
            //  Benefit
            //      Using checked indexing here makes this sobel_gradients 1.1x slower,
            //      as measured by bench_sobel_gradients
            //  Correctness
            //      x and y are in bounds for image by construction,
            //      vertical and horizontal are the result of calling filter3x3 on image,
            //      and filter3x3 returns an image of the same size as its input
            let (h, v) = unsafe {
                (
                    horizontal.unsafe_get_pixel(x, y),
                    vertical.unsafe_get_pixel(x, y),
                )
            };
            let mut p = ChannelMap::<P, u16>::black();

            for (h, v, p) in multizip((h.channels(), v.channels(), p.channels_mut())) {
                *p = gradient_magnitude(*h as f32, *v as f32);
            }

            // JUSTIFICATION
            //  Benefit
            //      Using checked indexing here makes this sobel_gradients 1.1x slower,
            //      as measured by bench_sobel_gradients
            //  Correctness
            //      x and y are in bounds for image by construction,
            //      and out has the same dimensions
            unsafe {
                out.unsafe_put_pixel(x, y, f(p));
            }
        }
    }

    out
}

#[inline]
fn gradient_magnitude(dx: f32, dy: f32) -> u16 {
    (dx.powi(2) + dy.powi(2)).sqrt() as u16
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::utils::gray_bench_image;
    use image::{ImageBuffer, Luma};
    use test::{black_box, Bencher};

    #[rustfmt::skip::macros(gray_image)]
    #[test]
    fn test_gradients_constant_image() {
        let image = ImageBuffer::from_pixel(5, 5, Luma([15u8]));
        let expected = ImageBuffer::from_pixel(5, 5, Luma([0i16]));
        assert_pixels_eq!(horizontal_sobel(&image), expected);
        assert_pixels_eq!(vertical_sobel(&image), expected);
        assert_pixels_eq!(horizontal_scharr(&image), expected);
        assert_pixels_eq!(vertical_scharr(&image), expected);
        assert_pixels_eq!(horizontal_prewitt(&image), expected);
        assert_pixels_eq!(vertical_prewitt(&image), expected);
    }

    #[test]
    fn test_horizontal_sobel_gradient_image() {
        let image = gray_image!(
            3, 2, 1;
            6, 5, 4;
            9, 8, 7);

        let expected = gray_image!(type: i16,
            -4, -8, -4;
            -4, -8, -4;
            -4, -8, -4);

        let filtered = horizontal_sobel(&image);
        assert_pixels_eq!(filtered, expected);
    }

    #[test]
    fn test_vertical_sobel_gradient_image() {
        let image = gray_image!(
            3, 6, 9;
            2, 5, 8;
            1, 4, 7);

        let expected = gray_image!(type: i16,
            -4, -4, -4;
            -8, -8, -8;
            -4, -4, -4);

        let filtered = vertical_sobel(&image);
        assert_pixels_eq!(filtered, expected);
    }

    #[test]
    fn test_horizontal_scharr_gradient_image() {
        let image = gray_image!(
            3, 2, 1;
            6, 5, 4;
            9, 8, 7);

        let expected = gray_image!(type: i16,
            -16, -32, -16;
            -16, -32, -16;
            -16, -32, -16);

        let filtered = horizontal_scharr(&image);
        assert_pixels_eq!(filtered, expected);
    }

    #[test]
    fn test_vertical_scharr_gradient_image() {
        let image = gray_image!(
            3, 6, 9;
            2, 5, 8;
            1, 4, 7);

        let expected = gray_image!(type: i16,
            -16, -16, -16;
            -32, -32, -32;
            -16, -16, -16);

        let filtered = vertical_scharr(&image);
        assert_pixels_eq!(filtered, expected);
    }

    #[test]
    fn test_horizontal_prewitt_gradient_image() {
        let image = gray_image!(
            3, 2, 1;
            6, 5, 4;
            9, 8, 7);

        let expected = gray_image!(type: i16,
            -3, -6, -3;
            -3, -6, -3;
            -3, -6, -3);

        let filtered = horizontal_prewitt(&image);
        assert_pixels_eq!(filtered, expected);
    }

    #[test]
    fn test_vertical_prewitt_gradient_image() {
        let image = gray_image!(
            3, 6, 9;
            2, 5, 8;
            1, 4, 7);

        let expected = gray_image!(type: i16,
            -3, -3, -3;
            -6, -6, -6;
            -3, -3, -3);

        let filtered = vertical_prewitt(&image);
        assert_pixels_eq!(filtered, expected);
    }

    #[bench]
    fn bench_sobel_gradients(b: &mut Bencher) {
        let image = gray_bench_image(500, 500);
        b.iter(|| {
            let gradients = sobel_gradients(&image);
            black_box(gradients);
        });
    }
}
<|endoftext|>


<commit_before>
use std::marker::PhantomData;
use crypto_stuff::*;

pub enum Token {E, S, Dhee, Dhes, Dhse, Dhss}

pub enum NoiseError {DecryptError}

struct SymmetricState<C: Cipher, H: Hash> {
    cipherstate : C,
    has_key : bool,
    h : [u8; MAXHASHLEN],
    ck: [u8; MAXHASHLEN],
    wtf : PhantomData<H>, /* So rust thinks I'm using H, this is ugly */
}

pub struct HandshakeState<D: Dh, C: Cipher, H: Hash> {
    symmetricstate: SymmetricState<C, H>,
    s: Option<D>,
    e: Option<D>,
    rs: Option<[u8; DHLEN]>,
    re: Option<[u8; DHLEN]>,
}

impl <C: Cipher, H: Hash> SymmetricState<C, H> {

    fn new(handshake_name: &[u8]) -> SymmetricState<C, H> {
        let mut hname = [0u8; MAXHASHLEN];
        if handshake_name.len() <= H::hash_len() {
            copy_memory(handshake_name, &mut hname);
        } else {
            let mut hasher = H::new();
            hasher.input(handshake_name);
            hasher.result(&mut hname);
        }
        SymmetricState{
            cipherstate: C::new(&[0u8; CIPHERKEYLEN], 0),
            has_key : false,
            h: hname,
            ck : hname,
            wtf: PhantomData::<H>
        }
    }

    fn mix_key(&mut self, data: &[u8]) {
        let mut hkdf_output = ([0u8; MAXHASHLEN], [0u8; MAXHASHLEN]);
        H::hkdf(&self.ck[..H::hash_len()], data, &mut hkdf_output.0, &mut hkdf_output.1);
        copy_memory(&hkdf_output.0, &mut self.ck);
        self.cipherstate = C::new(&hkdf_output.1[..CIPHERKEYLEN], 0);
        self.has_key = true;
    }

    fn mix_hash(&mut self, data: &[u8]) {
        let mut hasher = H::new();
        hasher.input(&self.h[..H::hash_len()]);
        hasher.input(data);
        hasher.result(&mut self.h);
    }

    fn encrypt_and_hash(&mut self, plaintext: &[u8], out: &mut [u8]) -> usize {
        if self.has_key {
            self.cipherstate.encrypt_and_inc(&self.h[..H::hash_len()], plaintext, out);
            self.mix_hash(&out[..plaintext.len() + TAGLEN]);
            return plaintext.len() + TAGLEN;
        } else {
            copy_memory(plaintext, out);
            self.mix_hash(plaintext);
            return plaintext.len();
        }
    }

    fn decrypt_and_hash(&mut self, data: &[u8], out: &mut [u8]) -> bool {
        if self.has_key {
            if !self.cipherstate.decrypt_and_inc(&self.h[..H::hash_len()], data, out) {
                return false;
            }
            self.mix_hash(data);
        } else {
            copy_memory(data, out);
            self.mix_hash(data);
        }
        true
    }

    fn split(&self) -> (C, C) {
        let mut hkdf_output = ([0u8; MAXHASHLEN], [0u8; MAXHASHLEN]);
        H::hkdf(&self.ck[..H::hash_len()], &[0u8; 0], &mut hkdf_output.0, &mut hkdf_output.1);
        let c1 = C::new(&hkdf_output.0[..CIPHERKEYLEN], 0);
        let c2 = C::new(&hkdf_output.1[..CIPHERKEYLEN], 0);
        (c1, c2)
    }

}

impl <D: Dh, C: Cipher, H: Hash> HandshakeState<D, C, H> {

    pub fn new(handshake_name: &[u8],
               new_s : Option<D>,
               new_e : Option<D>,
               new_rs: Option<[u8; DHLEN]>,
               new_re: Option<[u8; DHLEN]> ) -> HandshakeState<D, C, H> {
        let symmetricstate = SymmetricState::new(handshake_name);
        HandshakeState{symmetricstate: symmetricstate, s: new_s, e: new_e, rs: new_rs, re: new_re}
    }

    pub fn write_handshake_message(&mut self,
                               descriptor: &[Token],
                               last: bool,
                               payload: &[u8],
                               out: &mut [u8]) -> Option<(C, C)> {
        let mut index = 0;
        for token in descriptor {
            match *token {
                Token::E => {
                    self.e = Some(D::generate());
                    index += self.symmetricstate.encrypt_and_hash(&self.e.as_ref().unwrap().pubkey(), &mut out[index..]);
                },
                Token::S => index += self.symmetricstate.encrypt_and_hash(&self.s.as_ref().unwrap().pubkey(), &mut out[index..]),
                Token::Dhee => self.symmetricstate.mix_key(&self.e.as_ref().unwrap().dh(&self.re.unwrap())),
                Token::Dhes => self.symmetricstate.mix_key(&self.e.as_ref().unwrap().dh(&self.rs.unwrap())),
                Token::Dhse => self.symmetricstate.mix_key(&self.s.as_ref().unwrap().dh(&self.re.unwrap())),
                Token::Dhss => self.symmetricstate.mix_key(&self.s.as_ref().unwrap().dh(&self.rs.unwrap())),
            }
        }
        self.symmetricstate.encrypt_and_hash(payload, &mut out[index..]);
        match last {
            true => Some(self.symmetricstate.split()),
            false => None
        }
    }

    pub fn read_handshake_message(&mut self,
                              buffer: &[u8],
                              descriptor: &[Token],
                              last: bool,
                              out: &mut [u8]) -> Result<Option<(C, C)>, NoiseError> {
        let mut ptr = buffer;
        for token in descriptor {
            match *token {
                Token::E | Token::S => {
                    let data = match self.symmetricstate.has_key {
                        true =>  {
                            let temp = &ptr[..DHLEN + TAGLEN];
                            ptr = &ptr[DHLEN + TAGLEN..];
                            temp
                        }
                        false => {
                            let temp = &ptr[..DHLEN];
                            ptr = &ptr[DHLEN..];
                            temp
                        }
                    };
                    let mut pubkey = [0u8; DHLEN];
                    if !self.symmetricstate.decrypt_and_hash(data, &mut pubkey) {
                        return Err(NoiseError::DecryptError);
                    }
                    match *token {
                        Token::E => self.re = Some(pubkey),
                        Token::S => self.rs = Some(pubkey),
                        _ => unreachable!(),
                    }
                },
                Token::Dhee => self.symmetricstate.mix_key(&self.e.as_ref().unwrap().dh(&self.re.unwrap())),
                Token::Dhes => self.symmetricstate.mix_key(&self.s.as_ref().unwrap().dh(&self.re.unwrap())),
                Token::Dhse => self.symmetricstate.mix_key(&self.e.as_ref().unwrap().dh(&self.rs.unwrap())),
                Token::Dhss => self.symmetricstate.mix_key(&self.s.as_ref().unwrap().dh(&self.rs.unwrap())),
            }
        }
        if !self.symmetricstate.decrypt_and_hash(ptr, out) {
            return Err(NoiseError::DecryptError);
        }
        match last {
            true => Ok(Some(self.symmetricstate.split())),
            false => Ok(None)
        }
    }

}

<commit_msg>Minor cleanups<commit_after>
use std::marker::PhantomData;
use crypto_stuff::*;

pub enum Token {E, S, Dhee, Dhes, Dhse, Dhss}

pub enum NoiseError {DecryptError}

struct SymmetricState<C: Cipher, H: Hash> {
    cipherstate : C,
    has_key : bool,
    h : [u8; MAXHASHLEN], /* Change once Rust has trait-associated consts */
    ck: [u8; MAXHASHLEN], /* Change once Rust has trait-associated consts */
    wtf : PhantomData<H>, /* So rust thinks I'm using H, this is ugly */
}

pub struct HandshakeState<D: Dh, C: Cipher, H: Hash> {
    symmetricstate: SymmetricState<C, H>,
    s: Option<D>,
    e: Option<D>,
    rs: Option<[u8; DHLEN]>,
    re: Option<[u8; DHLEN]>,
}

impl <C: Cipher, H: Hash> SymmetricState<C, H> {

    fn new(handshake_name: &[u8]) -> SymmetricState<C, H> {
        let mut hname = [0u8; MAXHASHLEN];
        if handshake_name.len() <= H::hash_len() {
            copy_memory(handshake_name, &mut hname);
        } else {
            let mut hasher = H::new();
            hasher.input(handshake_name);
            hasher.result(&mut hname);
        }
        SymmetricState{
            cipherstate: C::new(&[0u8; CIPHERKEYLEN], 0),
            has_key : false,
            h: hname,
            ck : hname,
            wtf: PhantomData::<H>
        }
    }

    fn mix_key(&mut self, data: &[u8]) {
        let mut hkdf_output = ([0u8; MAXHASHLEN], [0u8; MAXHASHLEN]);
        H::hkdf(&self.ck[..H::hash_len()], data, &mut hkdf_output.0, &mut hkdf_output.1);
        copy_memory(&hkdf_output.0, &mut self.ck);
        self.cipherstate = C::new(&hkdf_output.1[..CIPHERKEYLEN], 0);
        self.has_key = true;
    }

    fn mix_hash(&mut self, data: &[u8]) {
        let mut hasher = H::new();
        hasher.input(&self.h[..H::hash_len()]);
        hasher.input(data);
        hasher.result(&mut self.h);
    }

    fn encrypt_and_hash(&mut self, plaintext: &[u8], out: &mut [u8]) -> usize {
        if self.has_key {
            self.cipherstate.encrypt_and_inc(&self.h[..H::hash_len()], plaintext, out);
            self.mix_hash(&out[..plaintext.len() + TAGLEN]);
            return plaintext.len() + TAGLEN;
        } else {
            copy_memory(plaintext, out);
            self.mix_hash(plaintext);
            return plaintext.len();
        }
    }

    fn decrypt_and_hash(&mut self, data: &[u8], out: &mut [u8]) -> bool {
        if self.has_key {
            if !self.cipherstate.decrypt_and_inc(&self.h[..H::hash_len()], data, out) {
                return false;
            }
        }
        else {
            copy_memory(data, out)
        }
        self.mix_hash(data);
        true
    }

    fn split(&self) -> (C, C) {
        let mut hkdf_output = ([0u8; MAXHASHLEN], [0u8; MAXHASHLEN]);
        H::hkdf(&self.ck[..H::hash_len()], &[0u8; 0], &mut hkdf_output.0, &mut hkdf_output.1);
        let c1 = C::new(&hkdf_output.0[..CIPHERKEYLEN], 0);
        let c2 = C::new(&hkdf_output.1[..CIPHERKEYLEN], 0);
        (c1, c2)
    }

}

impl <D: Dh, C: Cipher, H: Hash> HandshakeState<D, C, H> {

    pub fn new(handshake_name: &[u8],
               new_s : Option<D>,
               new_e : Option<D>,
               new_rs: Option<[u8; DHLEN]>,
               new_re: Option<[u8; DHLEN]> ) -> HandshakeState<D, C, H> {
        let symmetricstate = SymmetricState::new(handshake_name);
        HandshakeState{symmetricstate: symmetricstate, s: new_s, e: new_e, rs: new_rs, re: new_re}
    }

    pub fn write_handshake_message(&mut self,
                               descriptor: &[Token],
                               last: bool,
                               payload: &[u8],
                               out: &mut [u8]) -> Option<(C, C)> {
        let mut index = 0;
        for token in descriptor {
            match *token {
                Token::E => {
                    self.e = Some(D::generate());
                    index += self.symmetricstate.encrypt_and_hash(&self.e.as_ref().unwrap().pubkey(), &mut out[index..]);
                },
                Token::S => index += self.symmetricstate.encrypt_and_hash(&self.s.as_ref().unwrap().pubkey(), &mut out[index..]),
                Token::Dhee => self.symmetricstate.mix_key(&self.e.as_ref().unwrap().dh(&self.re.unwrap())),
                Token::Dhes => self.symmetricstate.mix_key(&self.e.as_ref().unwrap().dh(&self.rs.unwrap())),
                Token::Dhse => self.symmetricstate.mix_key(&self.s.as_ref().unwrap().dh(&self.re.unwrap())),
                Token::Dhss => self.symmetricstate.mix_key(&self.s.as_ref().unwrap().dh(&self.rs.unwrap())),
            }
        }
        self.symmetricstate.encrypt_and_hash(payload, &mut out[index..]);
        match last {
            true => Some(self.symmetricstate.split()),
            false => None
        }
    }

    pub fn read_handshake_message(&mut self,
                              buffer: &[u8],
                              descriptor: &[Token],
                              last: bool,
                              out: &mut [u8]) -> Result<Option<(C, C)>, NoiseError> {
        let mut ptr = buffer;
        for token in descriptor {
            match *token {
                Token::E | Token::S => {
                    let data = match self.symmetricstate.has_key {
                        true =>  {
                            let temp = &ptr[..DHLEN + TAGLEN];
                            ptr = &ptr[DHLEN + TAGLEN..];
                            temp
                        }
                        false => {
                            let temp = &ptr[..DHLEN];
                            ptr = &ptr[DHLEN..];
                            temp
                        }
                    };
                    let mut pubkey = [0u8; DHLEN];
                    if !self.symmetricstate.decrypt_and_hash(data, &mut pubkey) {
                        return Err(NoiseError::DecryptError);
                    }
                    match *token {
                        Token::E => self.re = Some(pubkey),
                        Token::S => self.rs = Some(pubkey),
                        _ => unreachable!(),
                    }
                },
                Token::Dhee => self.symmetricstate.mix_key(&self.e.as_ref().unwrap().dh(&self.re.unwrap())),
                Token::Dhes => self.symmetricstate.mix_key(&self.s.as_ref().unwrap().dh(&self.re.unwrap())),
                Token::Dhse => self.symmetricstate.mix_key(&self.e.as_ref().unwrap().dh(&self.rs.unwrap())),
                Token::Dhss => self.symmetricstate.mix_key(&self.s.as_ref().unwrap().dh(&self.rs.unwrap())),
            }
        }
        if !self.symmetricstate.decrypt_and_hash(ptr, out) {
            return Err(NoiseError::DecryptError);
        }
        match last {
            true => Ok(Some(self.symmetricstate.split())),
            false => Ok(None)
        }
    }

}

<|endoftext|>


<commit_before>//! Loads a Cargo project into a static instance of analysis, without support
//! for incorporating changes.
use std::{path::Path, sync::Arc};

use anyhow::Result;
use crossbeam_channel::{unbounded, Receiver};
use hir::db::DefDatabase;
use ide::{AnalysisHost, Change};
use ide_db::{base_db::CrateGraph, FxHashMap};
use proc_macro_api::ProcMacroServer;
use project_model::{CargoConfig, ProjectManifest, ProjectWorkspace};
use vfs::{loader::Handle, AbsPath, AbsPathBuf};

use crate::reload::{load_proc_macro, ProjectFolders, SourceRootConfig};

// Note: Since this type is used by external tools that use rust-analyzer as a library
// what otherwise would be `pub(crate)` has to be `pub` here instead.
pub struct LoadCargoConfig {
    pub load_out_dirs_from_check: bool,
    pub with_proc_macro: bool,
    pub prefill_caches: bool,
}

// Note: Since this function is used by external tools that use rust-analyzer as a library
// what otherwise would be `pub(crate)` has to be `pub` here instead.
pub fn load_workspace_at(
    root: &Path,
    cargo_config: &CargoConfig,
    load_config: &LoadCargoConfig,
    progress: &dyn Fn(String),
) -> Result<(AnalysisHost, vfs::Vfs, Option<ProcMacroServer>)> {
    let root = AbsPathBuf::assert(std::env::current_dir()?.join(root));
    let root = ProjectManifest::discover_single(&root)?;
    let mut workspace = ProjectWorkspace::load(root, cargo_config, progress)?;

    if load_config.load_out_dirs_from_check {
        let build_scripts = workspace.run_build_scripts(cargo_config, progress)?;
        workspace.set_build_scripts(build_scripts)
    }

    load_workspace(workspace, &cargo_config.extra_env, load_config)
}

// Note: Since this function is used by external tools that use rust-analyzer as a library
// what otherwise would be `pub(crate)` has to be `pub` here instead.
//
// The reason both, `load_workspace_at` and `load_workspace` are `pub` is that some of
// these tools need access to `ProjectWorkspace`, too, which `load_workspace_at` hides.
pub fn load_workspace(
    ws: ProjectWorkspace,
    extra_env: &FxHashMap<String, String>,
    load_config: &LoadCargoConfig,
) -> Result<(AnalysisHost, vfs::Vfs, Option<ProcMacroServer>)> {
    let (sender, receiver) = unbounded();
    let mut vfs = vfs::Vfs::default();
    let mut loader = {
        let loader =
            vfs_notify::NotifyHandle::spawn(Box::new(move |msg| sender.send(msg).unwrap()));
        Box::new(loader)
    };

    let proc_macro_client = if load_config.with_proc_macro {
        let path = AbsPathBuf::assert(std::env::current_exe()?);
        Ok(ProcMacroServer::spawn(path, &["proc-macro"]).unwrap())
    } else {
        Err("proc macro server not started".to_owned())
    };

    let crate_graph = ws.to_crate_graph(
        &mut |_, path: &AbsPath| {
            load_proc_macro(proc_macro_client.as_ref().map_err(|e| &**e), path, &[])
        },
        &mut |path: &AbsPath| {
            let contents = loader.load_sync(path);
            let path = vfs::VfsPath::from(path.to_path_buf());
            vfs.set_file_contents(path.clone(), contents);
            vfs.file_id(&path)
        },
        extra_env,
    );

    let project_folders = ProjectFolders::new(&[ws], &[]);
    loader.set_config(vfs::loader::Config {
        load: project_folders.load,
        watch: vec![],
        version: 0,
    });

    tracing::debug!("crate graph: {:?}", crate_graph);
    let host =
        load_crate_graph(crate_graph, project_folders.source_root_config, &mut vfs, &receiver);

    if load_config.prefill_caches {
        host.analysis().parallel_prime_caches(1, |_| {})?;
    }
    Ok((host, vfs, proc_macro_client.ok()))
}

fn load_crate_graph(
    crate_graph: CrateGraph,
    source_root_config: SourceRootConfig,
    vfs: &mut vfs::Vfs,
    receiver: &Receiver<vfs::loader::Message>,
) -> AnalysisHost {
    let lru_cap = std::env::var("RA_LRU_CAP").ok().and_then(|it| it.parse::<usize>().ok());
    let mut host = AnalysisHost::new(lru_cap);
    let mut analysis_change = Change::new();

    host.raw_database_mut().set_enable_proc_attr_macros(true);

    // wait until Vfs has loaded all roots
    for task in receiver {
        match task {
            vfs::loader::Message::Progress { n_done, n_total, config_version: _ } => {
                if n_done == n_total {
                    break;
                }
            }
            vfs::loader::Message::Loaded { files } => {
                for (path, contents) in files {
                    vfs.set_file_contents(path.into(), contents);
                }
            }
        }
    }
    let changes = vfs.take_changes();
    for file in changes {
        if file.exists() {
            let contents = vfs.file_contents(file.file_id).to_vec();
            if let Ok(text) = String::from_utf8(contents) {
                analysis_change.change_file(file.file_id, Some(Arc::new(text)))
            }
        }
    }
    let source_roots = source_root_config.partition(vfs);
    analysis_change.set_roots(source_roots);

    analysis_change.set_crate_graph(crate_graph);

    host.apply_change(analysis_change);
    host
}

#[cfg(test)]
mod tests {
    use super::*;

    use hir::Crate;

    #[test]
    fn test_loading_rust_analyzer() {
        let path = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap().parent().unwrap();
        let cargo_config = CargoConfig::default();
        let load_cargo_config = LoadCargoConfig {
            load_out_dirs_from_check: false,
            with_proc_macro: false,
            prefill_caches: false,
        };
        let (host, _vfs, _proc_macro) =
            load_workspace_at(path, &cargo_config, &load_cargo_config, &|_| {}).unwrap();

        let n_crates = Crate::all(host.raw_database()).len();
        // RA has quite a few crates, but the exact count doesn't matter
        assert!(n_crates > 20);
    }
}
<commit_msg>Auto merge of #13289 - rust-lang:analysis-stats-proc-server, r=Veykril<commit_after>//! Loads a Cargo project into a static instance of analysis, without support
//! for incorporating changes.
use std::{path::Path, sync::Arc};

use anyhow::Result;
use crossbeam_channel::{unbounded, Receiver};
use hir::db::DefDatabase;
use ide::{AnalysisHost, Change};
use ide_db::{base_db::CrateGraph, FxHashMap};
use proc_macro_api::ProcMacroServer;
use project_model::{CargoConfig, ProjectManifest, ProjectWorkspace};
use vfs::{loader::Handle, AbsPath, AbsPathBuf};

use crate::reload::{load_proc_macro, ProjectFolders, SourceRootConfig};

// Note: Since this type is used by external tools that use rust-analyzer as a library
// what otherwise would be `pub(crate)` has to be `pub` here instead.
pub struct LoadCargoConfig {
    pub load_out_dirs_from_check: bool,
    pub with_proc_macro: bool,
    pub prefill_caches: bool,
}

// Note: Since this function is used by external tools that use rust-analyzer as a library
// what otherwise would be `pub(crate)` has to be `pub` here instead.
pub fn load_workspace_at(
    root: &Path,
    cargo_config: &CargoConfig,
    load_config: &LoadCargoConfig,
    progress: &dyn Fn(String),
) -> Result<(AnalysisHost, vfs::Vfs, Option<ProcMacroServer>)> {
    let root = AbsPathBuf::assert(std::env::current_dir()?.join(root));
    let root = ProjectManifest::discover_single(&root)?;
    let mut workspace = ProjectWorkspace::load(root, cargo_config, progress)?;

    if load_config.load_out_dirs_from_check {
        let build_scripts = workspace.run_build_scripts(cargo_config, progress)?;
        workspace.set_build_scripts(build_scripts)
    }

    load_workspace(workspace, &cargo_config.extra_env, load_config)
}

// Note: Since this function is used by external tools that use rust-analyzer as a library
// what otherwise would be `pub(crate)` has to be `pub` here instead.
//
// The reason both, `load_workspace_at` and `load_workspace` are `pub` is that some of
// these tools need access to `ProjectWorkspace`, too, which `load_workspace_at` hides.
pub fn load_workspace(
    ws: ProjectWorkspace,
    extra_env: &FxHashMap<String, String>,
    load_config: &LoadCargoConfig,
) -> Result<(AnalysisHost, vfs::Vfs, Option<ProcMacroServer>)> {
    let (sender, receiver) = unbounded();
    let mut vfs = vfs::Vfs::default();
    let mut loader = {
        let loader =
            vfs_notify::NotifyHandle::spawn(Box::new(move |msg| sender.send(msg).unwrap()));
        Box::new(loader)
    };

    let proc_macro_client = if load_config.with_proc_macro {
        let mut path = AbsPathBuf::assert(std::env::current_exe()?);
        let mut args = vec!["proc-macro"];

        if let ProjectWorkspace::Cargo { sysroot, .. } | ProjectWorkspace::Json { sysroot, .. } =
            &ws
        {
            if let Some(sysroot) = sysroot.as_ref() {
                let standalone_server_name =
                    format!("rust-analyzer-proc-macro-srv{}", std::env::consts::EXE_SUFFIX);
                let server_path = sysroot.root().join("libexec").join(&standalone_server_name);
                if std::fs::metadata(&server_path).is_ok() {
                    path = server_path;
                    args = vec![];
                }
            }
        }

        ProcMacroServer::spawn(path.clone(), args.clone()).map_err(|e| e.to_string())
    } else {
        Err("proc macro server disabled".to_owned())
    };

    let crate_graph = ws.to_crate_graph(
        &mut |_, path: &AbsPath| {
            load_proc_macro(proc_macro_client.as_ref().map_err(|e| &**e), path, &[])
        },
        &mut |path: &AbsPath| {
            let contents = loader.load_sync(path);
            let path = vfs::VfsPath::from(path.to_path_buf());
            vfs.set_file_contents(path.clone(), contents);
            vfs.file_id(&path)
        },
        extra_env,
    );

    let project_folders = ProjectFolders::new(&[ws], &[]);
    loader.set_config(vfs::loader::Config {
        load: project_folders.load,
        watch: vec![],
        version: 0,
    });

    tracing::debug!("crate graph: {:?}", crate_graph);
    let host =
        load_crate_graph(crate_graph, project_folders.source_root_config, &mut vfs, &receiver);

    if load_config.prefill_caches {
        host.analysis().parallel_prime_caches(1, |_| {})?;
    }
    Ok((host, vfs, proc_macro_client.ok()))
}

fn load_crate_graph(
    crate_graph: CrateGraph,
    source_root_config: SourceRootConfig,
    vfs: &mut vfs::Vfs,
    receiver: &Receiver<vfs::loader::Message>,
) -> AnalysisHost {
    let lru_cap = std::env::var("RA_LRU_CAP").ok().and_then(|it| it.parse::<usize>().ok());
    let mut host = AnalysisHost::new(lru_cap);
    let mut analysis_change = Change::new();

    host.raw_database_mut().set_enable_proc_attr_macros(true);

    // wait until Vfs has loaded all roots
    for task in receiver {
        match task {
            vfs::loader::Message::Progress { n_done, n_total, config_version: _ } => {
                if n_done == n_total {
                    break;
                }
            }
            vfs::loader::Message::Loaded { files } => {
                for (path, contents) in files {
                    vfs.set_file_contents(path.into(), contents);
                }
            }
        }
    }
    let changes = vfs.take_changes();
    for file in changes {
        if file.exists() {
            let contents = vfs.file_contents(file.file_id).to_vec();
            if let Ok(text) = String::from_utf8(contents) {
                analysis_change.change_file(file.file_id, Some(Arc::new(text)))
            }
        }
    }
    let source_roots = source_root_config.partition(vfs);
    analysis_change.set_roots(source_roots);

    analysis_change.set_crate_graph(crate_graph);

    host.apply_change(analysis_change);
    host
}

#[cfg(test)]
mod tests {
    use super::*;

    use hir::Crate;

    #[test]
    fn test_loading_rust_analyzer() {
        let path = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap().parent().unwrap();
        let cargo_config = CargoConfig::default();
        let load_cargo_config = LoadCargoConfig {
            load_out_dirs_from_check: false,
            with_proc_macro: false,
            prefill_caches: false,
        };
        let (host, _vfs, _proc_macro) =
            load_workspace_at(path, &cargo_config, &load_cargo_config, &|_| {}).unwrap();

        let n_crates = Crate::all(host.raw_database()).len();
        // RA has quite a few crates, but the exact count doesn't matter
        assert!(n_crates > 20);
    }
}
<|endoftext|>