<commit_before>extern mod std;
use io::{Reader,ReaderUtil};

mod environment;
use environment::Environment;
mod expression;
use expression::Expression;
use expression::{Bool,Int,Float,Symbol,List,Proc,Error,Lambda};
use expression::Expression::new_proc;
mod parse;
use parse::parse;

fn test_env() -> @Environment {
    @Environment::new_global_environment()
}

fn test_eval( expr:&str, result:&str ) {
    let evaluated = eval(parse(expr), test_env());
    let expected = parse(result);
    if expected != evaluated.first() {
        fail fmt!("Expected: %s -> Got %s", expected.to_str(), evaluated.first().to_str())
    }
}

fn test_eval_fails( expr:&str, result:&str, reason:&str ) {
    let evaluated = eval(parse(expr), test_env());
    let expected = parse(result);
    if expected == evaluated.first() {
        fail fmt!("%s should not have evaluated to %s (%s)", expr, result, reason)
    }
}

fn test_eval_to_error( expr:&str, reason:&str ) {
    let evaluated = eval(parse(expr), test_env());
    match evaluated.first() {
        Error(_) => (),
        _ => fail fmt!("%s should have returned an error: %s", expr, reason)
    }
}

#[test]
fn test_eval_empty_list_returns_itself() {
    test_eval( ~"()", ~"()" );
}

#[test]
fn test_eval_returns_number_when_passed_number() {
    test_eval( ~"1", ~"1" );
}

#[test]
fn test_eval_returns_expression_when_passed_quote() {
    test_eval( ~"(quote (a))", ~"(a)" );
}

#[test]
fn test_eval_returns_last_expression_when_passed_begin() {
    test_eval( ~"(begin 1 2 3)", ~"3" );
}

#[test]
fn test_if_returns_third_part_when_if_is_true() {
    test_eval( ~"(if 1 2 3)", ~"2" );
}

#[test]
fn test_if_returns_fourth_part_when_if_is_false() {
    test_eval( ~"(if 0 2 3)", ~"3" );
}

#[test]
fn test_that_if_evaluates_the_then_branch() {
    let expression = ~"(if 1 (begin 1 2) 7)";
    test_eval_fails( expression, ~"(begin 1 2)", ~"if just returned the raw then" );
    test_eval( expression, ~"2" );
}

#[test]
fn test_that_if_evaluates_the_else_branch() {
    let expression = ~"(if 0 7 (begin 1 2))";
    test_eval_fails( expression, ~"(begin 1 2)", ~"if just returned the else branch" );
    test_eval( expression, ~"2");
}

#[test]
fn test_that_if_evaluates_the_test() {
    let expression = ~"(if (begin 1 0) 1 2)";
    test_eval_fails( expression, ~"1", ~"if didn't evaluate the test" );
    test_eval( expression, ~"2" );
}

#[test]
fn test_that_bare_symbol_is_interpreted_as_variable() {
    let env = test_env();
    env.define(~"monkey", Int(10));
    let expression = parse( ~"monkey" );
    let value = eval( expression, env );
    match value {
        (Int(10), _) => (),
        _ => fail fmt!("Expected 10 got %s", value.first().to_str())
    }
}

#[test]
fn test_that_undefined_symbol_is_an_error() {
    let expression = ~"monkey";
    test_eval_to_error( expression, ~"Undefined symbol wasn't an error" );
}

#[test]
fn test_that_define_can_add_a_variable() {
    let env = test_env();
    let expression = parse( ~"(define x 10)" );
    let value = eval( expression, env );
    match env.lookup(~"x") {
        Some(Int(10)) => (),
        _ => fail fmt!("Expected 10 got %s", value.first().to_str())
    }
}

#[test]
fn test_that_set_cannot_create_a_variable() {
    let env = test_env();
    let expression = parse( ~"(set! x 10)" );
    let value = eval( expression, env );
    match value.first() {
        Error(_) => (),
        _ => fail ~"set! created a variable"
    }
}

#[test]
fn test_that_set_can_change_a_variable() {
    let env = test_env();
    env.define(~"x", Int(100));
    let expression = parse( ~"(set! x 10)" );
    let value = eval( expression, env );
    match env.lookup(~"x") {
        Some(Int(10)) => (),
        _ => fail fmt!("Expected 10 got %s", value.first().to_str())
    }
}

#[test]
fn test_that_set_returns_the_value_not_the_key() {
    let env = test_env();
    env.define(~"x", Int(100));
    let expression = parse( ~"(set! x 10)" );
    let value = eval( expression, env );
    match value {
        (Int(10), _) => (),
        (Symbol(~"x"), _) => fail ~"set! returned the key, not the value",
        _ => fail fmt!("Expected 10 got %s", value.first().to_str())
    }
}

#[test]
fn test_that_begin_can_handle_one_argument() {
    let env = test_env();
    let expression = parse( ~"(begin 10)" );
    let value = eval( expression, env );
    match value {
        (Int(10), _) => (),
        _ => fail fmt!("Expected 10 got %s", value.first().to_str())
    }
}

#[test]
fn test_that_begin_evaluates_all_arguments() {
    let env = test_env();
    let expression = parse( ~"(begin (define x 10) x)" );
    let value = eval( expression, env );
    match env.lookup(~"x") {
        Some(Int(10)) => (),
        _ => fail fmt!("Expected 10 got %s", value.first().to_str())
    }
    match value {
        (Int(10), _) => (),
        _ => fail fmt!("Expected 10 got %s", value.first().to_str())
    }
}

#[test]
fn test_that_other_symbols_are_evaluated_as_procs() {
    let env = test_env();
    let expression = parse( ~"(+ 1 2)" );
    let value = eval( expression, env );
    match value {
        (Int(3), _) => (),
        _ => fail fmt!("(+ 1 2) became %s", value.first().to_str())
    }
}

#[test]
fn test_that_proc_params_are_evaluated() {
    let env=test_env();
    let expression = parse( ~"(+ (+ 1 2) 3)" );
    let value = eval (expression, env);
    match value {
        (Int(6), _) => (),
        _ => fail fmt!("(+ (+ 1 2) 3) became %s", value.first().to_str())
    }
}

#[test]
fn test_that_lambda_evaluates_to_a_lambda() {
    let env=test_env();
    let expression = parse( ~"(lambda (x) (* x x))" );
    let value = eval(expression, env);
    match value {
       (Lambda(_,_,_), _) => (),
        _ => fail ~"lambda doesn't turn into a Proc"
    }
}

#[test]
fn test_that_lambda_without_variables_evals() {
    let env=test_env();
    let expression = parse( ~"( (lambda () (+ 1 1))  )" );
    let value = eval(expression, env);
    match value {
        (Int(2), _) => (),
        _ => fail fmt!("lambda evaluated to %s", value.first().to_str())
    }
}

#[test]
fn test_that_lambda_with_a_variable_evals() {
    let env=test_env();
    let expression = parse( ~"( (lambda (x) (+ x 1)) 1  )" );
    let value = eval(expression, env);
    match value {
        (Int(2), _) => (),
        _ => fail fmt!("lambda evaluated to %s", value.first().to_str())
    }
}

fn eval( expression:Expression, environment:@Environment ) -> (Expression, @Environment ) {
    fn quote(expressions:~[Expression]) -> Expression {
        match expressions {
            [_, expr] => expr,
            _ => Error( ~"Syntax Error: quote must take a single argument" )
        }
    }

    fn begin(expressions:~[Expression], environment:@Environment) -> Expression {
        for expressions.tail().init().each() |&expression| {
            eval( expression, environment );
        }
        eval( expressions.last(), environment ).first()
    }

    fn if_(expressions:~[Expression], environment:@Environment) -> Expression {
        match expressions {
            [_, test, true_expr, false_expr] => {
                let condition = eval(test, environment).first();
                eval(if condition.to_bool() {
                    true_expr
                } else {
                    false_expr
                }, environment).first()
            }
            _ => Error( ~"Syntax Error: if must take three arguments" )
        }
    }

    fn reset_variable(expressions:~[Expression], environment:@Environment, function:~str) -> Expression {
        match expressions {
            [_, symbol, value] => {
                match copy symbol {
                    Symbol( key ) => {
                        environment.reset(key, eval(value, environment).first());
                        symbol
                    }
                    _ => Error( fmt!("Syntax Error: %s takes a symbol as its first argument", function) )
                }
            }
            _ => Error( fmt!("Syntax Error: %s must take two arguments", function) )
        }
    }

    fn define_variable(expressions:~[Expression], environment:@Environment, function:~str) -> Expression {
        match expressions {
            [_, symbol, value] => {
                match copy symbol {
                    Symbol( key ) => {
                        environment.define(key, eval(value, environment).first());
                        symbol
                    }
                    _ => Error( fmt!("Syntax Error: %s takes a symbol as its first argument", function) )
                }
            }
            _ => Error( fmt!("Syntax Error: %s must take two arguments", function) )
        }
    }

    fn define(expressions:~[Expression], environment:@Environment) -> Expression {
        define_variable(expressions, environment, ~"define")
    }

    fn set_bang(expressions:~[Expression], environment:@Environment) -> Expression {
        match copy expressions {
            [_, symbol, _] => {
                match copy symbol {
                    Symbol( key ) => {
                        match environment.lookup( copy key )  {
                            None => return Error( ~"Syntax Error: set! cannot create a variable" ),
                            _ => ()
                        }
                    }
                    _ => ()
                }
            }
            _ => ()
        }
        let symbol = reset_variable(expressions, environment, ~"set!");
        match copy symbol {
            Symbol( key ) => {
                match environment.lookup( copy key ) {
                    Some( value ) => value,
                    None => Error( ~"System Error: Reset variable failed to set the new value" )
                }
            }
            _ => Error( ~"System Error: Reset variable didn't return a symbol" )
        }
    }

    fn proc(expressions:~[Expression], environment:@Environment) -> Expression {
        let exprs = expressions.map(|&expr| eval(expr, environment).first());
        match exprs.head() {
            Proc( procedure, _ ) => procedure( exprs.tail(), environment ),
            Lambda( expr, variables, env ) => {
                let local_env = @Environment::new( *env );
                for vec::zip(copy variables, exprs.tail()).each |param| {
                    match param.first() {
                        Symbol(key) => local_env.define(key, param.second()),
                        _ => return Error( ~"Variable names must be symbols" )
                    }
                }
                eval( *expr, local_env).first()
            }
            _ => Error( fmt!("\"%s\" is not a procedure", exprs.head().to_str()) )
        }
    }

    fn lambda(expressions:~[Expression], env:@Environment) -> Expression {
        match copy expressions {
            [_, List(param_names), expression] => {
                Lambda(@expression, param_names, env)
            }
//            new_proc( |param_values, _| {
//                    let local_env = @Environment::new( copy *env );
//                    for vec::zip(copy param_names, param_values).each |param| {
//                        match param.first() {
//                            Symbol(key) => local_env.define( key, param.second() ),
//                            _ => return Error( ~"lambda params list must be list of symbols" )
//                        }
//                    }
//                    eval(copy expression, local_env).first()
//                } ),
            _ => Error( fmt!("Syntax Error: lambda requires 2 arguments, got \"%u\"", expressions.len()-1 ) )
        }
    }

    (match copy expression {
        List( expressions ) => {
            if expressions.len() == 0 {
                expression
            } else {
                match expressions[0] {
                    Symbol(~"quote") => quote(expressions),
                    Symbol(~"begin") => begin(expressions, environment),
                    Symbol(~"if") => if_(expressions, environment),
                    Symbol(~"define") => define(expressions, environment),
                    Symbol(~"set!") => set_bang(expressions, environment),
                    Symbol(~"lambda") => lambda(expressions, environment),
                    _ => proc(expressions, environment)
                }
            }
        }
        Symbol( symbol ) => {
            match environment.lookup( copy symbol ) {
                Some( value ) => value,
                None => Error( fmt!("Undefined symbol %s",symbol) )
            }
        }
        _ => {
            expression
        }
    }, environment)
}

fn main() {
    fn evaluate( expr:~str, env:Environment ) -> Option<Environment> {
        let (result, new_env) = eval( parse(expr), @env );
        io::println( fmt!("%s -> %s", expr, result.to_str() ));
        Some(*new_env)
    }

    let mut env = Environment::new_global_environment();
    loop {
        io::print("rusty> ");
        let in = io::stdin().read_line();
        let result = evaluate( in, copy env );
        match result {
            Some(new_env) => env = new_env,
            None => ()
        }
    }
}
<commit_msg>Add test with nested lambdas<commit_after>extern mod std;
use io::{Reader,ReaderUtil};

mod environment;
use environment::Environment;
mod expression;
use expression::Expression;
use expression::{Bool,Int,Float,Symbol,List,Proc,Error,Lambda};
use expression::Expression::new_proc;
mod parse;
use parse::parse;

fn test_env() -> @Environment {
    @Environment::new_global_environment()
}

fn test_eval( expr:&str, result:&str ) {
    let evaluated = eval(parse(expr), test_env());
    let expected = parse(result);
    if expected != evaluated.first() {
        fail fmt!("Expected: %s -> Got %s", expected.to_str(), evaluated.first().to_str())
    }
}

fn test_eval_fails( expr:&str, result:&str, reason:&str ) {
    let evaluated = eval(parse(expr), test_env());
    let expected = parse(result);
    if expected == evaluated.first() {
        fail fmt!("%s should not have evaluated to %s (%s)", expr, result, reason)
    }
}

fn test_eval_to_error( expr:&str, reason:&str ) {
    let evaluated = eval(parse(expr), test_env());
    match evaluated.first() {
        Error(_) => (),
        _ => fail fmt!("%s should have returned an error: %s", expr, reason)
    }
}

#[test]
fn test_complicated_eval() {
    test_eval( ~"(begin (define combine (lambda (f)    (lambda (x y)      (if (null? x) (quote ())          (f (list (car x) (car y))             ((combine f) (cdr x) (cdr y))))))) (define riff-shuffle (lambda (deck) (begin (define take (lambda (n seq) (if (<= n 0) (quote ()) (cons (car seq) (take (- n 1) (cdr seq)))))) (define drop (lambda (n seq) (if (<= n 0) seq (drop (- n 1) (cdr seq))))) (define mid (lambda (seq) (/ (length seq) 2))) ((combine append) (take (mid deck) deck) (drop (mid deck) deck))))) (riff-shuffle (list 1 2 3 4 5 6 7 8)))", ~"(1 5 2 6 3 7 4 8)" );
}

#[test]
fn test_eval_empty_list_returns_itself() {
    test_eval( ~"()", ~"()" );
}

#[test]
fn test_eval_returns_number_when_passed_number() {
    test_eval( ~"1", ~"1" );
}

#[test]
fn test_eval_returns_expression_when_passed_quote() {
    test_eval( ~"(quote (a))", ~"(a)" );
}

#[test]
fn test_eval_returns_last_expression_when_passed_begin() {
    test_eval( ~"(begin 1 2 3)", ~"3" );
}

#[test]
fn test_if_returns_third_part_when_if_is_true() {
    test_eval( ~"(if 1 2 3)", ~"2" );
}

#[test]
fn test_if_returns_fourth_part_when_if_is_false() {
    test_eval( ~"(if 0 2 3)", ~"3" );
}

#[test]
fn test_that_if_evaluates_the_then_branch() {
    let expression = ~"(if 1 (begin 1 2) 7)";
    test_eval_fails( expression, ~"(begin 1 2)", ~"if just returned the raw then" );
    test_eval( expression, ~"2" );
}

#[test]
fn test_that_if_evaluates_the_else_branch() {
    let expression = ~"(if 0 7 (begin 1 2))";
    test_eval_fails( expression, ~"(begin 1 2)", ~"if just returned the else branch" );
    test_eval( expression, ~"2");
}

#[test]
fn test_that_if_evaluates_the_test() {
    let expression = ~"(if (begin 1 0) 1 2)";
    test_eval_fails( expression, ~"1", ~"if didn't evaluate the test" );
    test_eval( expression, ~"2" );
}

#[test]
fn test_that_bare_symbol_is_interpreted_as_variable() {
    let env = test_env();
    env.define(~"monkey", Int(10));
    let expression = parse( ~"monkey" );
    let value = eval( expression, env );
    match value {
        (Int(10), _) => (),
        _ => fail fmt!("Expected 10 got %s", value.first().to_str())
    }
}

#[test]
fn test_that_undefined_symbol_is_an_error() {
    let expression = ~"monkey";
    test_eval_to_error( expression, ~"Undefined symbol wasn't an error" );
}

#[test]
fn test_that_define_can_add_a_variable() {
    let env = test_env();
    let expression = parse( ~"(define x 10)" );
    let value = eval( expression, env );
    match env.lookup(~"x") {
        Some(Int(10)) => (),
        _ => fail fmt!("Expected 10 got %s", value.first().to_str())
    }
}

#[test]
fn test_that_set_cannot_create_a_variable() {
    let env = test_env();
    let expression = parse( ~"(set! x 10)" );
    let value = eval( expression, env );
    match value.first() {
        Error(_) => (),
        _ => fail ~"set! created a variable"
    }
}

#[test]
fn test_that_set_can_change_a_variable() {
    let env = test_env();
    env.define(~"x", Int(100));
    let expression = parse( ~"(set! x 10)" );
    let value = eval( expression, env );
    match env.lookup(~"x") {
        Some(Int(10)) => (),
        _ => fail fmt!("Expected 10 got %s", value.first().to_str())
    }
}

#[test]
fn test_that_set_returns_the_value_not_the_key() {
    let env = test_env();
    env.define(~"x", Int(100));
    let expression = parse( ~"(set! x 10)" );
    let value = eval( expression, env );
    match value {
        (Int(10), _) => (),
        (Symbol(~"x"), _) => fail ~"set! returned the key, not the value",
        _ => fail fmt!("Expected 10 got %s", value.first().to_str())
    }
}

#[test]
fn test_that_begin_can_handle_one_argument() {
    let env = test_env();
    let expression = parse( ~"(begin 10)" );
    let value = eval( expression, env );
    match value {
        (Int(10), _) => (),
        _ => fail fmt!("Expected 10 got %s", value.first().to_str())
    }
}

#[test]
fn test_that_begin_evaluates_all_arguments() {
    let env = test_env();
    let expression = parse( ~"(begin (define x 10) x)" );
    let value = eval( expression, env );
    match env.lookup(~"x") {
        Some(Int(10)) => (),
        _ => fail fmt!("Expected 10 got %s", value.first().to_str())
    }
    match value {
        (Int(10), _) => (),
        _ => fail fmt!("Expected 10 got %s", value.first().to_str())
    }
}

#[test]
fn test_that_other_symbols_are_evaluated_as_procs() {
    let env = test_env();
    let expression = parse( ~"(+ 1 2)" );
    let value = eval( expression, env );
    match value {
        (Int(3), _) => (),
        _ => fail fmt!("(+ 1 2) became %s", value.first().to_str())
    }
}

#[test]
fn test_that_proc_params_are_evaluated() {
    let env=test_env();
    let expression = parse( ~"(+ (+ 1 2) 3)" );
    let value = eval (expression, env);
    match value {
        (Int(6), _) => (),
        _ => fail fmt!("(+ (+ 1 2) 3) became %s", value.first().to_str())
    }
}

#[test]
fn test_that_lambda_evaluates_to_a_lambda() {
    let env=test_env();
    let expression = parse( ~"(lambda (x) (* x x))" );
    let value = eval(expression, env);
    match value {
       (Lambda(_,_,_), _) => (),
        _ => fail ~"lambda doesn't turn into a Proc"
    }
}

#[test]
fn test_that_lambda_without_variables_evals() {
    let env=test_env();
    let expression = parse( ~"( (lambda () (+ 1 1))  )" );
    let value = eval(expression, env);
    match value {
        (Int(2), _) => (),
        _ => fail fmt!("lambda evaluated to %s", value.first().to_str())
    }
}

#[test]
fn test_that_lambda_with_a_variable_evals() {
    let env=test_env();
    let expression = parse( ~"( (lambda (x) (+ x 1)) 1  )" );
    let value = eval(expression, env);
    match value {
        (Int(2), _) => (),
        _ => fail fmt!("lambda evaluated to %s", value.first().to_str())
    }
}

fn eval( expression:Expression, environment:@Environment ) -> (Expression, @Environment ) {
    fn quote(expressions:~[Expression]) -> Expression {
        match expressions {
            [_, expr] => expr,
            _ => Error( ~"Syntax Error: quote must take a single argument" )
        }
    }

    fn begin(expressions:~[Expression], environment:@Environment) -> Expression {
        for expressions.tail().init().each() |&expression| {
            eval( expression, environment );
        }
        eval( expressions.last(), environment ).first()
    }

    fn if_(expressions:~[Expression], environment:@Environment) -> Expression {
        match expressions {
            [_, test, true_expr, false_expr] => {
                let condition = eval(test, environment).first();
                eval(if condition.to_bool() {
                    true_expr
                } else {
                    false_expr
                }, environment).first()
            }
            _ => Error( ~"Syntax Error: if must take three arguments" )
        }
    }

    fn reset_variable(expressions:~[Expression], environment:@Environment, function:~str) -> Expression {
        match expressions {
            [_, symbol, value] => {
                match copy symbol {
                    Symbol( key ) => {
                        environment.reset(key, eval(value, environment).first());
                        symbol
                    }
                    _ => Error( fmt!("Syntax Error: %s takes a symbol as its first argument", function) )
                }
            }
            _ => Error( fmt!("Syntax Error: %s must take two arguments", function) )
        }
    }

    fn define_variable(expressions:~[Expression], environment:@Environment, function:~str) -> Expression {
        match expressions {
            [_, symbol, value] => {
                match copy symbol {
                    Symbol( key ) => {
                        environment.define(key, eval(value, environment).first());
                        symbol
                    }
                    _ => Error( fmt!("Syntax Error: %s takes a symbol as its first argument", function) )
                }
            }
            _ => Error( fmt!("Syntax Error: %s must take two arguments", function) )
        }
    }

    fn define(expressions:~[Expression], environment:@Environment) -> Expression {
        define_variable(expressions, environment, ~"define")
    }

    fn set_bang(expressions:~[Expression], environment:@Environment) -> Expression {
        match copy expressions {
            [_, symbol, _] => {
                match copy symbol {
                    Symbol( key ) => {
                        match environment.lookup( copy key )  {
                            None => return Error( ~"Syntax Error: set! cannot create a variable" ),
                            _ => ()
                        }
                    }
                    _ => ()
                }
            }
            _ => ()
        }
        let symbol = reset_variable(expressions, environment, ~"set!");
        match copy symbol {
            Symbol( key ) => {
                match environment.lookup( copy key ) {
                    Some( value ) => value,
                    None => Error( ~"System Error: Reset variable failed to set the new value" )
                }
            }
            _ => Error( ~"System Error: Reset variable didn't return a symbol" )
        }
    }

    fn proc(expressions:~[Expression], environment:@Environment) -> Expression {
        let exprs = expressions.map(|&expr| eval(expr, environment).first());
        match exprs.head() {
            Proc( procedure, _ ) => procedure( exprs.tail(), environment ),
            Lambda( expr, variables, env ) => {
                let local_env = @Environment::new( *env );
                for vec::zip(copy variables, exprs.tail()).each |param| {
                    match param.first() {
                        Symbol(key) => local_env.define(key, param.second()),
                        _ => return Error( ~"Variable names must be symbols" )
                    }
                }
                eval( *expr, local_env).first()
            }
            _ => Error( fmt!("\"%s\" is not a procedure", exprs.head().to_str()) )
        }
    }

    fn lambda(expressions:~[Expression], env:@Environment) -> Expression {
        match copy expressions {
            [_, List(param_names), expression] => {
                Lambda(@expression, param_names, env)
            }
//            new_proc( |param_values, _| {
//                    let local_env = @Environment::new( copy *env );
//                    for vec::zip(copy param_names, param_values).each |param| {
//                        match param.first() {
//                            Symbol(key) => local_env.define( key, param.second() ),
//                            _ => return Error( ~"lambda params list must be list of symbols" )
//                        }
//                    }
//                    eval(copy expression, local_env).first()
//                } ),
            _ => Error( fmt!("Syntax Error: lambda requires 2 arguments, got \"%u\"", expressions.len()-1 ) )
        }
    }

    (match copy expression {
        List( expressions ) => {
            if expressions.len() == 0 {
                expression
            } else {
                match expressions[0] {
                    Symbol(~"quote") => quote(expressions),
                    Symbol(~"begin") => begin(expressions, environment),
                    Symbol(~"if") => if_(expressions, environment),
                    Symbol(~"define") => define(expressions, environment),
                    Symbol(~"set!") => set_bang(expressions, environment),
                    Symbol(~"lambda") => lambda(expressions, environment),
                    _ => proc(expressions, environment)
                }
            }
        }
        Symbol( symbol ) => {
            match environment.lookup( copy symbol ) {
                Some( value ) => value,
                None => Error( fmt!("Undefined symbol %s",symbol) )
            }
        }
        _ => {
            expression
        }
    }, environment)
}

fn main() {
    fn evaluate( expr:~str, env:Environment ) -> Option<Environment> {
        let (result, new_env) = eval( parse(expr), @env );
        io::println( fmt!("%s -> %s", expr, result.to_str() ));
        Some(*new_env)
    }

    let mut env = Environment::new_global_environment();
    loop {
        io::print("rusty> ");
        let in = io::stdin().read_line();
        let result = evaluate( in, copy env );
        match result {
            Some(new_env) => env = new_env,
            None => ()
        }
    }
}
<|endoftext|>


<commit_before>#[cfg(any(target_os = "android", target_os = "linux"))]
pub use self::sched_linux_like::*;

#[cfg(any(target_os = "android", target_os = "linux"))]
mod sched_linux_like {
    use errno::Errno;
    use libc::{self, c_int, c_void};
    use std::mem;
    use std::option::Option;
    use std::os::unix::io::RawFd;
    use unistd::Pid;
    use {Error, Result};

    // For some functions taking with a parameter of type CloneFlags,
    // only a subset of these flags have an effect.
    libc_bitflags! {
        pub struct CloneFlags: c_int {
            CLONE_VM;
            CLONE_FS;
            CLONE_FILES;
            CLONE_SIGHAND;
            CLONE_PTRACE;
            CLONE_VFORK;
            CLONE_PARENT;
            CLONE_THREAD;
            CLONE_NEWNS;
            CLONE_SYSVSEM;
            CLONE_SETTLS;
            CLONE_PARENT_SETTID;
            CLONE_CHILD_CLEARTID;
            CLONE_DETACHED;
            CLONE_UNTRACED;
            CLONE_CHILD_SETTID;
            CLONE_NEWCGROUP;
            CLONE_NEWUTS;
            CLONE_NEWIPC;
            CLONE_NEWUSER;
            CLONE_NEWPID;
            CLONE_NEWNET;
            CLONE_IO;
        }
    }

    pub type CloneCb<'a> = Box<FnMut() -> isize + 'a>;

    #[repr(C)]
    #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
    pub struct CpuSet {
        cpu_set: libc::cpu_set_t,
    }

    impl CpuSet {
        pub fn new() -> CpuSet {
            CpuSet {
                cpu_set: unsafe { mem::zeroed() },
            }
        }

        pub fn is_set(&self, field: usize) -> Result<bool> {
            if field >= 8 * mem::size_of::<libc::cpu_set_t>() {
                Err(Error::Sys(Errno::EINVAL))
            } else {
                Ok(unsafe { libc::CPU_ISSET(field, &self.cpu_set) })
            }
        }

        pub fn set(&mut self, field: usize) -> Result<()> {
            if field >= 8 * mem::size_of::<libc::cpu_set_t>() {
                Err(Error::Sys(Errno::EINVAL))
            } else {
                Ok(unsafe { libc::CPU_SET(field, &mut self.cpu_set) })
            }
        }

        pub fn unset(&mut self, field: usize) -> Result<()> {
            if field >= 8 * mem::size_of::<libc::cpu_set_t>() {
                Err(Error::Sys(Errno::EINVAL))
            } else {
                Ok(unsafe { libc::CPU_CLR(field, &mut self.cpu_set) })
            }
        }
    }

    pub fn sched_setaffinity(pid: Pid, cpuset: &CpuSet) -> Result<()> {
        let res = unsafe {
            libc::sched_setaffinity(
                pid.into(),
                mem::size_of::<CpuSet>() as libc::size_t,
                &cpuset.cpu_set,
            )
        };

        Errno::result(res).map(drop)
    }

    /// Explicitly yield the processor to other threads.
    ///
    /// [Further reading](http://pubs.opengroup.org/onlinepubs/9699919799/functions/sched_yield.html)
    pub fn sched_yield() -> Result<()> {
        let res = unsafe { libc::sched_yield() };

        Errno::result(res).map(drop)
    }

    pub fn clone(
        mut cb: CloneCb,
        stack: &mut [u8],
        flags: CloneFlags,
        signal: Option<c_int>,
    ) -> Result<Pid> {
        extern "C" fn callback(data: *mut CloneCb) -> c_int {
            let cb: &mut CloneCb = unsafe { &mut *data };
            (*cb)() as c_int
        }

        let res = unsafe {
            let combined = flags.bits() | signal.unwrap_or(0);
            let ptr = stack.as_mut_ptr().offset(stack.len() as isize);
            let ptr_aligned = ptr.offset((ptr as usize % 16) as isize * -1);
            libc::clone(
                mem::transmute(
                    callback as extern "C" fn(*mut Box<::std::ops::FnMut() -> isize>) -> i32,
                ),
                ptr_aligned as *mut c_void,
                combined,
                &mut cb as *mut _ as *mut c_void,
            )
        };

        Errno::result(res).map(Pid::from_raw)
    }

    pub fn unshare(flags: CloneFlags) -> Result<()> {
        let res = unsafe { libc::unshare(flags.bits()) };

        Errno::result(res).map(drop)
    }

    pub fn setns(fd: RawFd, nstype: CloneFlags) -> Result<()> {
        let res = unsafe { libc::setns(fd, nstype.bits()) };

        Errno::result(res).map(drop)
    }
}
<commit_msg>Enable sched_yield for all *nix hosts<commit_after>use libc;
use {Errno, Result};

#[cfg(any(target_os = "android", target_os = "linux"))]
pub use self::sched_linux_like::*;

#[cfg(any(target_os = "android", target_os = "linux"))]
mod sched_linux_like {
    use errno::Errno;
    use libc::{self, c_int, c_void};
    use std::mem;
    use std::option::Option;
    use std::os::unix::io::RawFd;
    use unistd::Pid;
    use {Error, Result};

    // For some functions taking with a parameter of type CloneFlags,
    // only a subset of these flags have an effect.
    libc_bitflags! {
        pub struct CloneFlags: c_int {
            CLONE_VM;
            CLONE_FS;
            CLONE_FILES;
            CLONE_SIGHAND;
            CLONE_PTRACE;
            CLONE_VFORK;
            CLONE_PARENT;
            CLONE_THREAD;
            CLONE_NEWNS;
            CLONE_SYSVSEM;
            CLONE_SETTLS;
            CLONE_PARENT_SETTID;
            CLONE_CHILD_CLEARTID;
            CLONE_DETACHED;
            CLONE_UNTRACED;
            CLONE_CHILD_SETTID;
            CLONE_NEWCGROUP;
            CLONE_NEWUTS;
            CLONE_NEWIPC;
            CLONE_NEWUSER;
            CLONE_NEWPID;
            CLONE_NEWNET;
            CLONE_IO;
        }
    }

    pub type CloneCb<'a> = Box<FnMut() -> isize + 'a>;

    #[repr(C)]
    #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
    pub struct CpuSet {
        cpu_set: libc::cpu_set_t,
    }

    impl CpuSet {
        pub fn new() -> CpuSet {
            CpuSet {
                cpu_set: unsafe { mem::zeroed() },
            }
        }

        pub fn is_set(&self, field: usize) -> Result<bool> {
            if field >= 8 * mem::size_of::<libc::cpu_set_t>() {
                Err(Error::Sys(Errno::EINVAL))
            } else {
                Ok(unsafe { libc::CPU_ISSET(field, &self.cpu_set) })
            }
        }

        pub fn set(&mut self, field: usize) -> Result<()> {
            if field >= 8 * mem::size_of::<libc::cpu_set_t>() {
                Err(Error::Sys(Errno::EINVAL))
            } else {
                Ok(unsafe { libc::CPU_SET(field, &mut self.cpu_set) })
            }
        }

        pub fn unset(&mut self, field: usize) -> Result<()> {
            if field >= 8 * mem::size_of::<libc::cpu_set_t>() {
                Err(Error::Sys(Errno::EINVAL))
            } else {
                Ok(unsafe { libc::CPU_CLR(field, &mut self.cpu_set) })
            }
        }
    }

    pub fn sched_setaffinity(pid: Pid, cpuset: &CpuSet) -> Result<()> {
        let res = unsafe {
            libc::sched_setaffinity(
                pid.into(),
                mem::size_of::<CpuSet>() as libc::size_t,
                &cpuset.cpu_set,
            )
        };

        Errno::result(res).map(drop)
    }

    pub fn clone(
        mut cb: CloneCb,
        stack: &mut [u8],
        flags: CloneFlags,
        signal: Option<c_int>,
    ) -> Result<Pid> {
        extern "C" fn callback(data: *mut CloneCb) -> c_int {
            let cb: &mut CloneCb = unsafe { &mut *data };
            (*cb)() as c_int
        }

        let res = unsafe {
            let combined = flags.bits() | signal.unwrap_or(0);
            let ptr = stack.as_mut_ptr().offset(stack.len() as isize);
            let ptr_aligned = ptr.offset((ptr as usize % 16) as isize * -1);
            libc::clone(
                mem::transmute(
                    callback as extern "C" fn(*mut Box<::std::ops::FnMut() -> isize>) -> i32,
                ),
                ptr_aligned as *mut c_void,
                combined,
                &mut cb as *mut _ as *mut c_void,
            )
        };

        Errno::result(res).map(Pid::from_raw)
    }

    pub fn unshare(flags: CloneFlags) -> Result<()> {
        let res = unsafe { libc::unshare(flags.bits()) };

        Errno::result(res).map(drop)
    }

    pub fn setns(fd: RawFd, nstype: CloneFlags) -> Result<()> {
        let res = unsafe { libc::setns(fd, nstype.bits()) };

        Errno::result(res).map(drop)
    }
}

/// Explicitly yield the processor to other threads.
///
/// [Further reading](http://pubs.opengroup.org/onlinepubs/9699919799/functions/sched_yield.html)
pub fn sched_yield() -> Result<()> {
    let res = unsafe { libc::sched_yield() };

    Errno::result(res).map(drop)
}
<|endoftext|>


<commit_before>extern crate rustbox;

use std::collections::{HashMap,VecDeque};
use std::usize;
use rustbox::Key;
use buffer::Buffer;
use mode::{Mode,ModeType};
use point::{Direction,Point};
use point::Direction::*;

#[derive(Eq,PartialEq,Debug,Clone)]
pub enum Action {
    BackwardDelete,
    Cancel,
    ChangeMode(ModeType),
    Delete,
    DeleteLine,
    Insert(char),
    NewLine,
    NewLineAtPoint,
    MoveCursor(Direction),
    PartialKey,
    Paste,
    Repeat,
    Replace(char),
    Save,
    Quit,
    YankLine,
}

pub struct State {
    pub cursor: Point,
    pub buffer: Buffer,
    pub width: usize,
    pub height: usize,
    pub status: Option<String>, // text to be displayed in the bottom bar
    pub keystrokes: Vec<Key>,
    pub mode: ModeType, // current mode

    yanked: VecDeque<Vec<char>>,
    modes: HashMap<ModeType, Mode>, // available modes
    previous_action: Option<Action>,
    last_col: usize,
}

impl State {
    pub fn new(width: usize, height: usize) -> State {
        let mut modes = HashMap::new();
        modes.insert(ModeType::Insert, Mode::insert_mode());
        modes.insert(ModeType::Normal, Mode::normal_mode());
        modes.insert(ModeType::Replace, Mode::replace_mode());

        State {
            cursor: Point::new(0, 0),
            buffer: Buffer::new(),
            width: width,
            height: height,
            status: None,
            modes: modes,
            mode: ModeType::Normal,
            keystrokes: Vec::new(),
            yanked: VecDeque::new(),
            previous_action: None,
            last_col: 0,
        }
    }

    pub fn mode(&self) -> &Mode {
        self.modes.get(&self.mode).expect(format!("Unknown mode {:?}", self.mode).as_ref())
    }

    pub fn handle_key(&mut self, key: rustbox::Key) -> bool {
        self.keystrokes.push(key);

        match self.mode().keys_pressed(self.keystrokes.as_slice()) {
            Some(Action::PartialKey) => false,
            Some(action) => self.execute_action(action),
            None => { self.keystrokes = Vec::new(); false }
        }
    }

    fn execute_action(&mut self, action: Action) -> bool {
        match action {
            Action::Repeat => {
                if let Some(action) = self.previous_action.clone() {
                    self.execute_action(action);
                }
            }
            Action::NewLineAtPoint => {
                self.buffer.split_line(self.cursor);
                self.move_cursor(Down);
                self.move_cursor(BeginningOfLine);
            }
            Action::NewLine => {
                self.buffer.new_line(self.cursor);
                self.move_cursor(Down);
                self.move_cursor(BeginningOfLine);
                self.mode = ModeType::Insert;
            }
            Action::Insert(c) => {
                self.buffer.insert(self.cursor, c);
                self.move_cursor(Right);
            }
            Action::Replace(c) => {
                self.buffer.upsert(self.cursor, c);
                self.move_cursor(Right);
            }
            Action::Delete => {
                let character = self.buffer.delete(self.cursor);
                self.yanked.push_front(vec!(character));
            }
            Action::DeleteLine => {
                let line = self.buffer.delete_line(self.cursor);
                self.yanked.push_front(line);
                self.move_cursor(BeginningOfLine);
            }
            Action::BackwardDelete => {
                self.move_cursor(Left);
                self.buffer.delete(self.cursor);
            }
            Action::Paste => {
                if self.yanked.is_empty() {
                    self.status = Some("Nothing to paste!".to_string());
                } else {
                    self.paste();
                }
            }
            Action::MoveCursor(direction) => {
                self.move_cursor(direction);
            }
            Action::ChangeMode(mode) => {
                if let Some(action) = self.mode().on_exit() {
                    self.execute_action(action);
                }
                self.mode = mode;
            }
            Action::Cancel => {
                self.keystrokes = Vec::new();
                self.execute_action(Action::ChangeMode(ModeType::Normal));
            }
            Action::Save => {
                let bytes = self.buffer.save_file();
                if bytes > 0 {
                    let path = self.buffer.filepath.clone().unwrap(); // We know the filepath is set
                    let status = format!("Saved \"{}\" ({} bytes)", path, bytes);
                    self.status = Some(status);
                }
            }
            Action::YankLine => {
                if let Some(line) = self.buffer.line_at(self.cursor.y) {
                    self.yanked.push_front(line.clone());
                }
            }
            Action::Quit => { return true },
            _ => {},
        }

        if action != Action::Repeat && action != Action::Cancel {
            self.previous_action = Some(action);
        }

        self.keystrokes = Vec::new();
        false
    }

    pub fn move_cursor(&mut self, direction: Direction) {
        let mut cur = self.cursor.with_direction(direction);

        match direction {
            EndOfLine => { cur.x = usize::max_value() } // This is so ugly...
            EndOfFile => { cur.y = self.buffer.line_len() - 1 }
            _ => {}
        }

        cur.x = self.buffer.last_non_empty_col(cur);

        self.cursor = cur;
        self.last_col = cur.y;
    }

    fn paste(&mut self) {
        let mut yanked = self.yanked.front().unwrap().clone();

        if yanked.iter().any(|&c| c == '\n') {
            self.buffer.new_line(self.cursor);
            self.move_cursor(Down);
            self.move_cursor(BeginningOfLine);
            // Remove the \n
            let last = yanked.len() - 1;
            yanked.remove(last);
        } else {
            self.move_cursor(Right);
        }

        self.buffer.insert_text(self.cursor, yanked);
    }
}
<commit_msg>Remove dead code<commit_after>extern crate rustbox;

use std::collections::{HashMap,VecDeque};
use std::usize;
use rustbox::Key;
use buffer::Buffer;
use mode::{Mode,ModeType};
use point::{Direction,Point};
use point::Direction::*;

#[derive(Eq,PartialEq,Debug,Clone)]
pub enum Action {
    BackwardDelete,
    Cancel,
    ChangeMode(ModeType),
    Delete,
    DeleteLine,
    Insert(char),
    NewLine,
    NewLineAtPoint,
    MoveCursor(Direction),
    PartialKey,
    Paste,
    Repeat,
    Replace(char),
    Save,
    Quit,
    YankLine,
}

pub struct State {
    pub cursor: Point,
    pub buffer: Buffer,
    pub width: usize,
    pub height: usize,
    pub status: Option<String>, // text to be displayed in the bottom bar
    pub keystrokes: Vec<Key>,
    pub mode: ModeType, // current mode

    yanked: VecDeque<Vec<char>>,
    modes: HashMap<ModeType, Mode>, // available modes
    previous_action: Option<Action>,
}

impl State {
    pub fn new(width: usize, height: usize) -> State {
        let mut modes = HashMap::new();
        modes.insert(ModeType::Insert, Mode::insert_mode());
        modes.insert(ModeType::Normal, Mode::normal_mode());
        modes.insert(ModeType::Replace, Mode::replace_mode());

        State {
            cursor: Point::new(0, 0),
            buffer: Buffer::new(),
            width: width,
            height: height,
            status: None,
            modes: modes,
            mode: ModeType::Normal,
            keystrokes: Vec::new(),
            yanked: VecDeque::new(),
            previous_action: None,
        }
    }

    pub fn mode(&self) -> &Mode {
        self.modes.get(&self.mode).expect(format!("Unknown mode {:?}", self.mode).as_ref())
    }

    pub fn handle_key(&mut self, key: rustbox::Key) -> bool {
        self.keystrokes.push(key);

        match self.mode().keys_pressed(self.keystrokes.as_slice()) {
            Some(Action::PartialKey) => false,
            Some(action) => self.execute_action(action),
            None => { self.keystrokes = Vec::new(); false }
        }
    }

    fn execute_action(&mut self, action: Action) -> bool {
        match action {
            Action::Repeat => {
                if let Some(action) = self.previous_action.clone() {
                    self.execute_action(action);
                }
            }
            Action::NewLineAtPoint => {
                self.buffer.split_line(self.cursor);
                self.move_cursor(Down);
                self.move_cursor(BeginningOfLine);
            }
            Action::NewLine => {
                self.buffer.new_line(self.cursor);
                self.move_cursor(Down);
                self.move_cursor(BeginningOfLine);
                self.mode = ModeType::Insert;
            }
            Action::Insert(c) => {
                self.buffer.insert(self.cursor, c);
                self.move_cursor(Right);
            }
            Action::Replace(c) => {
                self.buffer.upsert(self.cursor, c);
                self.move_cursor(Right);
            }
            Action::Delete => {
                let character = self.buffer.delete(self.cursor);
                self.yanked.push_front(vec!(character));
            }
            Action::DeleteLine => {
                let line = self.buffer.delete_line(self.cursor);
                self.yanked.push_front(line);
                self.move_cursor(BeginningOfLine);
            }
            Action::BackwardDelete => {
                self.move_cursor(Left);
                self.buffer.delete(self.cursor);
            }
            Action::Paste => {
                if self.yanked.is_empty() {
                    self.status = Some("Nothing to paste!".to_string());
                } else {
                    self.paste();
                }
            }
            Action::MoveCursor(direction) => {
                self.move_cursor(direction);
            }
            Action::ChangeMode(mode) => {
                if let Some(action) = self.mode().on_exit() {
                    self.execute_action(action);
                }
                self.mode = mode;
            }
            Action::Cancel => {
                self.keystrokes = Vec::new();
                self.execute_action(Action::ChangeMode(ModeType::Normal));
            }
            Action::Save => {
                let bytes = self.buffer.save_file();
                if bytes > 0 {
                    let path = self.buffer.filepath.clone().unwrap(); // We know the filepath is set
                    let status = format!("Saved \"{}\" ({} bytes)", path, bytes);
                    self.status = Some(status);
                }
            }
            Action::YankLine => {
                if let Some(line) = self.buffer.line_at(self.cursor.y) {
                    self.yanked.push_front(line.clone());
                }
            }
            Action::Quit => { return true },
            _ => {},
        }

        if action != Action::Repeat && action != Action::Cancel {
            self.previous_action = Some(action);
        }

        self.keystrokes = Vec::new();
        false
    }

    pub fn move_cursor(&mut self, direction: Direction) {
        let mut cur = self.cursor.with_direction(direction);

        match direction {
            EndOfLine => { cur.x = usize::max_value() } // This is so ugly...
            EndOfFile => { cur.y = self.buffer.line_len() - 1 }
            _ => {}
        }

        cur.x = self.buffer.last_non_empty_col(cur);

        self.cursor = cur;
    }

    fn paste(&mut self) {
        let mut yanked = self.yanked.front().unwrap().clone();

        if yanked.iter().any(|&c| c == '\n') {
            self.buffer.new_line(self.cursor);
            self.move_cursor(Down);
            self.move_cursor(BeginningOfLine);
            // Remove the \n
            let last = yanked.len() - 1;
            yanked.remove(last);
        } else {
            self.move_cursor(Right);
        }

        self.buffer.insert_text(self.cursor, yanked);
    }
}
<|endoftext|>


<commit_before>extern crate clipboard;
use self::clipboard::{ClipboardProvider, ClipboardContext};

use super::keybinds::*;
use super::file::*;

pub struct OneLinerState {
    pub command: Command,
    pub label: &'static str,
    pub file: File,
}

impl From<Command> for OneLinerState {
    fn from(c: Command) -> OneLinerState {
        let label = match c {
            Command::Quit | Command::CloseFile | Command::Refresh |
                Command::Cut | Command::Copy | Command::Paste |
                Command::Undo | Command::Redo |
                Command::NewTab => "",
            Command::OpenFile => "Open file:",
            Command::SaveFile => "Save file:",
            Command::Goto => "Jump to line[:col]:",
            Command::Find => "Find text:",
            Command::FindReplace => "AAAAAAAAAA",
        };
        OneLinerState {
            command: c,
            label: label,
            file: File::empty(),
        }
    }
}

impl OneLinerState {
    pub fn value(&self) -> &String {
        &self.file.lines[0]
    }

    pub fn tab(&mut self) {
        // TODO figure out if any of this is a good idea
        use std::path::PathBuf;
        let mut path = PathBuf::from(self.value());
        if path.file_name().is_none() {
            path = PathBuf::from(".");
        }
        let mut fragment = String::from("");
        if !path.is_dir() {
            fragment = path.file_name().unwrap().to_os_string().into_string().unwrap();
            path.pop();
        }
        let results: Vec<String> = path.read_dir().expect("did not get a dir").filter_map(|e| e.ok().and_then(|e| {
            let name = e.file_name().into_string().unwrap();
            if name.starts_with(&fragment) {
                Some(name)
            } else {
                None
            }
        })).collect();
        if results.len() == 1 {
            path.push(results[0].clone());
            if path.is_dir() {
                path.push("");
            }
            self.file.lines[0] = path.into_os_string().into_string().unwrap();
            self.file.move_cursor_end((9001, 9001));
            self.file.display_dirty = true;
        }
    }
}

pub struct EditorState {
    pub keys: KeybindTable,
    pub files: Vec<File>,
    pub one_liners: Vec<Option<OneLinerState>>,
    pub active_file: usize,
}

/* Man, I hate Rust sometimes.
 * This used to look like
 *     match self.one_liner_mut() {
 *         &mut Some(ref mut ols) => ols.file.$func(dim),
 *         &mut None => self.active_file_mut().$func(dim)
 *     }
 * but both _mut() calls borrow self mutably, and apparently that's not legal.
 * Even though I'm done with the borrow I'm matching on by the time I get to that arm.
 *
 * Too much research later, apparently they're working on it: https://github.com/rust-lang/rfcs/issues/811
 */

macro_rules! split_func {
    ($func:ident -> $ret:ty) => {
        pub fn $func(&mut self, dim: (i32, i32)) -> $ret {
            match self.one_liner_mut() {
                &mut Some(ref mut ols) => return ols.file.$func(dim),
                _ => ()
            }
            self.active_file_mut().$func(dim)
        }
    };
    ($func:ident) => {split_func!($func -> ());};
}

macro_rules! restrict_func {
    ($func:ident -> $ret:ty, $default:expr) => {
        pub fn $func(&mut self, dim: (i32, i32)) -> $ret {
            match self.one_liner() {
                &Some(_) => $default,
                &None => self.active_file_mut().$func(dim)
            }
        }
    };
    ($func:ident) => {restrict_func!($func -> (), ());};
}

impl EditorState {
    pub fn one_liner_active(&self) -> bool {
        self.one_liner().is_some()
    }

    pub fn consume_one_liner(&mut self) -> Option<(Command, String)> {
        let result = self.one_liner_mut().take().map(|ol| (ol.command.clone(), (*ol.value()).clone()));
        result
    }

    pub fn lineno_chars(&self) -> i32 {
        self.active_file().lineno_chars()
    }

    pub fn active_file(&self) -> &File {
        &self.files[self.active_file]
    }

    pub fn active_file_mut(&mut self) -> &mut File {
        &mut self.files[self.active_file]
    }

    pub fn one_liner(&self) -> &Option<OneLinerState> {
        &self.one_liners[self.active_file]
    }

    pub fn one_liner_mut(&mut self) -> &mut Option<OneLinerState> {
        &mut self.one_liners[self.active_file]
    }

    pub fn set_one_liner(&mut self, ol: OneLinerState) {
        self.one_liners[self.active_file] = Some(ol);
    }

    pub fn cursor(&self, dim: (i32, i32)) -> (i32, i32) {
        match self.one_liner() {
            &Some(ref ols) => {
                let cursor = ols.file.cursor(dim);
                (cursor.x + ols.label.len() as i32 + 1, dim.1 + 1)
            },
            &None => {
                let cursor = self.active_file().cursor(dim);
                (cursor.x + self.active_file().lineno_chars() + 1, cursor.y)
            }
        }
    }

    pub fn debug(&self, dim: (i32, i32)) -> String {
        self.active_file().debug(dim)
    }

    pub fn chunked_text(&self, dim: (i32, i32)) -> Vec<(Option<u16>, Vec<TextChunk>)> {
        self.active_file().chunked_text(dim)
    }

    pub fn refresh(&mut self, dim: (i32, i32)) {
        self.active_file_mut().refresh(dim)
    }

    pub fn new_tab(&mut self) {
        self.active_file += 1;
        self.files.insert(self.active_file, File::empty());
        self.one_liners.insert(self.active_file, None);
    }

    pub fn close_tab(&mut self) {
        self.files.remove(self.active_file);
        self.one_liners.remove(self.active_file);
        if self.files.len() > 0 {
            self.active_file %= self.files.len();
        }
    }

    pub fn next_tab(&mut self) {
        self.active_file = (self.active_file + 1) % self.files.len();
    }

    pub fn cut(&mut self, dim: (i32, i32)) {
        if self.active_file().has_selection() {
            self.copy(dim);
            self.delete(dim);
        }
    }

    pub fn copy(&mut self, _: (i32, i32)) {
        if self.active_file().has_selection() {
            let selection = self.active_file().selected_text();
            let mut ctx: ClipboardContext = ClipboardProvider::new().unwrap();
            ctx.set_contents(selection).unwrap();
        }
    }

    pub fn paste(&mut self, dim: (i32, i32)) {
        let mut ctx: ClipboardContext = ClipboardProvider::new().unwrap();
        let clipboard = ctx.get_contents().unwrap();
        for c in clipboard.chars() {
            if c == '\n' {
                self.insert_newline(dim, false);
            } else {
                self.insert(dim, c);
            }
        }
    }

    pub fn select(&mut self) {
        match self.one_liner_mut() {
            &mut Some(ref mut ols) => return ols.file.select(),
            _ => ()
        }
        self.active_file_mut().select();
    }

    pub fn deselect(&mut self) {
        match self.one_liner_mut() {
            &mut Some(ref mut ols) => return ols.file.deselect(),
            _ => ()
        }
        self.active_file_mut().deselect();
    }

    pub fn display_dirty(&self) -> bool {
        match self.one_liner() {
            &Some(ref ols) => ols.file.display_dirty,
            _ => self.active_file().display_dirty
        }
    }

    pub fn clean_display(&mut self) {
        match self.one_liner_mut() {
            &mut Some(ref mut ols) => {
                ols.file.display_dirty = false;
                return;
            },
            _ => ()
        }
        self.active_file_mut().display_dirty = false;
    }

    split_func!(move_cursor_left);
    split_func!(move_cursor_right);
    restrict_func!(move_cursor_up);
    restrict_func!(move_cursor_down);
    split_func!(move_cursor_home);
    split_func!(move_cursor_end);
    restrict_func!(page_up);
    restrict_func!(page_down);
    restrict_func!(scroll_up);
    restrict_func!(scroll_down);

    pub fn move_cursor_to(&mut self, dim: (i32, i32), dest: (i32, i32)) {
        let mut last_cursor = (-1, -1);
        while self.cursor(dim).1 < dest.1 && self.cursor(dim) != last_cursor {
            last_cursor = self.cursor(dim);
            self.move_cursor_down(dim);
        }
        while self.cursor(dim).1 > dest.1 && self.cursor(dim) != last_cursor {
            last_cursor = self.cursor(dim);
            self.move_cursor_up(dim);
        }
        self.move_cursor_end(dim);
        last_cursor = (-1, -1);
        while dest.0 < self.cursor(dim).0 && self.cursor(dim) != last_cursor {
            last_cursor = self.cursor(dim);
            self.move_cursor_left(dim);
        }
    }

    pub fn goto(&mut self, dim: (i32, i32), target: &str) {
        let colon_idx = target.find(':');
        let (row, col) = match colon_idx {
            Some(n) => {
                let (row_str, col_str) = target.split_at(n);
                let (_, col_str) = col_str.split_at(1);
                (i32::from_str_radix(row_str, 10).unwrap(), i32::from_str_radix(col_str, 10).unwrap())
            },
            None => {
                (i32::from_str_radix(target, 10).unwrap(), self.active_file().caret.x)
            }
        };
        let row = if row < 1 {
            1
        } else if row > self.active_file().lines.len() as i32 {
            self.active_file().lines.len() as i32
        } else {
            row
        };
        self.active_file_mut().goto(dim, (row, col));
    }

    pub fn insert_newline(&mut self, dim: (i32, i32), indent: bool) {
        match self.one_liner() {
            &Some(_) => panic!("Can't insert newline in one liner! That's the point!"),
            &None => self.active_file_mut().insert_newline(dim, indent)
        }
    }

    split_func!(delete);
    split_func!(backspace);
    restrict_func!(tab);

    pub fn insert(&mut self, dim: (i32, i32), c: char) {
        match self.one_liner_mut() {
            &mut Some(ref mut ols) => return ols.file.insert(dim, c),
            _ => ()
        };
        self.active_file_mut().insert(dim, c)
    }

    pub fn save_file(&mut self, path: &str) {
        self.active_file_mut().save(path);
    }

    pub fn open_file(&mut self, path: &str) {
        self.files[self.active_file] = File::open(path);
    }
}
<commit_msg>Make tab completion better<commit_after>extern crate clipboard;
use self::clipboard::{ClipboardProvider, ClipboardContext};

use super::keybinds::*;
use super::file::*;

pub struct OneLinerState {
    pub command: Command,
    pub label: &'static str,
    pub file: File,
}

impl From<Command> for OneLinerState {
    fn from(c: Command) -> OneLinerState {
        let label = match c {
            Command::Quit | Command::CloseFile | Command::Refresh |
                Command::Cut | Command::Copy | Command::Paste |
                Command::Undo | Command::Redo |
                Command::NewTab => "",
            Command::OpenFile => "Open file:",
            Command::SaveFile => "Save file:",
            Command::Goto => "Jump to line[:col]:",
            Command::Find => "Find text:",
            Command::FindReplace => "AAAAAAAAAA",
        };
        OneLinerState {
            command: c,
            label: label,
            file: File::empty(),
        }
    }
}

impl OneLinerState {
    pub fn value(&self) -> &String {
        &self.file.lines[0]
    }

    pub fn tab(&mut self) {
        use std::path::PathBuf;
        let mut path = PathBuf::from(self.value());
        if path.file_name().is_none() {
            path = PathBuf::from(".");
        }
        let mut fragment = String::from("");
        if !path.is_dir() {
            fragment = path.file_name().unwrap().to_os_string().into_string().unwrap();
            path.pop();
        }
        let results: Vec<String> = path.read_dir().expect("did not get a dir").filter_map(|e| e.ok().and_then(|e| {
            let name = e.file_name().into_string().unwrap();
            if name.starts_with(&fragment) {
                Some(name)
            } else {
                None
            }
        })).collect();
        if results.len() > 0 {
            let first = results[0].clone();
            let shared = results.iter().fold(first, |a, b| {
                a.chars().zip(b.chars()).take_while(|a| a.0 == a.1).map(|a| a.0).collect::<String>()
            });
            if shared.len() > 0 {
                path.push(shared.clone());
                if path.is_dir() && results.len() == 1 {
                    path.push("");
                }
                self.file.lines[0] = path.into_os_string().into_string().unwrap();
                self.file.move_cursor_end((9001, 9001));
                self.file.display_dirty = true;
            }
        }
    }
}

pub struct EditorState {
    pub keys: KeybindTable,
    pub files: Vec<File>,
    pub one_liners: Vec<Option<OneLinerState>>,
    pub active_file: usize,
}

/* Man, I hate Rust sometimes.
 * This used to look like
 *     match self.one_liner_mut() {
 *         &mut Some(ref mut ols) => ols.file.$func(dim),
 *         &mut None => self.active_file_mut().$func(dim)
 *     }
 * but both _mut() calls borrow self mutably, and apparently that's not legal.
 * Even though I'm done with the borrow I'm matching on by the time I get to that arm.
 *
 * Too much research later, apparently they're working on it: https://github.com/rust-lang/rfcs/issues/811
 */

macro_rules! split_func {
    ($func:ident -> $ret:ty) => {
        pub fn $func(&mut self, dim: (i32, i32)) -> $ret {
            match self.one_liner_mut() {
                &mut Some(ref mut ols) => return ols.file.$func(dim),
                _ => ()
            }
            self.active_file_mut().$func(dim)
        }
    };
    ($func:ident) => {split_func!($func -> ());};
}

macro_rules! restrict_func {
    ($func:ident -> $ret:ty, $default:expr) => {
        pub fn $func(&mut self, dim: (i32, i32)) -> $ret {
            match self.one_liner() {
                &Some(_) => $default,
                &None => self.active_file_mut().$func(dim)
            }
        }
    };
    ($func:ident) => {restrict_func!($func -> (), ());};
}

impl EditorState {
    pub fn one_liner_active(&self) -> bool {
        self.one_liner().is_some()
    }

    pub fn consume_one_liner(&mut self) -> Option<(Command, String)> {
        let result = self.one_liner_mut().take().map(|ol| (ol.command.clone(), (*ol.value()).clone()));
        result
    }

    pub fn lineno_chars(&self) -> i32 {
        self.active_file().lineno_chars()
    }

    pub fn active_file(&self) -> &File {
        &self.files[self.active_file]
    }

    pub fn active_file_mut(&mut self) -> &mut File {
        &mut self.files[self.active_file]
    }

    pub fn one_liner(&self) -> &Option<OneLinerState> {
        &self.one_liners[self.active_file]
    }

    pub fn one_liner_mut(&mut self) -> &mut Option<OneLinerState> {
        &mut self.one_liners[self.active_file]
    }

    pub fn set_one_liner(&mut self, ol: OneLinerState) {
        self.one_liners[self.active_file] = Some(ol);
    }

    pub fn cursor(&self, dim: (i32, i32)) -> (i32, i32) {
        match self.one_liner() {
            &Some(ref ols) => {
                let cursor = ols.file.cursor(dim);
                (cursor.x + ols.label.len() as i32 + 1, dim.1 + 1)
            },
            &None => {
                let cursor = self.active_file().cursor(dim);
                (cursor.x + self.active_file().lineno_chars() + 1, cursor.y)
            }
        }
    }

    pub fn debug(&self, dim: (i32, i32)) -> String {
        self.active_file().debug(dim)
    }

    pub fn chunked_text(&self, dim: (i32, i32)) -> Vec<(Option<u16>, Vec<TextChunk>)> {
        self.active_file().chunked_text(dim)
    }

    pub fn refresh(&mut self, dim: (i32, i32)) {
        self.active_file_mut().refresh(dim)
    }

    pub fn new_tab(&mut self) {
        self.active_file += 1;
        self.files.insert(self.active_file, File::empty());
        self.one_liners.insert(self.active_file, None);
    }

    pub fn close_tab(&mut self) {
        self.files.remove(self.active_file);
        self.one_liners.remove(self.active_file);
        if self.files.len() > 0 {
            self.active_file %= self.files.len();
        }
    }

    pub fn next_tab(&mut self) {
        self.active_file = (self.active_file + 1) % self.files.len();
    }

    pub fn cut(&mut self, dim: (i32, i32)) {
        if self.active_file().has_selection() {
            self.copy(dim);
            self.delete(dim);
        }
    }

    pub fn copy(&mut self, _: (i32, i32)) {
        if self.active_file().has_selection() {
            let selection = self.active_file().selected_text();
            let mut ctx: ClipboardContext = ClipboardProvider::new().unwrap();
            ctx.set_contents(selection).unwrap();
        }
    }

    pub fn paste(&mut self, dim: (i32, i32)) {
        let mut ctx: ClipboardContext = ClipboardProvider::new().unwrap();
        let clipboard = ctx.get_contents().unwrap();
        for c in clipboard.chars() {
            if c == '\n' {
                self.insert_newline(dim, false);
            } else {
                self.insert(dim, c);
            }
        }
    }

    pub fn select(&mut self) {
        match self.one_liner_mut() {
            &mut Some(ref mut ols) => return ols.file.select(),
            _ => ()
        }
        self.active_file_mut().select();
    }

    pub fn deselect(&mut self) {
        match self.one_liner_mut() {
            &mut Some(ref mut ols) => return ols.file.deselect(),
            _ => ()
        }
        self.active_file_mut().deselect();
    }

    pub fn display_dirty(&self) -> bool {
        match self.one_liner() {
            &Some(ref ols) => ols.file.display_dirty,
            _ => self.active_file().display_dirty
        }
    }

    pub fn clean_display(&mut self) {
        match self.one_liner_mut() {
            &mut Some(ref mut ols) => {
                ols.file.display_dirty = false;
                return;
            },
            _ => ()
        }
        self.active_file_mut().display_dirty = false;
    }

    split_func!(move_cursor_left);
    split_func!(move_cursor_right);
    restrict_func!(move_cursor_up);
    restrict_func!(move_cursor_down);
    split_func!(move_cursor_home);
    split_func!(move_cursor_end);
    restrict_func!(page_up);
    restrict_func!(page_down);
    restrict_func!(scroll_up);
    restrict_func!(scroll_down);

    pub fn move_cursor_to(&mut self, dim: (i32, i32), dest: (i32, i32)) {
        let mut last_cursor = (-1, -1);
        while self.cursor(dim).1 < dest.1 && self.cursor(dim) != last_cursor {
            last_cursor = self.cursor(dim);
            self.move_cursor_down(dim);
        }
        while self.cursor(dim).1 > dest.1 && self.cursor(dim) != last_cursor {
            last_cursor = self.cursor(dim);
            self.move_cursor_up(dim);
        }
        self.move_cursor_end(dim);
        last_cursor = (-1, -1);
        while dest.0 < self.cursor(dim).0 && self.cursor(dim) != last_cursor {
            last_cursor = self.cursor(dim);
            self.move_cursor_left(dim);
        }
    }

    pub fn goto(&mut self, dim: (i32, i32), target: &str) {
        let colon_idx = target.find(':');
        let (row, col) = match colon_idx {
            Some(n) => {
                let (row_str, col_str) = target.split_at(n);
                let (_, col_str) = col_str.split_at(1);
                (i32::from_str_radix(row_str, 10).unwrap(), i32::from_str_radix(col_str, 10).unwrap())
            },
            None => {
                (i32::from_str_radix(target, 10).unwrap(), self.active_file().caret.x)
            }
        };
        let row = if row < 1 {
            1
        } else if row > self.active_file().lines.len() as i32 {
            self.active_file().lines.len() as i32
        } else {
            row
        };
        self.active_file_mut().goto(dim, (row, col));
    }

    pub fn insert_newline(&mut self, dim: (i32, i32), indent: bool) {
        match self.one_liner() {
            &Some(_) => panic!("Can't insert newline in one liner! That's the point!"),
            &None => self.active_file_mut().insert_newline(dim, indent)
        }
    }

    split_func!(delete);
    split_func!(backspace);
    restrict_func!(tab);

    pub fn insert(&mut self, dim: (i32, i32), c: char) {
        match self.one_liner_mut() {
            &mut Some(ref mut ols) => return ols.file.insert(dim, c),
            _ => ()
        };
        self.active_file_mut().insert(dim, c)
    }

    pub fn save_file(&mut self, path: &str) {
        self.active_file_mut().save(path);
    }

    pub fn open_file(&mut self, path: &str) {
        self.files[self.active_file] = File::open(path);
    }
}
<|endoftext|>


<commit_before>// Copyright © 2016, Canal TP and/or its affiliates. All rights reserved.
//
// This file is part of Navitia,
//     the software to build cool stuff with public transport.
//
// Hope you'll enjoy and contribute to this project,
//     powered by Canal TP (www.canaltp.fr).
// Help us simplify mobility and open public transport:
//     a non ending quest to the responsive locomotion way of traveling!
//
// LICENCE: This program is free software; you can redistribute it
// and/or modify it under the terms of the GNU Affero General Public
// License as published by the Free Software Foundation, either
// version 3 of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public
// License along with this program. If not, see
// <http://www.gnu.org/licenses/>.
//
// Stay tuned using
// twitter @navitia
// IRC #navitia on freenode
// https://groups.google.com/d/forum/navitia
// www.navitia.io

/// In this module we put the code related to stops, that need to draw on 'places', 'mimir',
/// 'common', and 'config' (ie all the workspaces that make up mimirsbrunn).
use futures::stream::{Stream, TryStreamExt};
use mimir::domain::model::configuration::ContainerConfig;
use snafu::{ResultExt, Snafu};
use std::collections::{hash_map::DefaultHasher, HashMap};
use std::hash::BuildHasherDefault;
use std::ops::Deref;
use std::path::PathBuf;
use std::sync::Arc;
use tracing::info;

use crate::admin_geofinder::AdminGeoFinder;
use crate::labels;
use mimir::adapters::secondary::elasticsearch::{self, ElasticsearchStorage};
use mimir::domain::ports::primary::{generate_index::GenerateIndex, list_documents::ListDocuments};
use places::admin::Admin;
use places::stop::Stop;

#[derive(Debug, Snafu)]
pub enum Error {
    // #[snafu(display("Settings (Configuration or CLI) Error: {}", source))]
    // Settings { source: settings::Error },
    #[snafu(display("Elasticsearch Connection Pool {}", source))]
    ElasticsearchPool {
        source: elasticsearch::remote::Error,
    },

    #[snafu(display("Elasticsearch Connection Pool {}", source))]
    ElasticsearchConnection {
        source: mimir::domain::ports::secondary::remote::Error,
    },

    // #[snafu(display("Cosmogony Error: {}", details))]
    // Cosmogony { details: String },
    #[snafu(display("Index Generation Error {}", source))]
    IndexGeneration {
        source: mimir::domain::model::error::Error,
    },

    // transit_model uses failure::Error, which does not implement std::Error, so
    // we use a String to get the error message instead.
    #[snafu(display("Transit Model Error {}", details))]
    TransitModel { details: String },

    #[snafu(display("Admin Retrieval Error {}", details))]
    AdminRetrieval { details: String },
}

pub fn initialize_weights<'a, It, S: ::std::hash::BuildHasher>(
    stops: It,
    nb_stop_points: &HashMap<String, u32, S>,
) where
    It: Iterator<Item = &'a mut Stop>,
{
    let max = *nb_stop_points.values().max().unwrap_or(&1) as f64;
    for stop in stops {
        stop.weight = if let Some(weight) = nb_stop_points.get(&stop.id) {
            *weight as f64 / max
        } else {
            0.0
        };
    }
}

fn attach_stop(stop: &mut Stop, admins: Vec<Arc<Admin>>) {
    let admins_iter = admins.iter().map(|a| a.deref());
    let country_codes = places::admin::find_country_codes(admins_iter.clone());

    stop.label = labels::format_stop_label(&stop.name, admins_iter, &country_codes);
    stop.zip_codes = places::admin::get_zip_codes_from_admins(&admins);

    stop.country_codes = country_codes;
    stop.administrative_regions = admins;
}

/// Attach the stops to administrative regions
///
/// The admins are loaded from Elasticsearch and stored in a quadtree
/// We attach a stop with all the admins that have a boundary containing
/// the coordinate of the stop
async fn attach_stops_to_admins<'a, It: Iterator<Item = &'a mut Stop>>(
    stops: It,
    client: &ElasticsearchStorage,
) -> Result<(), Error> {
    match client.list_documents().await {
        Ok(stream) => {
            let admins: Vec<Admin> = stream.try_collect().await.context(IndexGenerationSnafu)?;

            if admins.is_empty() {
                return Err(Error::AdminRetrieval {
                    details: String::from("no admin retrieved to enrich stops"),
                });
            }

            let admins_geofinder = admins.into_iter().collect::<AdminGeoFinder>();

            let mut nb_unmatched = 0u32;
            let mut nb_matched = 0u32;
            // FIXME Opportunity for concurrent work
            for mut stop in stops {
                let admins = admins_geofinder.get(&stop.coord);

                if admins.is_empty() {
                    nb_unmatched += 1;
                } else {
                    nb_matched += 1;
                }

                attach_stop(&mut stop, admins);
            }

            info!(
                "there are {}/{} stops without any admin",
                nb_unmatched,
                nb_matched + nb_unmatched
            );
            Ok(())
        }
        Err(_) => Err(Error::AdminRetrieval {
            details: String::from("Could not retrieve admins to enrich stops"),
        }),
    }
}

/// Stores the stops found in the 'input' directory, in Elasticsearch, with the given
/// configuration.
///
/// The main part of this function is to actually create a list of stops
/// from the information found in the NTFS directory.
pub async fn index_ntfs(
    input: PathBuf,
    config: &ContainerConfig,
    client: &ElasticsearchStorage,
) -> Result<(), Error> {
    let navitia = transit_model::ntfs::read(&input).map_err(|err| Error::TransitModel {
        details: format!(
            "Could not read transit model from {}: {}",
            input.display(),
            err.to_string()
        ),
    })?;

    let nb_stop_points: HashMap<String, u32, BuildHasherDefault<DefaultHasher>> = navitia
        .stop_areas
        .iter()
        .map(|(idx, sa)| {
            let id = places::utils::normalize_id("stop_area", &sa.id);
            let nb_stop_points = navitia
                .get_corresponding_from_idx::<_, transit_model::objects::StopPoint>(idx)
                .len();
            (id, nb_stop_points as u32)
        })
        .collect();

    let mut stops: Vec<Stop> = navitia
        .stop_areas
        .iter()
        .map(|(idx, sa)| places::stop::to_mimir(idx, sa, &navitia))
        .collect();

    initialize_weights(stops.iter_mut(), &nb_stop_points);

    attach_stops_to_admins(stops.iter_mut(), client).await?;

    // FIXME Should be done concurrently (for_each_concurrent....)
    for stop in &mut stops {
        stop.coverages.push(config.dataset.clone());
        let mut admin_weight = stop
            .administrative_regions
            .iter()
            .filter(|adm| adm.is_city())
            .map(|adm| adm.weight)
            .next()
            .unwrap_or(0.0);
        // FIXME: 1024, automagic!
        // It's a factor used to bring the stop weight and the admin weight in the same order of
        // magnitude...
        // We then use a log to compress the distance between low admin weight and high ones.
        admin_weight = admin_weight * 1024.0 + 1.0;
        admin_weight = admin_weight.log10();
        stop.weight = (stop.weight + admin_weight) / 2.0;
    }

    tracing::info!("Beginning to import stops into elasticsearch.");
    import_stops(client, config, futures::stream::iter(stops)).await
}

// FIXME Should not be ElasticsearchStorage, but rather a trait GenerateIndex
pub async fn import_stops<S>(
    client: &ElasticsearchStorage,
    config: &ContainerConfig,
    stops: S,
) -> Result<(), Error>
where
    S: Stream<Item = Stop> + Send + Sync + Unpin + 'static,
{
    client
        .generate_index(config, stops)
        .await
        .context(IndexGenerationSnafu)?;

    Ok(())
}
<commit_msg>logs added<commit_after>// Copyright © 2016, Canal TP and/or its affiliates. All rights reserved.
//
// This file is part of Navitia,
//     the software to build cool stuff with public transport.
//
// Hope you'll enjoy and contribute to this project,
//     powered by Canal TP (www.canaltp.fr).
// Help us simplify mobility and open public transport:
//     a non ending quest to the responsive locomotion way of traveling!
//
// LICENCE: This program is free software; you can redistribute it
// and/or modify it under the terms of the GNU Affero General Public
// License as published by the Free Software Foundation, either
// version 3 of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public
// License along with this program. If not, see
// <http://www.gnu.org/licenses/>.
//
// Stay tuned using
// twitter @navitia
// IRC #navitia on freenode
// https://groups.google.com/d/forum/navitia
// www.navitia.io

/// In this module we put the code related to stops, that need to draw on 'places', 'mimir',
/// 'common', and 'config' (ie all the workspaces that make up mimirsbrunn).
use futures::stream::{Stream, TryStreamExt};
use mimir::domain::model::configuration::ContainerConfig;
use snafu::{ResultExt, Snafu};
use std::collections::{hash_map::DefaultHasher, HashMap};
use std::hash::BuildHasherDefault;
use std::ops::Deref;
use std::path::PathBuf;
use std::sync::Arc;
use tracing::{info, warn};

use crate::admin_geofinder::AdminGeoFinder;
use crate::labels;
use mimir::adapters::secondary::elasticsearch::{self, ElasticsearchStorage};
use mimir::domain::ports::primary::{generate_index::GenerateIndex, list_documents::ListDocuments};
use places::admin::Admin;
use places::stop::Stop;

#[derive(Debug, Snafu)]
pub enum Error {
    // #[snafu(display("Settings (Configuration or CLI) Error: {}", source))]
    // Settings { source: settings::Error },
    #[snafu(display("Elasticsearch Connection Pool {}", source))]
    ElasticsearchPool {
        source: elasticsearch::remote::Error,
    },

    #[snafu(display("Elasticsearch Connection Pool {}", source))]
    ElasticsearchConnection {
        source: mimir::domain::ports::secondary::remote::Error,
    },

    // #[snafu(display("Cosmogony Error: {}", details))]
    // Cosmogony { details: String },
    #[snafu(display("Index Generation Error {}", source))]
    IndexGeneration {
        source: mimir::domain::model::error::Error,
    },

    // transit_model uses failure::Error, which does not implement std::Error, so
    // we use a String to get the error message instead.
    #[snafu(display("Transit Model Error {}", details))]
    TransitModel { details: String },

    #[snafu(display("Admin Retrieval Error {}", details))]
    AdminRetrieval { details: String },
}

pub fn initialize_weights<'a, It, S: ::std::hash::BuildHasher>(
    stops: It,
    nb_stop_points: &HashMap<String, u32, S>,
) where
    It: Iterator<Item = &'a mut Stop>,
{
    let max = *nb_stop_points.values().max().unwrap_or(&1) as f64;
    for stop in stops {
        stop.weight = if let Some(weight) = nb_stop_points.get(&stop.id) {
            *weight as f64 / max
        } else {
            0.0
        };
    }
}

fn attach_stop(stop: &mut Stop, admins: Vec<Arc<Admin>>) {
    let admins_iter = admins.iter().map(|a| a.deref());
    let country_codes = places::admin::find_country_codes(admins_iter.clone());

    stop.label = labels::format_stop_label(&stop.name, admins_iter, &country_codes);
    stop.zip_codes = places::admin::get_zip_codes_from_admins(&admins);

    stop.country_codes = country_codes;
    stop.administrative_regions = admins;
}

/// Attach the stops to administrative regions
///
/// The admins are loaded from Elasticsearch and stored in a quadtree
/// We attach a stop with all the admins that have a boundary containing
/// the coordinate of the stop
async fn attach_stops_to_admins<'a, It: Iterator<Item = &'a mut Stop>>(
    stops: It,
    client: &ElasticsearchStorage,
) -> Result<(), Error> {
    match client.list_documents().await {
        Ok(stream) => {
            let admins: Vec<Admin> = stream.try_collect().await.context(IndexGenerationSnafu)?;

            if admins.is_empty() {
                return Err(Error::AdminRetrieval {
                    details: String::from("no admin retrieved to enrich stops"),
                });
            }
            info!("{} admins retrieved from ES ", admins.len());
            let admins_geofinder = admins.into_iter().collect::<AdminGeoFinder>();

            let mut nb_unmatched = 0u32;
            let mut nb_matched = 0u32;
            // FIXME Opportunity for concurrent work
            for mut stop in stops {
                let admins = admins_geofinder.get(&stop.coord);

                if admins.is_empty() {
                    nb_unmatched += 1;
                } else {
                    nb_matched += 1;
                }

                attach_stop(&mut stop, admins);
            }

            info!(
                "there are {}/{} stops without any admin",
                nb_unmatched,
                nb_matched + nb_unmatched
            );
            Ok(())
        }
        Err(_) => Err(Error::AdminRetrieval {
            details: String::from("Could not retrieve admins to enrich stops"),
        }),
    }
}

/// Stores the stops found in the 'input' directory, in Elasticsearch, with the given
/// configuration.
///
/// The main part of this function is to actually create a list of stops
/// from the information found in the NTFS directory.
pub async fn index_ntfs(
    input: PathBuf,
    config: &ContainerConfig,
    client: &ElasticsearchStorage,
) -> Result<(), Error> {
    let navitia = transit_model::ntfs::read(&input).map_err(|err| Error::TransitModel {
        details: format!(
            "Could not read transit model from {}: {}",
            input.display(),
            err.to_string()
        ),
    })?;
    info!("Build number of stops per stoparea");
    let nb_stop_points: HashMap<String, u32, BuildHasherDefault<DefaultHasher>> = navitia
        .stop_areas
        .iter()
        .map(|(idx, sa)| {
            let id = places::utils::normalize_id("stop_area", &sa.id);
            let nb_stop_points = navitia
                .get_corresponding_from_idx::<_, transit_model::objects::StopPoint>(idx)
                .len();
            (id, nb_stop_points as u32)
        })
        .collect();

    info!("Make mimir stops from navitia stops");
    let mut stops: Vec<Stop> = navitia
        .stop_areas
        .iter()
        .map(|(idx, sa)| places::stop::to_mimir(idx, sa, &navitia))
        .collect();

    info!("Initialize stops weights");
    initialize_weights(stops.iter_mut(), &nb_stop_points);

    info!("Attach stops to admins");
    attach_stops_to_admins(stops.iter_mut(), client).await?;

    // FIXME Should be done concurrently (for_each_concurrent....)
    for stop in &mut stops {
        stop.coverages.push(config.dataset.clone());
        let mut admin_weight = stop
            .administrative_regions
            .iter()
            .filter(|adm| adm.is_city())
            .map(|adm| adm.weight)
            .next()
            .unwrap_or(0.0);
        // FIXME: 1024, automagic!
        // It's a factor used to bring the stop weight and the admin weight in the same order of
        // magnitude...
        // We then use a log to compress the distance between low admin weight and high ones.
        admin_weight = admin_weight * 1024.0 + 1.0;
        admin_weight = admin_weight.log10();
        stop.weight = (stop.weight + admin_weight) / 2.0;
    }

    tracing::info!("Beginning to import stops into elasticsearch.");
    import_stops(client, config, futures::stream::iter(stops)).await
}

// FIXME Should not be ElasticsearchStorage, but rather a trait GenerateIndex
pub async fn import_stops<S>(
    client: &ElasticsearchStorage,
    config: &ContainerConfig,
    stops: S,
) -> Result<(), Error>
where
    S: Stream<Item = Stop> + Send + Sync + Unpin + 'static,
{
    client
        .generate_index(config, stops)
        .await
        .context(IndexGenerationSnafu)?;

    Ok(())
}
<|endoftext|>