<commit_before>#![feature(exit_status)]

extern crate mal;

use std::collections::HashMap;
use std::env as stdenv;

use mal::types::{MalVal, MalRet, MalError, err_str};
use mal::types::{symbol, _nil, string, list, vector, hash_map, malfunc, malfuncd};
use mal::types::MalType::{Nil, False, Sym, List, Vector, Hash_Map, Func, MalFunc};
use mal::types::MalError::{ErrString, ErrMalVal};
use mal::{readline, reader, core};
use mal::env::{env_set, env_get, env_new, env_bind, env_find, env_root, Env};

// read
fn read(str: String) -> MalRet {
    reader::read_str(str)
}

// eval
fn is_pair(x: MalVal) -> bool {
    match *x {
        List(ref lst,_) | Vector(ref lst,_) => lst.len() > 0,
        _ => false,
    }
}

fn quasiquote(ast: MalVal) -> MalVal {
    if !is_pair(ast.clone()) {
        return list(vec![symbol("quote"), ast])
    }

    match *ast.clone() {
        List(ref args,_) | Vector(ref args,_) => {
            let ref a0 = args[0];
            match **a0 {
                Sym(ref s) if *s == "unquote" => return args[1].clone(),
                _ => (),
            }
            if is_pair(a0.clone()) {
                match **a0 {
                    List(ref a0args,_) | Vector(ref a0args,_) => {
                        match *a0args[0] {
                            Sym(ref s) if *s == "split-unqoute" => {
                                return list(vec![symbol("concat"),
                                                 a0args[1].clone(),
                                                 quasiquote(list(args[1..].to_vec()))])
                            },
                            _ => (),
                        }
                    },
                    _ => (),
                }
            }
            let rest = list(args[1..].to_vec());
            return list(vec![symbol("cons"),
                             quasiquote(a0.clone()),
                             quasiquote(rest)])
        },
        _ => _nil(), // should never reach
    }
}

fn is_macro_call(ast: MalVal, env: Env) -> bool {
    let lst = match *ast {
        List(ref lst,_) => &lst[0],
        _ => return false
    };
    match **lst {
        Sym(_) => {},
        _ => return false
    }
    if env_find(&env, lst).is_none() {
        return false
    }
    let f = match env_get(&env, lst) {
        Ok(f) => f,
        _ => return false
    };
    match *f {
        MalFunc(ref mfd,_) => mfd.is_macro,
        _ => false,
    }
}

fn macroexpand(mut ast: MalVal, env: Env) -> MalRet {
    while is_macro_call(ast.clone(), env.clone()) {
        let ast2 = ast.clone();
        let args = match *ast2 {
            List(ref args,_) => args,
            _ => break,
        };
        let ref a0 = args[0];
        let mf = match **a0 {
            Sym(_) => try!(env_get(&env, &a0)),
            _ => break,
        };
        match *mf {
            MalFunc(_,_) => ast = try!(mf.apply(args[1..].to_vec())),
            _ => break,
        }
    }
    Ok(ast)
}

fn eval_ast(ast: MalVal, env: Env) -> MalRet {
    match *ast {
        Sym(_) => env_get(&env, &ast),
        List(ref a,_) | Vector(ref a,_) => {
            let mut ast_vec : Vec<MalVal> = vec![];
            for mv in a.iter() {
                let mv2 = mv.clone();
                ast_vec.push(try!(eval(mv2, env.clone())));
            }
            Ok(match *ast { List(_,_) => list(ast_vec),
                            _         => vector(ast_vec) })
        }
        Hash_Map(ref hm,_) => {
            let mut new_hm: HashMap<String,MalVal> = HashMap::new();
            for (key, value) in hm.iter() {
                new_hm.insert(key.to_string(),
                              try!(eval(value.clone(), env.clone())));
            }
            Ok(hash_map(new_hm))
        }
        _ => Ok(ast.clone()),
    }
}

fn eval(mut ast: MalVal, mut env: Env) -> MalRet {
    'tco: loop {

    //println!("eval: {}, {}", ast, env.borrow());
    //println!("eval: {}", ast);
    match *ast {
        List(_,_) => (),  // continue
        _ => return eval_ast(ast, env),
    }

    // apply list
    ast = try!(macroexpand(ast, env.clone()));
    match *ast {
        List(_,_) => (),  // continue
        _ => return Ok(ast),
    }

    let tmp = ast;
    let (args, a0sym) = match *tmp {
        List(ref args,_) => {
            if args.len() == 0 {
                return Ok(tmp.clone());
            }
            let ref a0 = *args[0];
            match *a0 {
                Sym(ref a0sym) => (args, &a0sym[..]),
                _ => (args, "__<fn*>__"),
            }
        },
        _ => return err_str("Expected list"),
    };

    match a0sym {
        "def!" => {
            let a1 = (*args)[1].clone();
            let a2 = (*args)[2].clone();
            let r = try!(eval(a2, env.clone()));
            match *a1 {
                Sym(_) => {
                    env_set(&env.clone(), a1, r.clone());
                    return Ok(r);
                },
                _ => return err_str("def! of non-symbol"),
            }
        },
        "let*" => {
            let let_env = env_new(Some(env.clone()));
            let a1 = (*args)[1].clone();
            let a2 = (*args)[2].clone();
            match *a1 {
                List(ref binds,_) | Vector(ref binds,_) => {
                    let mut it = binds.iter();
                    while it.len() >= 2 {
                        let b = it.next().unwrap();
                        let exp = it.next().unwrap();
                        match **b {
                            Sym(_) => {
                                let r = try!(eval(exp.clone(), let_env.clone()));
                                env_set(&let_env, b.clone(), r);
                            },
                            _ => return err_str("let* with non-symbol binding"),
                        }
                    }
                },
                _ => return err_str("let* with non-list bindings"),
            }
            ast = a2;
            env = let_env.clone();
            continue 'tco;
        },
        "quote" => return Ok((*args)[1].clone()),
        "quasiquote" => {
            let a1 = (*args)[1].clone();
            ast = quasiquote(a1);
            continue 'tco;
        },
        "defmacro!" => {
            let a1 = (*args)[1].clone();
            let a2 = (*args)[2].clone();
            let r = try!(eval(a2, env.clone()));
            match *r {
                MalFunc(ref mfd,_) => {
                    match *a1 {
                        Sym(_) => {
                            let mut new_mfd = mfd.clone();
                            new_mfd.is_macro = true;
                            let mf = malfuncd(new_mfd,_nil());
                            env_set(&env.clone(), a1.clone(), mf.clone());
                            return Ok(mf);
                        },
                        _ => return err_str("def! of non-symbol"),
                    }
                },
                _ => return err_str("def! of non-symbol"),
            }
        },
        "macroexpand" => {
            let a1 = (*args)[1].clone();
            return macroexpand(a1, env.clone())
        },
        "try*" => {
            let a1 = (*args)[1].clone();
            match eval(a1, env.clone()) {
                Ok(res) => return Ok(res),
                Err(err) => {
                    if args.len() < 3 { return Err(err); }
                    let a2 = (*args)[2].clone();
                    let cat = match *a2 {
                        List(ref cat,_) => cat,
                        _ => return err_str("invalid catch* clause"),
                    };
                    if cat.len() != 3 {
                        return err_str("wrong arity to catch* clause");
                    }
                    let c1 = (*cat)[1].clone();
                    match *c1 {
                        Sym(_) => {},
                        _ => return err_str("invalid catch* binding"),
                    };
                    let exc = match err {
                        ErrMalVal(mv) => mv,
                        ErrString(s) => string(s),
                    };
                    let bind_env = env_new(Some(env.clone()));
                    env_set(&bind_env, c1.clone(), exc);
                    let c2 = (*cat)[2].clone();
                    return eval(c2, bind_env);
                },
            };
        }
        "do" => {
            let el = list(args[1..args.len()-1].to_vec());
            try!(eval_ast(el, env.clone()));
            ast = args[args.len() - 1].clone();
            continue 'tco;
        },
        "if" => {
            let a1 = (*args)[1].clone();
            let c = try!(eval(a1, env.clone()));
            match *c {
                False | Nil => {
                    if args.len() >= 4 {
                        ast = args[3].clone();
                        continue 'tco;
                    } else {
                        return Ok(_nil());
                    }
                },
                _ => {
                    ast = args[2].clone();
                    continue 'tco;
                },
            }
        },
        "fn*" => {
            let a1 = args[1].clone();
            let a2 = args[2].clone();
            return Ok(malfunc(eval, a2, env, a1, _nil()));
        },
        "eval" => {
            let a1 = (*args)[1].clone();
            ast = try!(eval(a1, env.clone()));
            env = env_root(&env);
            continue 'tco;
        },
        _ => { // function call
            let el = try!(eval_ast(tmp.clone(), env.clone()));
            let args = match *el {
                List(ref args,_) => args,
                _ => return err_str("Invalid apply"),
            };
            return match *args.clone()[0] {
                Func(f,_) => f(args[1..].to_vec()),
                MalFunc(ref mf,_) => {
                    let mfc = mf.clone();
                    let alst = list(args[1..].to_vec());
                    let new_env = env_new(Some(mfc.env.clone()));
                    match env_bind(&new_env, mfc.params, alst) {
                        Ok(_) => {
                            ast = mfc.exp;
                            env = new_env;
                            continue 'tco;
                        },
                        Err(e) => err_str(&e),
                    }
                },
                _ => err_str("attempt to call non-function"),
            }
        },
    }

    }
}

// print
fn print(exp: MalVal) -> String {
    exp.pr_str(true)
}

fn rep(str: &str, env: Env) -> Result<String,MalError> {
    let ast = try!(read(str.to_string()));
    //println!("read: {}", ast);
    let exp = try!(eval(ast, env));
    Ok(print(exp))
}

fn main() {
    // core.rs: defined using rust
    let repl_env = env_new(None);
    for (k, v) in core::ns().into_iter() {
        env_set(&repl_env, symbol(&k), v);
    }
    // see eval() for definition of "eval"
    env_set(&repl_env, symbol("*ARGV*"), list(vec![]));

    // core.mal: defined using the language itself
    let _ = rep("(def! *host-language* \"rust\")", repl_env.clone());
    let _ = rep("(def! not (fn* (a) (if a false true)))", repl_env.clone());
    let _ = rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", repl_env.clone());
    let _ = rep("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))", repl_env.clone());
    let _ = rep("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))", repl_env.clone());

    // Invoked with command line arguments
    let args = stdenv::args();
    if args.len() > 1 {
        let mv_args = args.skip(2)
            .map(|a| string(a))
            .collect::<Vec<MalVal>>();
        env_set(&repl_env, symbol("*ARGV*"), list(mv_args));
        let lf = format!("(load-file \"{}\")",
                         stdenv::args().skip(1).next().unwrap());
        return match rep(&lf, repl_env.clone()) {
            Ok(_) => stdenv::set_exit_status(0),
            Err(str) => {
                println!("Error: {:?}", str);
                stdenv::set_exit_status(1);
            }
        };
    }

    // repl loop
    let _  = rep("(println (str \"Mal [\" *host-language* \"]\"))", repl_env.clone());
    loop {
        let line = readline::mal_readline("user> ");
        match line { None => break, _ => () }
        match rep(&line.unwrap(), repl_env.clone()) {
            Ok(str)  => println!("{}", str),
            Err(ErrMalVal(_)) => (),  // Blank line
            Err(ErrString(s)) => println!("Error: {}", s),
        }
    }
}
<commit_msg>Fix a bad copy/paste apparently<commit_after>#![feature(exit_status)]

extern crate mal;

use std::collections::HashMap;
use std::env as stdenv;

use mal::types::{MalVal, MalRet, MalError, err_str};
use mal::types::{symbol, _nil, string, list, vector, hash_map, malfunc, malfuncd};
use mal::types::MalType::{Nil, False, Sym, List, Vector, Hash_Map, Func, MalFunc};
use mal::types::MalError::{ErrString, ErrMalVal};
use mal::{readline, reader, core};
use mal::env::{env_set, env_get, env_new, env_bind, env_find, env_root, Env};

// read
fn read(str: String) -> MalRet {
    reader::read_str(str)
}

// eval
fn is_pair(x: MalVal) -> bool {
    match *x {
        List(ref lst,_) | Vector(ref lst,_) => lst.len() > 0,
        _ => false,
    }
}

fn quasiquote(ast: MalVal) -> MalVal {
    if !is_pair(ast.clone()) {
        return list(vec![symbol("quote"), ast])
    }

    match *ast.clone() {
        List(ref args,_) | Vector(ref args,_) => {
            let ref a0 = args[0];
            match **a0 {
                Sym(ref s) if *s == "unquote" => return args[1].clone(),
                _ => (),
            }
            if is_pair(a0.clone()) {
                match **a0 {
                    List(ref a0args,_) | Vector(ref a0args,_) => {
                        match *a0args[0] {
                            Sym(ref s) if *s == "splice-unquote" => {
                                return list(vec![symbol("concat"),
                                                 a0args[1].clone(),
                                                 quasiquote(list(args[1..].to_vec()))])
                            },
                            _ => (),
                        }
                    },
                    _ => (),
                }
            }
            let rest = list(args[1..].to_vec());
            return list(vec![symbol("cons"),
                             quasiquote(a0.clone()),
                             quasiquote(rest)])
        },
        _ => _nil(), // should never reach
    }
}

fn is_macro_call(ast: MalVal, env: Env) -> bool {
    let lst = match *ast {
        List(ref lst,_) => &lst[0],
        _ => return false
    };
    match **lst {
        Sym(_) => {},
        _ => return false
    }
    if env_find(&env, lst).is_none() {
        return false
    }
    let f = match env_get(&env, lst) {
        Ok(f) => f,
        _ => return false
    };
    match *f {
        MalFunc(ref mfd,_) => mfd.is_macro,
        _ => false,
    }
}

fn macroexpand(mut ast: MalVal, env: Env) -> MalRet {
    while is_macro_call(ast.clone(), env.clone()) {
        let ast2 = ast.clone();
        let args = match *ast2 {
            List(ref args,_) => args,
            _ => break,
        };
        let ref a0 = args[0];
        let mf = match **a0 {
            Sym(_) => try!(env_get(&env, &a0)),
            _ => break,
        };
        match *mf {
            MalFunc(_,_) => ast = try!(mf.apply(args[1..].to_vec())),
            _ => break,
        }
    }
    Ok(ast)
}

fn eval_ast(ast: MalVal, env: Env) -> MalRet {
    match *ast {
        Sym(_) => env_get(&env, &ast),
        List(ref a,_) | Vector(ref a,_) => {
            let mut ast_vec : Vec<MalVal> = vec![];
            for mv in a.iter() {
                let mv2 = mv.clone();
                ast_vec.push(try!(eval(mv2, env.clone())));
            }
            Ok(match *ast { List(_,_) => list(ast_vec),
                            _         => vector(ast_vec) })
        }
        Hash_Map(ref hm,_) => {
            let mut new_hm: HashMap<String,MalVal> = HashMap::new();
            for (key, value) in hm.iter() {
                new_hm.insert(key.to_string(),
                              try!(eval(value.clone(), env.clone())));
            }
            Ok(hash_map(new_hm))
        }
        _ => Ok(ast.clone()),
    }
}

fn eval(mut ast: MalVal, mut env: Env) -> MalRet {
    'tco: loop {

    //println!("eval: {}, {}", ast, env.borrow());
    //println!("eval: {}", ast);
    match *ast {
        List(_,_) => (),  // continue
        _ => return eval_ast(ast, env),
    }

    // apply list
    ast = try!(macroexpand(ast, env.clone()));
    match *ast {
        List(_,_) => (),  // continue
        _ => return Ok(ast),
    }

    let tmp = ast;
    let (args, a0sym) = match *tmp {
        List(ref args,_) => {
            if args.len() == 0 {
                return Ok(tmp.clone());
            }
            let ref a0 = *args[0];
            match *a0 {
                Sym(ref a0sym) => (args, &a0sym[..]),
                _ => (args, "__<fn*>__"),
            }
        },
        _ => return err_str("Expected list"),
    };

    match a0sym {
        "def!" => {
            let a1 = (*args)[1].clone();
            let a2 = (*args)[2].clone();
            let r = try!(eval(a2, env.clone()));
            match *a1 {
                Sym(_) => {
                    env_set(&env.clone(), a1, r.clone());
                    return Ok(r);
                },
                _ => return err_str("def! of non-symbol"),
            }
        },
        "let*" => {
            let let_env = env_new(Some(env.clone()));
            let a1 = (*args)[1].clone();
            let a2 = (*args)[2].clone();
            match *a1 {
                List(ref binds,_) | Vector(ref binds,_) => {
                    let mut it = binds.iter();
                    while it.len() >= 2 {
                        let b = it.next().unwrap();
                        let exp = it.next().unwrap();
                        match **b {
                            Sym(_) => {
                                let r = try!(eval(exp.clone(), let_env.clone()));
                                env_set(&let_env, b.clone(), r);
                            },
                            _ => return err_str("let* with non-symbol binding"),
                        }
                    }
                },
                _ => return err_str("let* with non-list bindings"),
            }
            ast = a2;
            env = let_env.clone();
            continue 'tco;
        },
        "quote" => return Ok((*args)[1].clone()),
        "quasiquote" => {
            let a1 = (*args)[1].clone();
            ast = quasiquote(a1);
            continue 'tco;
        },
        "defmacro!" => {
            let a1 = (*args)[1].clone();
            let a2 = (*args)[2].clone();
            let r = try!(eval(a2, env.clone()));
            match *r {
                MalFunc(ref mfd,_) => {
                    match *a1 {
                        Sym(_) => {
                            let mut new_mfd = mfd.clone();
                            new_mfd.is_macro = true;
                            let mf = malfuncd(new_mfd,_nil());
                            env_set(&env.clone(), a1.clone(), mf.clone());
                            return Ok(mf);
                        },
                        _ => return err_str("def! of non-symbol"),
                    }
                },
                _ => return err_str("def! of non-symbol"),
            }
        },
        "macroexpand" => {
            let a1 = (*args)[1].clone();
            return macroexpand(a1, env.clone())
        },
        "try*" => {
            let a1 = (*args)[1].clone();
            match eval(a1, env.clone()) {
                Ok(res) => return Ok(res),
                Err(err) => {
                    if args.len() < 3 { return Err(err); }
                    let a2 = (*args)[2].clone();
                    let cat = match *a2 {
                        List(ref cat,_) => cat,
                        _ => return err_str("invalid catch* clause"),
                    };
                    if cat.len() != 3 {
                        return err_str("wrong arity to catch* clause");
                    }
                    let c1 = (*cat)[1].clone();
                    match *c1 {
                        Sym(_) => {},
                        _ => return err_str("invalid catch* binding"),
                    };
                    let exc = match err {
                        ErrMalVal(mv) => mv,
                        ErrString(s) => string(s),
                    };
                    let bind_env = env_new(Some(env.clone()));
                    env_set(&bind_env, c1.clone(), exc);
                    let c2 = (*cat)[2].clone();
                    return eval(c2, bind_env);
                },
            };
        }
        "do" => {
            let el = list(args[1..args.len()-1].to_vec());
            try!(eval_ast(el, env.clone()));
            ast = args[args.len() - 1].clone();
            continue 'tco;
        },
        "if" => {
            let a1 = (*args)[1].clone();
            let c = try!(eval(a1, env.clone()));
            match *c {
                False | Nil => {
                    if args.len() >= 4 {
                        ast = args[3].clone();
                        continue 'tco;
                    } else {
                        return Ok(_nil());
                    }
                },
                _ => {
                    ast = args[2].clone();
                    continue 'tco;
                },
            }
        },
        "fn*" => {
            let a1 = args[1].clone();
            let a2 = args[2].clone();
            return Ok(malfunc(eval, a2, env, a1, _nil()));
        },
        "eval" => {
            let a1 = (*args)[1].clone();
            ast = try!(eval(a1, env.clone()));
            env = env_root(&env);
            continue 'tco;
        },
        _ => { // function call
            let el = try!(eval_ast(tmp.clone(), env.clone()));
            let args = match *el {
                List(ref args,_) => args,
                _ => return err_str("Invalid apply"),
            };
            return match *args.clone()[0] {
                Func(f,_) => f(args[1..].to_vec()),
                MalFunc(ref mf,_) => {
                    let mfc = mf.clone();
                    let alst = list(args[1..].to_vec());
                    let new_env = env_new(Some(mfc.env.clone()));
                    match env_bind(&new_env, mfc.params, alst) {
                        Ok(_) => {
                            ast = mfc.exp;
                            env = new_env;
                            continue 'tco;
                        },
                        Err(e) => err_str(&e),
                    }
                },
                _ => err_str("attempt to call non-function"),
            }
        },
    }

    }
}

// print
fn print(exp: MalVal) -> String {
    exp.pr_str(true)
}

fn rep(str: &str, env: Env) -> Result<String,MalError> {
    let ast = try!(read(str.to_string()));
    //println!("read: {}", ast);
    let exp = try!(eval(ast, env));
    Ok(print(exp))
}

fn main() {
    // core.rs: defined using rust
    let repl_env = env_new(None);
    for (k, v) in core::ns().into_iter() {
        env_set(&repl_env, symbol(&k), v);
    }
    // see eval() for definition of "eval"
    env_set(&repl_env, symbol("*ARGV*"), list(vec![]));

    // core.mal: defined using the language itself
    let _ = rep("(def! *host-language* \"rust\")", repl_env.clone());
    let _ = rep("(def! not (fn* (a) (if a false true)))", repl_env.clone());
    let _ = rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", repl_env.clone());
    let _ = rep("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))", repl_env.clone());
    let _ = rep("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))", repl_env.clone());

    // Invoked with command line arguments
    let args = stdenv::args();
    if args.len() > 1 {
        let mv_args = args.skip(2)
            .map(|a| string(a))
            .collect::<Vec<MalVal>>();
        env_set(&repl_env, symbol("*ARGV*"), list(mv_args));
        let lf = format!("(load-file \"{}\")",
                         stdenv::args().skip(1).next().unwrap());
        return match rep(&lf, repl_env.clone()) {
            Ok(_) => stdenv::set_exit_status(0),
            Err(str) => {
                println!("Error: {:?}", str);
                stdenv::set_exit_status(1);
            }
        };
    }

    // repl loop
    let _  = rep("(println (str \"Mal [\" *host-language* \"]\"))", repl_env.clone());
    loop {
        let line = readline::mal_readline("user> ");
        match line { None => break, _ => () }
        match rep(&line.unwrap(), repl_env.clone()) {
            Ok(str)  => println!("{}", str),
            Err(ErrMalVal(_)) => (),  // Blank line
            Err(ErrString(s)) => println!("Error: {}", s),
        }
    }
}
<|endoftext|>


<commit_before>//! Utilities for testing.

use diff;
use lalrpop_util::ParseError;
use std::fmt::{Debug, Error, Formatter};
use util::tok::Tok;

// a simple tokenizer
pub mod tok;

pub fn test<R: Debug + Eq, F>(parse_fn: F, input: &str, expected: R)
where
    F: FnOnce(Vec<Tok>) -> Result<R, ParseError<(), Tok, &'static str>>,
{
    // create tokens
    let tokens = tok::tokenize(input);

    // filter to tokens
    let tokens = tokens.into_iter().map(|(_, tok, _)| tok).collect();

    // parse, expecting input to be totally consumed
    let r = parse_fn(tokens).unwrap();

    // expect output to be correct
    assert!(
        r == expected,
        "parsing {:?}, got {:#?}, expected {:#?}",
        input,
        r,
        expected
    );
}

pub fn test_loc<R: Debug + Eq, F>(parse_fn: F, input: &str, expected: R)
where
    F: FnOnce(Vec<(usize, Tok, usize)>) -> Result<R, ParseError<usize, Tok, &'static str>>,
{
    // create tokens
    let tokens = tok::tokenize(input);

    // parse, expecting input to be totally consumed
    let r = parse_fn(tokens).unwrap();

    // expect output to be correct
    assert!(
        r == expected,
        "parsing {:?}, got {:#?}, expected {:#?}",
        input,
        r,
        expected
    );
}

pub fn test_err_gen<R, F>(parse_fn: F, input: &str) -> R
where
    F: FnOnce(Vec<(usize, Tok, usize)>) -> R,
{
    // create tokens
    let tokens = tok::tokenize(input);

    // parse, expecting input to be totally consumed
    parse_fn(tokens)
}

struct ExpectedDebug<'a>(&'a str);

impl<'a> Debug for ExpectedDebug<'a> {
    fn fmt(&self, fmt: &mut Formatter) -> Result<(), Error> {
        write!(fmt, "{}", self.0)
    }
}

pub fn expect_debug<D: Debug>(actual: D, expected: &str) {
    compare(
        ExpectedDebug(&format!("{:#?}", actual)),
        ExpectedDebug(expected),
    )
}

pub fn compare<D: Debug, E: Debug>(actual: D, expected: E) {
    let actual_s = format!("{:?}", actual);
    let expected_s = format!("{:?}", expected);
    if actual_s != expected_s {
        let actual_s = format!("{:#?}", actual);
        let expected_s = format!("{:#?}", expected);

        compare_str(&actual_s, &expected_s, "");
    }
}

pub fn compare_str(actual: &str, expected: &str, msg: &str) {
    if actual != expected {
        let mut lines = 0;
        for diff in diff::lines(actual, expected).into_iter().take(100) {
            lines += 1;
            match diff {
                diff::Result::Right(r) => println!("- {}", r),
                diff::Result::Left(l) => println!("+ {}", l),
                diff::Result::Both(l, _) => println!("  {}", l),
            }
        }

        if lines >= 100 {
            println!("... more");
        }
        assert!(false, "{}", msg);
    }
}
<commit_msg>Avoid printing the entire diff if there are lots of lines<commit_after>//! Utilities for testing.

use diff;
use lalrpop_util::ParseError;
use std::fmt::{Debug, Error, Formatter};
use util::tok::Tok;

// a simple tokenizer
pub mod tok;

pub fn test<R: Debug + Eq, F>(parse_fn: F, input: &str, expected: R)
where
    F: FnOnce(Vec<Tok>) -> Result<R, ParseError<(), Tok, &'static str>>,
{
    // create tokens
    let tokens = tok::tokenize(input);

    // filter to tokens
    let tokens = tokens.into_iter().map(|(_, tok, _)| tok).collect();

    // parse, expecting input to be totally consumed
    let r = parse_fn(tokens).unwrap();

    // expect output to be correct
    assert!(
        r == expected,
        "parsing {:?}, got {:#?}, expected {:#?}",
        input,
        r,
        expected
    );
}

pub fn test_loc<R: Debug + Eq, F>(parse_fn: F, input: &str, expected: R)
where
    F: FnOnce(Vec<(usize, Tok, usize)>) -> Result<R, ParseError<usize, Tok, &'static str>>,
{
    // create tokens
    let tokens = tok::tokenize(input);

    // parse, expecting input to be totally consumed
    let r = parse_fn(tokens).unwrap();

    // expect output to be correct
    assert!(
        r == expected,
        "parsing {:?}, got {:#?}, expected {:#?}",
        input,
        r,
        expected
    );
}

pub fn test_err_gen<R, F>(parse_fn: F, input: &str) -> R
where
    F: FnOnce(Vec<(usize, Tok, usize)>) -> R,
{
    // create tokens
    let tokens = tok::tokenize(input);

    // parse, expecting input to be totally consumed
    parse_fn(tokens)
}

struct ExpectedDebug<'a>(&'a str);

impl<'a> Debug for ExpectedDebug<'a> {
    fn fmt(&self, fmt: &mut Formatter) -> Result<(), Error> {
        write!(fmt, "{}", self.0)
    }
}

pub fn expect_debug<D: Debug>(actual: D, expected: &str) {
    compare(
        ExpectedDebug(&format!("{:#?}", actual)),
        ExpectedDebug(expected),
    )
}

pub fn compare<D: Debug, E: Debug>(actual: D, expected: E) {
    let actual_s = format!("{:?}", actual);
    let expected_s = format!("{:?}", expected);
    if actual_s != expected_s {
        let actual_s = format!("{:#?}", actual);
        let expected_s = format!("{:#?}", expected);

        compare_str(&actual_s, &expected_s, "");
    }
}

pub fn compare_str(actual: &str, expected: &str, msg: &str) {
    if actual != expected {
        let lines = diff::lines(actual, expected);
        for diff in lines.iter().take(100) {
            match diff {
                diff::Result::Right(r) => println!("- {}", r),
                diff::Result::Left(l) => println!("+ {}", l),
                diff::Result::Both(l, _) if lines.len() < 100 => println!("  {}", l),
                _ => (),
            }
        }

        if lines.len() >= 100 {
            println!("... more");
        }
        assert!(false, "{}", msg);
    }
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#![crate_name = "devtools_traits"]
#![crate_type = "rlib"]

#![allow(non_snake_case)]
#![allow(missing_copy_implementations)]
#![feature(globs)]

extern crate "msg" as servo_msg;
extern crate serialize;
extern crate url;
extern crate "util" as servo_util;

/// This module contains shared types and messages for use by devtools/script.
/// The traits are here instead of in script so that the devtools crate can be
/// modified independently of the rest of Servo.

pub use self::DevtoolsControlMsg::*;
pub use self::DevtoolScriptControlMsg::*;
pub use self::EvaluateJSReply::*;

use serialize::{Decodable, Decoder};
use servo_msg::constellation_msg::PipelineId;
use servo_util::str::DOMString;
use url::Url;

pub type DevtoolsControlChan = Sender<DevtoolsControlMsg>;
pub type DevtoolsControlPort = Receiver<DevtoolScriptControlMsg>;

// Information would be attached to NewGlobal to be received and show in devtools.
// Extend these fields if we need more information.
pub struct DevtoolsPageInfo {
    pub title: DOMString,
    pub url: Url
}

/// Messages to the instruct the devtools server to update its known actors/state
/// according to changes in the browser.
pub enum DevtoolsControlMsg {
    NewGlobal(PipelineId, Sender<DevtoolScriptControlMsg>, DevtoolsPageInfo),
    ServerExitMsg
}

/// Serialized JS return values
/// TODO: generalize this beyond the EvaluateJS message?
pub enum EvaluateJSReply {
    VoidValue,
    NullValue,
    BooleanValue(bool),
    NumberValue(f64),
    StringValue(String),
    ActorValue(String),
}

pub struct AttrInfo {
    pub namespace: String,
    pub name: String,
    pub value: String,
}

pub struct NodeInfo {
    pub uniqueId: String,
    pub baseURI: String,
    pub parent: String,
    pub nodeType: uint,
    pub namespaceURI: String,
    pub nodeName: String,
    pub numChildren: uint,

    pub name: String,
    pub publicId: String,
    pub systemId: String,

    pub attrs: Vec<AttrInfo>,

    pub isDocumentElement: bool,

    pub shortValue: String,
    pub incompleteValue: bool,
}

/// Messages to process in a particular script task, as instructed by a devtools client.
pub enum DevtoolScriptControlMsg {
    EvaluateJS(PipelineId, String, Sender<EvaluateJSReply>),
    GetRootNode(PipelineId, Sender<NodeInfo>),
    GetDocumentElement(PipelineId, Sender<NodeInfo>),
    GetChildren(PipelineId, String, Sender<Vec<NodeInfo>>),
    GetLayout(PipelineId, String, Sender<(f32, f32)>),
    ModifyAttribute(PipelineId, String, Vec<Modification>),
}

/// Messages to instruct devtools server to update its state relating to a particular
/// tab.
pub enum ScriptDevtoolControlMsg {
    /// Report a new JS error message
    ReportConsoleMsg(String),
}

#[deriving(Encodable)]
pub struct Modification{
    pub attributeName: String,
    pub newValue: Option<String>,
}

impl<D:Decoder<E>, E> Decodable<D, E> for Modification {
    fn decode(d: &mut D) -> Result<Modification, E> {
        d.read_struct("Modification", 2u, |d|
            Ok(Modification {
                attributeName: try!(d.read_struct_field("attributeName", 0u, |d| Decodable::decode(d))),
                newValue: match d.read_struct_field("newValue", 1u, |d| Decodable::decode(d)) {
                    Ok(opt) => opt,
                    Err(_) => None
                }
            })
        )
    }
}
<commit_msg>auto merge of #4606 : Ms2ger/servo/devtools-comment, r=larsbergstrom<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! This module contains shared types and messages for use by devtools/script.
//! The traits are here instead of in script so that the devtools crate can be
//! modified independently of the rest of Servo.

#![crate_name = "devtools_traits"]
#![crate_type = "rlib"]

#![allow(non_snake_case)]
#![allow(missing_copy_implementations)]
#![feature(globs)]

extern crate "msg" as servo_msg;
extern crate serialize;
extern crate url;
extern crate "util" as servo_util;

pub use self::DevtoolsControlMsg::*;
pub use self::DevtoolScriptControlMsg::*;
pub use self::EvaluateJSReply::*;

use serialize::{Decodable, Decoder};
use servo_msg::constellation_msg::PipelineId;
use servo_util::str::DOMString;
use url::Url;

pub type DevtoolsControlChan = Sender<DevtoolsControlMsg>;
pub type DevtoolsControlPort = Receiver<DevtoolScriptControlMsg>;

// Information would be attached to NewGlobal to be received and show in devtools.
// Extend these fields if we need more information.
pub struct DevtoolsPageInfo {
    pub title: DOMString,
    pub url: Url
}

/// Messages to the instruct the devtools server to update its known actors/state
/// according to changes in the browser.
pub enum DevtoolsControlMsg {
    NewGlobal(PipelineId, Sender<DevtoolScriptControlMsg>, DevtoolsPageInfo),
    ServerExitMsg
}

/// Serialized JS return values
/// TODO: generalize this beyond the EvaluateJS message?
pub enum EvaluateJSReply {
    VoidValue,
    NullValue,
    BooleanValue(bool),
    NumberValue(f64),
    StringValue(String),
    ActorValue(String),
}

pub struct AttrInfo {
    pub namespace: String,
    pub name: String,
    pub value: String,
}

pub struct NodeInfo {
    pub uniqueId: String,
    pub baseURI: String,
    pub parent: String,
    pub nodeType: uint,
    pub namespaceURI: String,
    pub nodeName: String,
    pub numChildren: uint,

    pub name: String,
    pub publicId: String,
    pub systemId: String,

    pub attrs: Vec<AttrInfo>,

    pub isDocumentElement: bool,

    pub shortValue: String,
    pub incompleteValue: bool,
}

/// Messages to process in a particular script task, as instructed by a devtools client.
pub enum DevtoolScriptControlMsg {
    EvaluateJS(PipelineId, String, Sender<EvaluateJSReply>),
    GetRootNode(PipelineId, Sender<NodeInfo>),
    GetDocumentElement(PipelineId, Sender<NodeInfo>),
    GetChildren(PipelineId, String, Sender<Vec<NodeInfo>>),
    GetLayout(PipelineId, String, Sender<(f32, f32)>),
    ModifyAttribute(PipelineId, String, Vec<Modification>),
}

/// Messages to instruct devtools server to update its state relating to a particular
/// tab.
pub enum ScriptDevtoolControlMsg {
    /// Report a new JS error message
    ReportConsoleMsg(String),
}

#[deriving(Encodable)]
pub struct Modification{
    pub attributeName: String,
    pub newValue: Option<String>,
}

impl<D:Decoder<E>, E> Decodable<D, E> for Modification {
    fn decode(d: &mut D) -> Result<Modification, E> {
        d.read_struct("Modification", 2u, |d|
            Ok(Modification {
                attributeName: try!(d.read_struct_field("attributeName", 0u, |d| Decodable::decode(d))),
                newValue: match d.read_struct_field("newValue", 1u, |d| Decodable::decode(d)) {
                    Ok(opt) => opt,
                    Err(_) => None
                }
            })
        )
    }
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use syntax::ast::MetaItem;
use syntax::codemap::Span;
use syntax::ext::base::{Annotatable, ExtCtxt};
use syntax::ptr::P;

pub fn expand_dom_struct(cx: &mut ExtCtxt, sp: Span, _: &MetaItem, anno: Annotatable) -> Annotatable {
    if let Annotatable::Item(item) = anno {
        let mut item2 = (*item).clone();
        item2.attrs.push(quote_attr!(cx, #[must_root]));
        item2.attrs.push(quote_attr!(cx, #[repr(C)]));
        item2.attrs.push(quote_attr!(cx, #[derive(JSTraceable)]));
        item2.attrs.push(quote_attr!(cx, #[derive(HeapSizeOf)]));
        item2.attrs.push(quote_attr!(cx, #[derive(DomObject)]));
        // #[dom_struct] gets consumed, so this lets us keep around a residue
        // Do NOT register a modifier/decorator on this attribute
        item2.attrs.push(quote_attr!(cx, #[_dom_struct_marker]));
        Annotatable::Item(P(item2))
    } else {
        cx.span_err(sp, "#[dom_struct] applied to something other than a struct");
        anno
    }
}
<commit_msg>Add #[derive(DenyPublicFields)] back to #[dom_struct]<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use syntax::ast::MetaItem;
use syntax::codemap::Span;
use syntax::ext::base::{Annotatable, ExtCtxt};
use syntax::ptr::P;

pub fn expand_dom_struct(cx: &mut ExtCtxt, sp: Span, _: &MetaItem, anno: Annotatable) -> Annotatable {
    if let Annotatable::Item(item) = anno {
        let mut item2 = (*item).clone();
        item2.attrs.push(quote_attr!(cx, #[must_root]));
        item2.attrs.push(quote_attr!(cx, #[repr(C)]));
        item2.attrs.push(quote_attr!(cx, #[derive(JSTraceable)]));
        item2.attrs.push(quote_attr!(cx, #[derive(HeapSizeOf)]));
        item2.attrs.push(quote_attr!(cx, #[derive(DenyPublicFields)]));
        item2.attrs.push(quote_attr!(cx, #[derive(DomObject)]));
        // #[dom_struct] gets consumed, so this lets us keep around a residue
        // Do NOT register a modifier/decorator on this attribute
        item2.attrs.push(quote_attr!(cx, #[_dom_struct_marker]));
        Annotatable::Item(P(item2))
    } else {
        cx.span_err(sp, "#[dom_struct] applied to something other than a struct");
        anno
    }
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
use dom::bindings::codegen::Bindings::NodeListBinding;
use dom::bindings::codegen::Bindings::NodeListBinding::NodeListMethods;
use dom::bindings::global::GlobalRef;
use dom::bindings::js::{JS, MutNullableHeap, Root};
use dom::bindings::utils::{Reflector, reflect_dom_object};
use dom::node::{ChildrenMutation, Node};
use dom::window::Window;

use std::cell::Cell;

#[derive(JSTraceable, HeapSizeOf)]
#[must_root]
pub enum NodeListType {
    Simple(Vec<JS<Node>>),
    Children(ChildrenList),
}

// https://dom.spec.whatwg.org/#interface-nodelist
#[dom_struct]
pub struct NodeList {
    reflector_: Reflector,
    list_type: NodeListType,
}

impl NodeList {
    fn new_inherited(list_type: NodeListType) -> NodeList {
        NodeList {
            reflector_: Reflector::new(),
            list_type: list_type,
        }
    }

    pub fn new(window: &Window,
               list_type: NodeListType) -> Root<NodeList> {
        reflect_dom_object(box NodeList::new_inherited(list_type),
                           GlobalRef::Window(window), NodeListBinding::Wrap)
    }

    pub fn new_simple_list<T>(window: &Window, iter: T)
                              -> Root<NodeList>
                              where T: Iterator<Item=Root<Node>> {
        NodeList::new(window, NodeListType::Simple(iter.map(|r| JS::from_rooted(&r)).collect()))
    }

    pub fn new_child_list(window: &Window, node: &Node) -> Root<NodeList> {
        NodeList::new(window, NodeListType::Children(ChildrenList::new(node)))
    }
}

impl NodeListMethods for NodeList {
    // https://dom.spec.whatwg.org/#dom-nodelist-length
    fn Length(&self) -> u32 {
        match self.list_type {
            NodeListType::Simple(ref elems) => elems.len() as u32,
            NodeListType::Children(ref list) => list.len(),
        }
    }

    // https://dom.spec.whatwg.org/#dom-nodelist-item
    fn Item(&self, index: u32) -> Option<Root<Node>> {
        match self.list_type {
            NodeListType::Simple(ref elems) => {
                elems.get(index as usize).map(|node| Root::from_rooted(*node))
            },
            NodeListType::Children(ref list) => list.item(index),
        }
    }

    // https://dom.spec.whatwg.org/#dom-nodelist-item
    fn IndexedGetter(&self, index: u32, found: &mut bool) -> Option<Root<Node>> {
        let item = self.Item(index);
        *found = item.is_some();
        item
    }
}

impl NodeList {
    pub fn as_children_list(&self) -> &ChildrenList {
        if let NodeListType::Children(ref list) = self.list_type {
            list
        } else {
            panic!("called as_children_list() on a simple node list")
        }
    }
}

#[derive(JSTraceable, HeapSizeOf)]
#[must_root]
pub struct ChildrenList {
    node: JS<Node>,
    #[ignore_heap_size_of = "Defined in rust-mozjs"]
    last_visited: MutNullableHeap<JS<Node>>,
    last_index: Cell<u32>,
}

impl ChildrenList {
    fn new(node: &Node) -> ChildrenList {
        let last_visited = node.GetFirstChild();
        ChildrenList {
            node: JS::from_ref(node),
            last_visited:
                MutNullableHeap::new(last_visited.as_ref().map(JS::from_rooted)),
            last_index: Cell::new(0u32),
        }
    }

    pub fn len(&self) -> u32 {
        self.node.root().children_count()
    }

    pub fn item(&self, index: u32) -> Option<Root<Node>> {
        // This always start traversing the children from the closest element
        // among parent's first and last children and the last visited one.
        let len = self.len() as u32;
        if index >= len {
            return None;
        }
        if index == 0u32 {
            // Item is first child if any, not worth updating last visited.
            return self.node.root().GetFirstChild();
        }
        let last_index = self.last_index.get();
        if index == last_index {
            // Item is last visited child, no need to update last visited.
            return Some(self.last_visited.get().unwrap().root());
        }
        let last_visited = if index - 1u32 == last_index {
            // Item is last visited's next sibling.
            self.last_visited.get().unwrap().root().GetNextSibling().unwrap()
        } else if last_index > 0 && index == last_index - 1u32 {
            // Item is last visited's previous sibling.
            self.last_visited.get().unwrap().root().GetPreviousSibling().unwrap()
        } else if index > last_index {
            if index == len - 1u32 {
                // Item is parent's last child, not worth updating last visited.
                return Some(self.node.root().GetLastChild().unwrap());
            }
            if index <= last_index + (len - last_index) / 2u32 {
                // Item is closer to the last visited child and follows it.
                self.last_visited.get().unwrap().root()
                                 .inclusively_following_siblings()
                                 .nth((index - last_index) as usize).unwrap()
            } else {
                // Item is closer to parent's last child and obviously
                // precedes it.
                self.node.root().GetLastChild().unwrap()
                    .inclusively_preceding_siblings()
                    .nth((len - index - 1u32) as usize).unwrap()
            }
        } else if index >= last_index / 2u32 {
            // Item is closer to the last visited child and precedes it.
            self.last_visited.get().unwrap().root()
                             .inclusively_preceding_siblings()
                             .nth((last_index - index) as usize).unwrap()
        } else {
            // Item is closer to parent's first child and obviously follows it.
            debug_assert!(index < last_index / 2u32);
            self.node.root().GetFirstChild().unwrap()
                     .inclusively_following_siblings()
                     .nth(index as usize)
                     .unwrap()
        };
        self.last_visited.set(Some(JS::from_rooted(&last_visited)));
        self.last_index.set(index);
        Some(last_visited)
    }

    pub fn children_changed(&self, mutation: &ChildrenMutation) {
        fn prepend(list: &ChildrenList, added: &[&Node], next: &Node) {
            let len = added.len() as u32;
            if len == 0u32 {
                return;
            }
            let index = list.last_index.get();
            if index < len {
                list.last_visited.set(Some(JS::from_ref(added[index as usize])));
            } else if index / 2u32 >= len {
                // If last index is twice as large as the number of added nodes,
                // updating only it means that less nodes will be traversed if
                // caller is traversing the node list linearly.
                list.last_index.set(len + index);
            } else {
                // If last index is not twice as large but still larger,
                // it's better to update it to the number of added nodes.
                list.last_visited.set(Some(JS::from_ref(next)));
                list.last_index.set(len);
            }
        }

        fn replace(list: &ChildrenList,
                   prev: Option<&Node>,
                   removed: &Node,
                   added: &[&Node],
                   next: Option<&Node>) {
            let index = list.last_index.get();
            if removed == &*list.last_visited.get().unwrap().root() {
                let visited = match (prev, added, next) {
                    (None, _, None) => {
                        // Such cases where parent had only one child should
                        // have been changed into ChildrenMutation::ReplaceAll
                        // by ChildrenMutation::replace().
                        unreachable!()
                    },
                    (_, [node, ..], _) => node,
                    (_, [], Some(next)) => next,
                    (Some(prev), [], None) => {
                        list.last_index.set(index - 1u32);
                        prev
                    },
                };
                list.last_visited.set(Some(JS::from_ref(visited)));
            } else {
                match (prev, next) {
                    (Some(_), None) => {},
                    (None, Some(next)) => {
                        list.last_index.set(index - 1);
                        prepend(list, added, next);
                    },
                    (Some(_), Some(_)) => {
                        list.reset();
                    },
                    (None, None) => unreachable!(),
                }
            }
        }

        match *mutation {
            ChildrenMutation::Append { .. } => {},
            ChildrenMutation::Insert { .. } => {
                self.reset();
            },
            ChildrenMutation::Prepend { added, next } => {
                prepend(self, added, next);
            },
            ChildrenMutation::Replace { prev, removed, added, next } => {
                replace(self, prev, removed, added, next);
            },
            ChildrenMutation::ReplaceAll { added, .. } => {
                let len = added.len();
                let index = self.last_index.get();
                if len == 0 {
                    self.last_visited.set(None);
                    self.last_index.set(0u32);
                } else if index < len as u32 {
                    self.last_visited.set(Some(JS::from_ref(added[index as usize])));
                } else {
                    // Setting last visited to parent's last child serves no purpose,
                    // so the middle is arbitrarily chosen here in case the caller
                    // wants random access.
                    let middle = len / 2;
                    self.last_visited.set(Some(JS::from_ref(added[middle])));
                    self.last_index.set(middle as u32);
                }
            },
        }
    }

    fn reset(&self) {
        self.last_visited.set(
            self.node.root().GetFirstChild().map(|node| JS::from_rooted(&node)));
        self.last_index.set(0u32);
    }
}
<commit_msg>Auto merge of #7407 - nox:childnodes-replace, r=jdm<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
use dom::bindings::codegen::Bindings::NodeListBinding;
use dom::bindings::codegen::Bindings::NodeListBinding::NodeListMethods;
use dom::bindings::global::GlobalRef;
use dom::bindings::js::{JS, MutNullableHeap, Root};
use dom::bindings::utils::{Reflector, reflect_dom_object};
use dom::node::{ChildrenMutation, Node};
use dom::window::Window;

use std::cell::Cell;

#[derive(JSTraceable, HeapSizeOf)]
#[must_root]
pub enum NodeListType {
    Simple(Vec<JS<Node>>),
    Children(ChildrenList),
}

// https://dom.spec.whatwg.org/#interface-nodelist
#[dom_struct]
pub struct NodeList {
    reflector_: Reflector,
    list_type: NodeListType,
}

impl NodeList {
    fn new_inherited(list_type: NodeListType) -> NodeList {
        NodeList {
            reflector_: Reflector::new(),
            list_type: list_type,
        }
    }

    pub fn new(window: &Window,
               list_type: NodeListType) -> Root<NodeList> {
        reflect_dom_object(box NodeList::new_inherited(list_type),
                           GlobalRef::Window(window), NodeListBinding::Wrap)
    }

    pub fn new_simple_list<T>(window: &Window, iter: T)
                              -> Root<NodeList>
                              where T: Iterator<Item=Root<Node>> {
        NodeList::new(window, NodeListType::Simple(iter.map(|r| JS::from_rooted(&r)).collect()))
    }

    pub fn new_child_list(window: &Window, node: &Node) -> Root<NodeList> {
        NodeList::new(window, NodeListType::Children(ChildrenList::new(node)))
    }
}

impl NodeListMethods for NodeList {
    // https://dom.spec.whatwg.org/#dom-nodelist-length
    fn Length(&self) -> u32 {
        match self.list_type {
            NodeListType::Simple(ref elems) => elems.len() as u32,
            NodeListType::Children(ref list) => list.len(),
        }
    }

    // https://dom.spec.whatwg.org/#dom-nodelist-item
    fn Item(&self, index: u32) -> Option<Root<Node>> {
        match self.list_type {
            NodeListType::Simple(ref elems) => {
                elems.get(index as usize).map(|node| Root::from_rooted(*node))
            },
            NodeListType::Children(ref list) => list.item(index),
        }
    }

    // https://dom.spec.whatwg.org/#dom-nodelist-item
    fn IndexedGetter(&self, index: u32, found: &mut bool) -> Option<Root<Node>> {
        let item = self.Item(index);
        *found = item.is_some();
        item
    }
}

impl NodeList {
    pub fn as_children_list(&self) -> &ChildrenList {
        if let NodeListType::Children(ref list) = self.list_type {
            list
        } else {
            panic!("called as_children_list() on a simple node list")
        }
    }
}

#[derive(JSTraceable, HeapSizeOf)]
#[must_root]
pub struct ChildrenList {
    node: JS<Node>,
    #[ignore_heap_size_of = "Defined in rust-mozjs"]
    last_visited: MutNullableHeap<JS<Node>>,
    last_index: Cell<u32>,
}

impl ChildrenList {
    fn new(node: &Node) -> ChildrenList {
        let last_visited = node.GetFirstChild();
        ChildrenList {
            node: JS::from_ref(node),
            last_visited:
                MutNullableHeap::new(last_visited.as_ref().map(JS::from_rooted)),
            last_index: Cell::new(0u32),
        }
    }

    pub fn len(&self) -> u32 {
        self.node.root().children_count()
    }

    pub fn item(&self, index: u32) -> Option<Root<Node>> {
        // This always start traversing the children from the closest element
        // among parent's first and last children and the last visited one.
        let len = self.len() as u32;
        if index >= len {
            return None;
        }
        if index == 0u32 {
            // Item is first child if any, not worth updating last visited.
            return self.node.root().GetFirstChild();
        }
        let last_index = self.last_index.get();
        if index == last_index {
            // Item is last visited child, no need to update last visited.
            return Some(self.last_visited.get().unwrap().root());
        }
        let last_visited = if index - 1u32 == last_index {
            // Item is last visited's next sibling.
            self.last_visited.get().unwrap().root().GetNextSibling().unwrap()
        } else if last_index > 0 && index == last_index - 1u32 {
            // Item is last visited's previous sibling.
            self.last_visited.get().unwrap().root().GetPreviousSibling().unwrap()
        } else if index > last_index {
            if index == len - 1u32 {
                // Item is parent's last child, not worth updating last visited.
                return Some(self.node.root().GetLastChild().unwrap());
            }
            if index <= last_index + (len - last_index) / 2u32 {
                // Item is closer to the last visited child and follows it.
                self.last_visited.get().unwrap().root()
                                 .inclusively_following_siblings()
                                 .nth((index - last_index) as usize).unwrap()
            } else {
                // Item is closer to parent's last child and obviously
                // precedes it.
                self.node.root().GetLastChild().unwrap()
                    .inclusively_preceding_siblings()
                    .nth((len - index - 1u32) as usize).unwrap()
            }
        } else if index >= last_index / 2u32 {
            // Item is closer to the last visited child and precedes it.
            self.last_visited.get().unwrap().root()
                             .inclusively_preceding_siblings()
                             .nth((last_index - index) as usize).unwrap()
        } else {
            // Item is closer to parent's first child and obviously follows it.
            debug_assert!(index < last_index / 2u32);
            self.node.root().GetFirstChild().unwrap()
                     .inclusively_following_siblings()
                     .nth(index as usize)
                     .unwrap()
        };
        self.last_visited.set(Some(JS::from_rooted(&last_visited)));
        self.last_index.set(index);
        Some(last_visited)
    }

    pub fn children_changed(&self, mutation: &ChildrenMutation) {
        fn prepend(list: &ChildrenList, added: &[&Node], next: &Node) {
            let len = added.len() as u32;
            if len == 0u32 {
                return;
            }
            let index = list.last_index.get();
            if index < len {
                list.last_visited.set(Some(JS::from_ref(added[index as usize])));
            } else if index / 2u32 >= len {
                // If last index is twice as large as the number of added nodes,
                // updating only it means that less nodes will be traversed if
                // caller is traversing the node list linearly.
                list.last_index.set(len + index);
            } else {
                // If last index is not twice as large but still larger,
                // it's better to update it to the number of added nodes.
                list.last_visited.set(Some(JS::from_ref(next)));
                list.last_index.set(len);
            }
        }

        fn replace(list: &ChildrenList,
                   prev: Option<&Node>,
                   removed: &Node,
                   added: &[&Node],
                   next: Option<&Node>) {
            let index = list.last_index.get();
            if removed == &*list.last_visited.get().unwrap().root() {
                let visited = match (prev, added, next) {
                    (None, _, None) => {
                        // Such cases where parent had only one child should
                        // have been changed into ChildrenMutation::ReplaceAll
                        // by ChildrenMutation::replace().
                        unreachable!()
                    },
                    (_, [node, ..], _) => node,
                    (_, [], Some(next)) => next,
                    (Some(prev), [], None) => {
                        list.last_index.set(index - 1u32);
                        prev
                    },
                };
                list.last_visited.set(Some(JS::from_ref(visited)));
            } else if added.len() != 1 {
                // The replaced child isn't the last visited one, and there are
                // 0 or more than 1 nodes to replace it. Special care must be
                // given to update the state of that ChildrenList.
                match (prev, next) {
                    (Some(_), None) => {},
                    (None, Some(next)) => {
                        list.last_index.set(index - 1);
                        prepend(list, added, next);
                    },
                    (Some(_), Some(_)) => {
                        list.reset();
                    },
                    (None, None) => unreachable!(),
                }
            }
        }

        match *mutation {
            ChildrenMutation::Append { .. } => {},
            ChildrenMutation::Insert { .. } => {
                self.reset();
            },
            ChildrenMutation::Prepend { added, next } => {
                prepend(self, added, next);
            },
            ChildrenMutation::Replace { prev, removed, added, next } => {
                replace(self, prev, removed, added, next);
            },
            ChildrenMutation::ReplaceAll { added, .. } => {
                let len = added.len();
                let index = self.last_index.get();
                if len == 0 {
                    self.last_visited.set(None);
                    self.last_index.set(0u32);
                } else if index < len as u32 {
                    self.last_visited.set(Some(JS::from_ref(added[index as usize])));
                } else {
                    // Setting last visited to parent's last child serves no purpose,
                    // so the middle is arbitrarily chosen here in case the caller
                    // wants random access.
                    let middle = len / 2;
                    self.last_visited.set(Some(JS::from_ref(added[middle])));
                    self.last_index.set(middle as u32);
                }
            },
        }
    }

    fn reset(&self) {
        self.last_visited.set(
            self.node.root().GetFirstChild().map(|node| JS::from_rooted(&node)));
        self.last_index.set(0u32);
    }
}
<|endoftext|>


<commit_before>use keyboard::Key;
use keymap::{ KeyMap, KeyMapState };
use buffer::{ Direction, Mark, WordEdgeMatch };
use textobject::{ TextObject, Reference, Kind, Anchor };
use overlay::OverlayType;

/// Instructions for the Editor.
/// These do NOT alter the text, but may change editor/view state
#[derive(Copy, Show)]
#[allow(dead_code)]
pub enum Instruction {
    SaveBuffer,
    //FindFile,
    ExitEditor,

    SetMark(Mark),
    SetOverlay(OverlayType),
    // SetMode(...)
}

/// Operations on the Buffer.
/// These DO alter the text, but otherwise may NOT change editor/view state
/// Note that these differ from log::Change in that they are higher-level
/// operations dependent on state (cursor/mark locations, etc.), as opposed
/// to concrete operations on absolute indexes (insert 'a' at index 158, etc.)
#[derive(Copy, Show)]
#[allow(dead_code)]
pub enum Operation {
    Insert,     // insert text
    Delete,     // delete some object

    Undo,       // rewind buffer transaction log
    Redo,       // replay buffer transaction log
}

/// Fragments that can be combined to specify a command
#[derive(Copy, Show)]
#[allow(dead_code)]
pub enum Partial {
    Kind(Kind),
    Anchor(Anchor),
    Reference(Reference),
    Object(TextObject),
    Action(Action),
}

#[derive(Copy, Show)]
#[allow(dead_code)]
pub enum Action {
    Operation(Operation),
    Instruction(Instruction),
}

/// A complete, actionable command
#[derive(Copy, Show)]
pub struct Command {
    pub number: i32,        // numeric paramter, line number, repeat count, etc.
    pub action: Action,     // what to do
    pub object: TextObject, // where to do it
}

pub struct Builder {
    number: Option<i32>,
    repeat: Option<uint>,

    action: Option<Action>,
    mark: Option<Mark>,
    kind: Option<Kind>,
    anchor: Option<Anchor>,
    reference: Option<Reference>,
    object: Option<TextObject>,

    reading_number: bool,
    keymap: KeyMap<Partial>,
}

#[derive(Copy, Show)]
pub enum BuilderEvent {
    Invalid,            // cannot find a valid interpretation
    Incomplete,         // needs more information
    Complete(Command),  // command is finished
}

impl Builder {
    pub fn new() -> Builder {
        Builder {
            number: None,
            repeat: None,
            action: None,
            mark: None,
            kind: None,
            anchor: None,
            reference: None,
            object: None,
            reading_number: false,
            keymap: default_keymap()
        }
    }

    pub fn reset(&mut self) {
        self.number = None;
        self.repeat = None;
        self.action = None;
        self.mark = None;
        self.kind = None;
        self.anchor = None;
        self.object = None;
        self.reference = None;
        self.reading_number = false;
    }

    pub fn check_key(&mut self, key: Key) -> BuilderEvent {
        if let Key::Char(c) = key {
            // '0' might be bound (start of line), and cannot be the start of a number sequence
            if c.is_digit(10) && (self.reading_number || c != '0') {
                let n = c.to_digit(10).unwrap();
                self.reading_number = true;
                self.append_digit(n);
                return BuilderEvent::Incomplete;
            } else if self.reading_number {

                self.reading_number = false;
            }
        }

        match self.keymap.check_key(key) {
            KeyMapState::Match(partial) => self.apply_partial(partial),
            KeyMapState::None           => { self.reset(); return BuilderEvent::Invalid; }
            _ => {},
        }

        if let Some(c) = self.complete_command() {
            self.reset();
            return BuilderEvent::Complete(c)
        } else {
            return BuilderEvent::Incomplete
        }
    }

    fn complete_object(&mut self) -> Option<TextObject> {
        if let Some(to) = self.object {
            // we have a whole object ready
            return Some(to)
        } else if let Some(reference) = self.reference {
            // we have a particular object in mind, use number as index/offset
            let reference = if let Some(n) = self.number {
                // set the reference index to n, or multiply offset by n
                // also, reset the 'outer' number, since index/offset is separate from repeat counter
                self.number = None;
                match reference {
                    Reference::Index(k, _) => Reference::Index(k, n as u32),
                    Reference::Offset(m, k, i) => Reference::Offset(m, k, i * n),
                    Reference::Mark(m, k) => Reference::Mark(m, k)
                }
            } else { reference };
            self.reference = Some(reference);

            return Some(TextObject { anchor: self.anchor.unwrap_or(Anchor::default()),
                                     reference: reference })
        } else if let Some(kind) = self.kind {
            // we have a particular kind...
            if let Some(mark) = self.mark {
                // and a specific mark, return the object of that kind, at that mark
                return Some(TextObject { anchor: self.anchor.unwrap_or(Anchor::default()),
                                         reference: Reference::Mark(mark, kind) })
            } else if let Some(n) = self.number {
                // and a particular number, return the nth of that kind
                return Some(TextObject { anchor: self.anchor.unwrap_or(Anchor::default()),
                                         reference: Reference::Index(kind, n as u32) })
            }
        }
        None
    }

    fn complete_command(&mut self) -> Option<Command> {
        /* rules for completing commands:
              | action            | number | object | reference    | kind |   | result                                                            |
              | -                 | -      | -      | -            | -    | - | -                                                                 |
              | no                | no     | no     | no           | yes  |   | move cursor to next (default) text object with kind               |
              | no                | no     | no     | yes          | -    |   | move cursor to text object with reference + default anchor        |
              | no                | no     | yes    | -            | -    |   | move cursor to text object                                        |
              | no                | yes    | no     | no           | no   |   | incomplete                                                        |
              | no                | yes    | no     | no           | yes  |   | move cursor to nth instance of kind (from start of buffer)        |
              | no                | yes    | no     | yes (index)  | -    |   | set index to number, cursor to ref + default anchor               |
              | no                | yes    | no     | yes (offset) | -    |   | multiply offset by number, cursor to ref + default anchor         |
              | no                | yes    | no     | yes (mark)   | -    |   | incomplete                                                        |
              | yes (instruction) | -      | -      | -            | -    |   | send instruction to editor with whatever parameters are available |
              | yes (operation)   | no     | no     | no           | no   |   | incomplete                                                        |
              | yes (operation)   | no     | no     | no           | yes  |   | apply operation to kind at cursor (default anchor)                |
              | yes (operation)   | no     | no     | yes          | -    |   | apply operation to reference with default anchor                  |
              | yes (operation)   | no     | yes    | -            | -    |   | apply operation to object                                         |
              | yes (operation)   | yes    | no     | no           | no   |   | incomplete                                                        |
              | yes (operation)   | yes    | no     | no           | yes  |   | apply operation to kind at cursor n times                         |
        */

        // editor instructions may not need a text object, go ahead and return immediately
        if let Some(Action::Instruction(i)) = self.action {
            return Some(Command {
                number: self.repeat.unwrap_or(0) as i32,
                action: Action::Instruction(i),
                object: self.complete_object().unwrap_or(TextObject {
                    anchor: Anchor::default(),
                    reference: Reference::default()
                })
            });
        }

        if let Some(to) = self.complete_object() {
            if let Some(Action::Operation(o)) = self.action {
                // we have an object, and an operation
                return Some(Command {
                    number: self.repeat.unwrap_or(0) as i32,
                    action: Action::Operation(o),
                    object: to
                });
            } else {
                // we have just an object, assume move cursor instruction
                return Some(Command {
                    number: self.repeat.unwrap_or(0) as i32,
                    action: Action::Instruction(Instruction::SetMark(Mark::Cursor(0))),
                    object: to
                });
            }
        }
        None
    }

    fn append_digit(&mut self, n: uint) {
        if let Some(current) = self.number {
            self.number = Some((current*10) + n as i32);
        } else {
            self.number = Some(n as i32);
        }
    }

    fn apply_partial(&mut self, partial: Partial) {
        match partial {
            Partial::Kind(k)      => self.kind = Some(k),
            Partial::Anchor(a)    => self.anchor = Some(a),
            Partial::Reference(r) => self.reference = Some(r),
            Partial::Object(o)    => self.object = Some(o),
            Partial::Action(a)    => {
                self.action = Some(a);
                if !self.reading_number && self.number.is_some() && self.repeat.is_none() {
                    self.repeat = Some(self.number.unwrap() as uint);
                    self.number = None;
                }
            }
        }
    }
}

fn default_keymap() -> KeyMap<Partial> {
    let mut keymap = KeyMap::new();

    // next/previous char
    keymap.bind_key(Key::Char('l'), Partial::Reference(Reference::Offset(Mark::Cursor(0), Kind::Char,  1)));
    keymap.bind_key(Key::Char('h'), Partial::Reference(Reference::Offset(Mark::Cursor(0), Kind::Char, -1)));

    // next/previous word
    keymap.bind_key(Key::Char('w'), Partial::Reference(Reference::Offset(Mark::Cursor(0), Kind::Word(WordEdgeMatch::Alphabet),  1)));
    keymap.bind_key(Key::Char('b'), Partial::Reference(Reference::Offset(Mark::Cursor(0), Kind::Word(WordEdgeMatch::Alphabet), -1)));
    keymap.bind_key(Key::Char('W'), Partial::Reference(Reference::Offset(Mark::Cursor(0), Kind::Word(WordEdgeMatch::Whitespace),  1)));
    keymap.bind_key(Key::Char('B'), Partial::Reference(Reference::Offset(Mark::Cursor(0), Kind::Word(WordEdgeMatch::Whitespace), -1)));

    // kinds
    keymap.bind_keys(&[Key::Char('`'), Key::Char('c')], Partial::Kind(Kind::Char));
    keymap.bind_keys(&[Key::Char('`'), Key::Char('w')], Partial::Kind(Kind::Word(WordEdgeMatch::Alphabet)));
    keymap.bind_keys(&[Key::Char('`'), Key::Char('W')], Partial::Kind(Kind::Word(WordEdgeMatch::Whitespace)));
    keymap.bind_keys(&[Key::Char('`'), Key::Char('l')], Partial::Kind(Kind::Line));

    // next/previous line
    keymap.bind_key(Key::Char('j'), Partial::Reference(Reference::Offset(Mark::Cursor(0), Kind::Line,  1)));
    keymap.bind_key(Key::Char('k'), Partial::Reference(Reference::Offset(Mark::Cursor(0), Kind::Line, -1)));

    // start/end of line
    keymap.bind_key(Key::Char('0'), Partial::Object(TextObject { anchor: Anchor::Before,
                                                                 reference: Reference::Mark(Mark::Cursor(0), Kind::Line) }));
    keymap.bind_key(Key::Char('$'), Partial::Object(TextObject { anchor: Anchor::After,
                                                                 reference: Reference::Mark(Mark::Cursor(0), Kind::Line) }));

    keymap.bind_key(Key::Char('d'), Partial::Action(Action::Operation(Operation::Delete)));

    keymap.bind_key(Key::Char(':'), Partial::Action(Action::Instruction(Instruction::SetOverlay(OverlayType::Prompt))));

    keymap
}
<commit_msg>Add partial binds for `Kind`s and `Anchor`s<commit_after>use keyboard::Key;
use keymap::{ KeyMap, KeyMapState };
use buffer::{ Direction, Mark, WordEdgeMatch };
use textobject::{ TextObject, Reference, Kind, Anchor };
use overlay::OverlayType;

/// Instructions for the Editor.
/// These do NOT alter the text, but may change editor/view state
#[derive(Copy, Show)]
#[allow(dead_code)]
pub enum Instruction {
    SaveBuffer,
    //FindFile,
    ExitEditor,

    SetMark(Mark),
    SetOverlay(OverlayType),
    // SetMode(...)
}

/// Operations on the Buffer.
/// These DO alter the text, but otherwise may NOT change editor/view state
/// Note that these differ from log::Change in that they are higher-level
/// operations dependent on state (cursor/mark locations, etc.), as opposed
/// to concrete operations on absolute indexes (insert 'a' at index 158, etc.)
#[derive(Copy, Show)]
#[allow(dead_code)]
pub enum Operation {
    Insert,     // insert text
    Delete,     // delete some object

    Undo,       // rewind buffer transaction log
    Redo,       // replay buffer transaction log
}

/// Fragments that can be combined to specify a command
#[derive(Copy, Show)]
#[allow(dead_code)]
pub enum Partial {
    Kind(Kind),
    Anchor(Anchor),
    Reference(Reference),
    Object(TextObject),
    Action(Action),
}

#[derive(Copy, Show)]
#[allow(dead_code)]
pub enum Action {
    Operation(Operation),
    Instruction(Instruction),
}

/// A complete, actionable command
#[derive(Copy, Show)]
pub struct Command {
    pub number: i32,        // numeric paramter, line number, repeat count, etc.
    pub action: Action,     // what to do
    pub object: TextObject, // where to do it
}

pub struct Builder {
    number: Option<i32>,
    repeat: Option<uint>,

    action: Option<Action>,
    mark: Option<Mark>,
    kind: Option<Kind>,
    anchor: Option<Anchor>,
    reference: Option<Reference>,
    object: Option<TextObject>,

    reading_number: bool,
    keymap: KeyMap<Partial>,
}

#[derive(Copy, Show)]
pub enum BuilderEvent {
    Invalid,            // cannot find a valid interpretation
    Incomplete,         // needs more information
    Complete(Command),  // command is finished
}

impl Builder {
    pub fn new() -> Builder {
        Builder {
            number: None,
            repeat: None,
            action: None,
            mark: None,
            kind: None,
            anchor: None,
            reference: None,
            object: None,
            reading_number: false,
            keymap: default_keymap()
        }
    }

    pub fn reset(&mut self) {
        self.number = None;
        self.repeat = None;
        self.action = None;
        self.mark = None;
        self.kind = None;
        self.anchor = None;
        self.object = None;
        self.reference = None;
        self.reading_number = false;
    }

    pub fn check_key(&mut self, key: Key) -> BuilderEvent {
        if let Key::Char(c) = key {
            // '0' might be bound (start of line), and cannot be the start of a number sequence
            if c.is_digit(10) && (self.reading_number || c != '0') {
                let n = c.to_digit(10).unwrap();
                self.reading_number = true;
                self.append_digit(n);
                return BuilderEvent::Incomplete;
            } else if self.reading_number {

                self.reading_number = false;
            }
        }

        match self.keymap.check_key(key) {
            KeyMapState::Match(partial) => self.apply_partial(partial),
            KeyMapState::None           => { self.reset(); return BuilderEvent::Invalid; }
            _ => {},
        }

        if let Some(c) = self.complete_command() {
            self.reset();
            return BuilderEvent::Complete(c)
        } else {
            return BuilderEvent::Incomplete
        }
    }

    fn complete_object(&mut self) -> Option<TextObject> {
        if let Some(to) = self.object {
            // we have a whole object ready
            return Some(to)
        } else if let Some(reference) = self.reference {
            // we have a particular object in mind, use number as index/offset
            let reference = if let Some(n) = self.number {
                // set the reference index to n, or multiply offset by n
                // also, reset the 'outer' number, since index/offset is separate from repeat counter
                self.number = None;
                match reference {
                    Reference::Index(k, _) => Reference::Index(k, n as u32),
                    Reference::Offset(m, k, i) => Reference::Offset(m, k, i * n),
                    Reference::Mark(m, k) => Reference::Mark(m, k)
                }
            } else { reference };
            self.reference = Some(reference);

            return Some(TextObject { anchor: self.anchor.unwrap_or(Anchor::default()),
                                     reference: reference })
        } else if let Some(kind) = self.kind {
            // we have a particular kind...
            if let Some(mark) = self.mark {
                // and a specific mark, return the object of that kind, at that mark
                return Some(TextObject { anchor: self.anchor.unwrap_or(Anchor::default()),
                                         reference: Reference::Mark(mark, kind) })
            } else if let Some(n) = self.number {
                // and a particular number, return the nth of that kind
                return Some(TextObject { anchor: self.anchor.unwrap_or(Anchor::default()),
                                         reference: Reference::Index(kind, n as u32) })
            }
        }
        None
    }

    fn complete_command(&mut self) -> Option<Command> {
        /* rules for completing commands:
              | action            | number | object | reference    | kind |   | result                                                            |
              | -                 | -      | -      | -            | -    | - | -                                                                 |
              | no                | no     | no     | no           | yes  |   | move cursor to next (default) text object with kind               |
              | no                | no     | no     | yes          | -    |   | move cursor to text object with reference + default anchor        |
              | no                | no     | yes    | -            | -    |   | move cursor to text object                                        |
              | no                | yes    | no     | no           | no   |   | incomplete                                                        |
              | no                | yes    | no     | no           | yes  |   | move cursor to nth instance of kind (from start of buffer)        |
              | no                | yes    | no     | yes (index)  | -    |   | set index to number, cursor to ref + default anchor               |
              | no                | yes    | no     | yes (offset) | -    |   | multiply offset by number, cursor to ref + default anchor         |
              | no                | yes    | no     | yes (mark)   | -    |   | incomplete                                                        |
              | yes (instruction) | -      | -      | -            | -    |   | send instruction to editor with whatever parameters are available |
              | yes (operation)   | no     | no     | no           | no   |   | incomplete                                                        |
              | yes (operation)   | no     | no     | no           | yes  |   | apply operation to kind at cursor (default anchor)                |
              | yes (operation)   | no     | no     | yes          | -    |   | apply operation to reference with default anchor                  |
              | yes (operation)   | no     | yes    | -            | -    |   | apply operation to object                                         |
              | yes (operation)   | yes    | no     | no           | no   |   | incomplete                                                        |
              | yes (operation)   | yes    | no     | no           | yes  |   | apply operation to kind at cursor n times                         |
        */

        // editor instructions may not need a text object, go ahead and return immediately
        if let Some(Action::Instruction(i)) = self.action {
            return Some(Command {
                number: self.repeat.unwrap_or(0) as i32,
                action: Action::Instruction(i),
                object: self.complete_object().unwrap_or(TextObject {
                    anchor: Anchor::default(),
                    reference: Reference::default()
                })
            });
        }

        if let Some(to) = self.complete_object() {
            if let Some(Action::Operation(o)) = self.action {
                // we have an object, and an operation
                return Some(Command {
                    number: self.repeat.unwrap_or(0) as i32,
                    action: Action::Operation(o),
                    object: to
                });
            } else {
                // we have just an object, assume move cursor instruction
                return Some(Command {
                    number: self.repeat.unwrap_or(0) as i32,
                    action: Action::Instruction(Instruction::SetMark(Mark::Cursor(0))),
                    object: to
                });
            }
        }
        None
    }

    fn append_digit(&mut self, n: uint) {
        if let Some(current) = self.number {
            self.number = Some((current*10) + n as i32);
        } else {
            self.number = Some(n as i32);
        }
    }

    fn apply_partial(&mut self, partial: Partial) {
        match partial {
            Partial::Kind(k)      => self.kind = Some(k),
            Partial::Anchor(a)    => self.anchor = Some(a),
            Partial::Reference(r) => self.reference = Some(r),
            Partial::Object(o)    => self.object = Some(o),
            Partial::Action(a)    => {
                self.action = Some(a);
                if !self.reading_number && self.number.is_some() && self.repeat.is_none() {
                    self.repeat = Some(self.number.unwrap() as uint);
                    self.number = None;
                }
            }
        }
    }
}

fn default_keymap() -> KeyMap<Partial> {
    let mut keymap = KeyMap::new();

    // next/previous char
    keymap.bind_key(Key::Char('l'), Partial::Reference(Reference::Offset(Mark::Cursor(0), Kind::Char,  1)));
    keymap.bind_key(Key::Char('h'), Partial::Reference(Reference::Offset(Mark::Cursor(0), Kind::Char, -1)));

    // next/previous word
    keymap.bind_key(Key::Char('w'), Partial::Reference(Reference::Offset(Mark::Cursor(0), Kind::Word(WordEdgeMatch::Alphabet),  1)));
    keymap.bind_key(Key::Char('b'), Partial::Reference(Reference::Offset(Mark::Cursor(0), Kind::Word(WordEdgeMatch::Alphabet), -1)));
    keymap.bind_key(Key::Char('W'), Partial::Reference(Reference::Offset(Mark::Cursor(0), Kind::Word(WordEdgeMatch::Whitespace),  1)));
    keymap.bind_key(Key::Char('B'), Partial::Reference(Reference::Offset(Mark::Cursor(0), Kind::Word(WordEdgeMatch::Whitespace), -1)));

    // next/previous line
    keymap.bind_key(Key::Char('j'), Partial::Reference(Reference::Offset(Mark::Cursor(0), Kind::Line,  1)));
    keymap.bind_key(Key::Char('k'), Partial::Reference(Reference::Offset(Mark::Cursor(0), Kind::Line, -1)));

    // start/end of line
    keymap.bind_key(Key::Char('0'), Partial::Object(TextObject { anchor: Anchor::Before,
                                                                 reference: Reference::Mark(Mark::Cursor(0), Kind::Line) }));
    keymap.bind_key(Key::Char('$'), Partial::Object(TextObject { anchor: Anchor::After,
                                                                 reference: Reference::Mark(Mark::Cursor(0), Kind::Line) }));

    // kinds
    keymap.bind_keys(&[Key::Char('`'), Key::Char('c')], Partial::Kind(Kind::Char));
    keymap.bind_keys(&[Key::Char('`'), Key::Char('w')], Partial::Kind(Kind::Word(WordEdgeMatch::Alphabet)));
    keymap.bind_keys(&[Key::Char('`'), Key::Char('W')], Partial::Kind(Kind::Word(WordEdgeMatch::Whitespace)));
    keymap.bind_keys(&[Key::Char('`'), Key::Char('l')], Partial::Kind(Kind::Line));

    // anchors
    keymap.bind_key(Key::Char(','), Partial::Anchor(Anchor::Before));
    keymap.bind_key(Key::Char('.'), Partial::Anchor(Anchor::After));

    // actions
    keymap.bind_key(Key::Char('d'), Partial::Action(Action::Operation(Operation::Delete)));
    keymap.bind_key(Key::Char('g'), Partial::Action(Action::Instruction(Instruction::SetMark(Mark::Cursor(0)))));

    keymap.bind_key(Key::Char(':'), Partial::Action(Action::Instruction(Instruction::SetOverlay(OverlayType::Prompt))));

    keymap
}
<|endoftext|>


<commit_before>use dynasmrt::x64::Assembler;
use dynasmrt::{DynasmApi, DynasmLabelApi};

use super::*;
use peephole;

dynasm!(asm
    ; .alias pointer, r12
    ; .alias mem_start, r13
    ; .alias mem_limit, r14
    ; .alias rts, r15
);

pub fn compile(program: &peephole::Program) -> Program {
    let mut asm = dynasmrt::x64::Assembler::new();
    let start = asm.offset();

    dynasm!(asm
        ; push r12
        ; push r13
        ; push r14
        ; push r15
        ; mov pointer, rcx
        ; mov mem_start, rcx
        ; mov mem_limit, rcx
        ; add mem_limit, rdx
        ; mov rts, r8
    );

    compile_sequence(&mut asm, program);

    dynasm!(asm
        ; mov rax, rts::OKAY as i32
        ; jmp ->finish

        ; ->underflow:
        ; mov rax, rts::UNDERFLOW as i32
        ; jmp ->finish

        ; ->overflow:
        ; mov rax, rts::OVERFLOW as i32

        ; ->finish:
        ; pop r15
        ; pop r14
        ; pop r13
        ; pop r12
        ; ret
    );

    let code = asm.finalize().unwrap();

    Program {
        code: code,
        start: start,
    }
}

pub fn compile_sequence(asm: &mut Assembler, program: &[peephole::Instruction]) {
    for instruction in program {
        compile_instruction(asm, instruction);
    }
}

macro_rules! check_pos_offset {
    ($asm:ident, $offset:expr) => {{
        dynasm!($asm
            ; mov rax, QWORD $offset as i64
        );

        if cfg!(not(feature = "omit_bounds_checks")) {
            dynasm!($asm
                ; mov rcx, mem_limit
                ; sub rcx, pointer
                ; cmp rcx, rax
                ; jle -> overflow
            );
        }
    }}
}

macro_rules! check_neg_offset {
    ($asm:ident, $offset:expr) => {{
        dynasm!($asm
            ; mov rax, QWORD $offset as i64
        );

        if cfg!(not(feature = "omit_bounds_checks")) {
            dynasm!($asm
                ; mov rcx, pointer
                ; sub rcx, mem_start
                ; cmp rcx, rax
                ; jl ->underflow
            );
        }
    }}
}

pub fn compile_instruction(asm: &mut Assembler, instruction: &peephole::Instruction) {
    use peephole::Instruction::*;

    match *instruction {
        Right(count) => {
            dynasm!(asm
                ;; check_pos_offset!(asm, count)
                ; add pointer, rax
            );
        }

        Left(count) => {
            dynasm!(asm
                ;; check_neg_offset!(asm, count)
                ; sub pointer, rax
            );
        }

        Change(count) => {
            dynasm!(asm
                ; add [pointer], BYTE u8_to_i8(count)
            );
        }

        In => {
            dynasm!(asm
                ; mov rax, QWORD rts::RtsState::read as _
                ; mov rcx, rts
                ; sub rsp, BYTE 0x28
                ; call rax
                ; add rsp, BYTE 0x28
                ; mov [pointer], al
            );
        }

        Out => {
            dynasm!(asm
                ; mov rax, QWORD rts::RtsState::write as _
                ; mov rcx, rts
                ; xor rdx, rdx
                ; mov dl, [pointer]
                ; sub rsp, BYTE 0x28
                ; call rax
                ; add rsp, BYTE 0x28
            );
        }

        SetZero => {
            dynasm!(asm
                ; mov BYTE [pointer], 0
            )
        }

        FindZeroRight(skip) => {
            dynasm!(asm
                ; cmp BYTE [pointer], 0
                ; jz >end_loop
                ; begin_loop:
                ;; check_pos_offset!(asm, skip)
                ; add pointer, rax
                ; cmp BYTE [pointer], 0
                ; jnz <begin_loop
                ; end_loop:
            )
        }

        FindZeroLeft(skip) => {
            dynasm!(asm
                ; cmp BYTE [pointer], 0
                ; jz >end_loop
                ; begin_loop:
                ;; check_neg_offset!(asm, skip)
                ; sub pointer, rax
                ; cmp BYTE [pointer], 0
                ; jnz <begin_loop
                ; end_loop:
            )
        }

        OffsetAddRight(offset) => {
            dynasm!(asm
                ; cmp BYTE [pointer], 0
                ; jz >skip
                ;; check_pos_offset!(asm, offset)
                ; mov cl, BYTE [pointer]
                ; mov BYTE [pointer], 0
                ; add BYTE [pointer + rax], cl
                ; skip:
            );
        }

        OffsetAddLeft(offset) => {
            dynasm!(asm
                ; cmp BYTE [pointer], 0
                ; jz >skip
                ;; check_neg_offset!(asm, offset)
                ; mov cl, BYTE [pointer]
                ; mov BYTE [pointer], 0
                ; mov rdx, pointer
                ; sub rdx, rax
                ; add BYTE [rdx], cl
                ; skip:
            );
        }

        Loop(ref body) => {
            let begin_label = asm.new_dynamic_label();
            let end_label   = asm.new_dynamic_label();

            dynasm!(asm
                ; cmp BYTE [pointer], 0
                ; jz =>end_label
                ; =>begin_label
            );

            compile_sequence(asm, body);

            dynasm!(asm
                ; cmp BYTE [pointer], 0
                ; jnz =>begin_label
                ; =>end_label
            );
        }
    }
}

fn u8_to_i8(n: u8) -> i8 {
    let mut n = n as isize % 256;
    if n > 127 { n -= 256 };
    n as i8
}
<commit_msg>Simplified generation of OffsetAddLeft.<commit_after>use dynasmrt::x64::Assembler;
use dynasmrt::{DynasmApi, DynasmLabelApi};

use super::*;
use peephole;

dynasm!(asm
    ; .alias pointer, r12
    ; .alias mem_start, r13
    ; .alias mem_limit, r14
    ; .alias rts, r15
);

pub fn compile(program: &peephole::Program) -> Program {
    let mut asm = dynasmrt::x64::Assembler::new();
    let start = asm.offset();

    dynasm!(asm
        ; push r12
        ; push r13
        ; push r14
        ; push r15
        ; mov pointer, rcx
        ; mov mem_start, rcx
        ; mov mem_limit, rcx
        ; add mem_limit, rdx
        ; mov rts, r8
    );

    compile_sequence(&mut asm, program);

    dynasm!(asm
        ; mov rax, rts::OKAY as i32
        ; jmp ->finish

        ; ->underflow:
        ; mov rax, rts::UNDERFLOW as i32
        ; jmp ->finish

        ; ->overflow:
        ; mov rax, rts::OVERFLOW as i32

        ; ->finish:
        ; pop r15
        ; pop r14
        ; pop r13
        ; pop r12
        ; ret
    );

    let code = asm.finalize().unwrap();

    Program {
        code: code,
        start: start,
    }
}

pub fn compile_sequence(asm: &mut Assembler, program: &[peephole::Instruction]) {
    for instruction in program {
        compile_instruction(asm, instruction);
    }
}

macro_rules! check_pos_offset {
    ($asm:ident, $offset:expr) => {{
        dynasm!($asm
            ; mov rax, QWORD $offset as i64
        );

        if cfg!(not(feature = "omit_bounds_checks")) {
            dynasm!($asm
                ; mov rcx, mem_limit
                ; sub rcx, pointer
                ; cmp rcx, rax
                ; jle -> overflow
            );
        }
    }}
}

macro_rules! check_neg_offset {
    ($asm:ident, $offset:expr) => {{
        dynasm!($asm
            ; mov rax, QWORD $offset as i64
        );

        if cfg!(not(feature = "omit_bounds_checks")) {
            dynasm!($asm
                ; mov rcx, pointer
                ; sub rcx, mem_start
                ; cmp rcx, rax
                ; jl ->underflow
            );
        }
    }}
}

pub fn compile_instruction(asm: &mut Assembler, instruction: &peephole::Instruction) {
    use peephole::Instruction::*;

    match *instruction {
        Right(count) => {
            dynasm!(asm
                ;; check_pos_offset!(asm, count)
                ; add pointer, rax
            );
        }

        Left(count) => {
            dynasm!(asm
                ;; check_neg_offset!(asm, count)
                ; sub pointer, rax
            );
        }

        Change(count) => {
            dynasm!(asm
                ; add [pointer], BYTE u8_to_i8(count)
            );
        }

        In => {
            dynasm!(asm
                ; mov rax, QWORD rts::RtsState::read as _
                ; mov rcx, rts
                ; sub rsp, BYTE 0x28
                ; call rax
                ; add rsp, BYTE 0x28
                ; mov [pointer], al
            );
        }

        Out => {
            dynasm!(asm
                ; mov rax, QWORD rts::RtsState::write as _
                ; mov rcx, rts
                ; xor rdx, rdx
                ; mov dl, [pointer]
                ; sub rsp, BYTE 0x28
                ; call rax
                ; add rsp, BYTE 0x28
            );
        }

        SetZero => {
            dynasm!(asm
                ; mov BYTE [pointer], 0
            )
        }

        FindZeroRight(skip) => {
            dynasm!(asm
                ; cmp BYTE [pointer], 0
                ; jz >end_loop
                ; begin_loop:
                ;; check_pos_offset!(asm, skip)
                ; add pointer, rax
                ; cmp BYTE [pointer], 0
                ; jnz <begin_loop
                ; end_loop:
            )
        }

        FindZeroLeft(skip) => {
            dynasm!(asm
                ; cmp BYTE [pointer], 0
                ; jz >end_loop
                ; begin_loop:
                ;; check_neg_offset!(asm, skip)
                ; sub pointer, rax
                ; cmp BYTE [pointer], 0
                ; jnz <begin_loop
                ; end_loop:
            )
        }

        OffsetAddRight(offset) => {
            dynasm!(asm
                ; cmp BYTE [pointer], 0
                ; jz >skip
                ;; check_pos_offset!(asm, offset)
                ; mov cl, BYTE [pointer]
                ; mov BYTE [pointer], 0
                ; add BYTE [pointer + rax], cl
                ; skip:
            );
        }

        OffsetAddLeft(offset) => {
            dynasm!(asm
                ; cmp BYTE [pointer], 0
                ; jz >skip
                ;; check_neg_offset!(asm, offset)
                ; mov cl, BYTE [pointer]
                ; mov BYTE [pointer], 0
                ; neg rax
                ; add BYTE [pointer + rax], cl
                ; skip:
            );
        }

        Loop(ref body) => {
            let begin_label = asm.new_dynamic_label();
            let end_label   = asm.new_dynamic_label();

            dynasm!(asm
                ; cmp BYTE [pointer], 0
                ; jz =>end_label
                ; =>begin_label
            );

            compile_sequence(asm, body);

            dynasm!(asm
                ; cmp BYTE [pointer], 0
                ; jnz =>begin_label
                ; =>end_label
            );
        }
    }
}

fn u8_to_i8(n: u8) -> i8 {
    let mut n = n as isize % 256;
    if n > 127 { n -= 256 };
    n as i8
}
<|endoftext|>