estree 0.0.1

A deserializer for the ESTree format.
use rustc_serialize::json::Json;
use serde_json::value::Value;
use easter::expr::{ExprData, Expr};
use easter::obj::DotKeyData;
use easter::id::IdExt;
use easter::punc::{Unop, Binop, Assop, Logop};
use unjson::ty::{Object, TyOf};
use unjson::{Unjson, ExtractField};
use joker::track::*;
use joker::token::{StringLiteral, NumberLiteral};

use tag::{Tag, TagOf};
use id::IntoId;
use result::Result;
use error::{Error, string_error, node_type_error, type_error};
use node::ExtractNode;
use fun::IntoFun;

pub trait IntoExpr {
    fn into_expr(self) -> Result<Expr>;
    fn into_lit(self) -> Result<Expr>;
}

impl IntoExpr for Object {
    fn into_expr(mut self) -> Result<Expr> {
        let tag = try!(self.tag());
        Ok(match tag {
            Tag::Identifier => { return Ok(try!(self.into_id()).into_expr()); }
            Tag::Literal => {
                let json = try!(self.extract_field("value").map_err(Error::Json));
                match json {
                    Value::Null => ExprData::Null,
                    Value::Bool(val) => if val { ExprData::True } else { ExprData::False },
                    Value::String(value) => {
                        // FIXME: this hack is pretty close to correct but it's inefficient and the only dependency on rustc-serialize
                        let source = Json::String(value.to_string()).pretty().to_string(); // FIXME: deal with \u2028, \u2029
                        ExprData::String(StringLiteral {
                            source: source,
                            value: value
                        })
                    }
                    Value::I64(val) => ExprData::Number(NumberLiteral::DecimalInt(val.to_string(), None)),
                    Value::U64(val) => ExprData::Number(NumberLiteral::DecimalInt(val.to_string(), None)),
                    Value::F64(val) => {
                        let s = val.to_string();
                        let v: Vec<&str> = s.split('.').collect();
                        let int_part = Some(v[0].to_owned());
                        let fract_part = if v.len() > 1 { Some(v[1].to_owned()) } else { None };
                        ExprData::Number(NumberLiteral::Float(int_part, fract_part, None))
                    }
                    Value::Object(_) => {
                        let mut regex = try!(self.extract_object("regex").map_err(Error::Json));
                        let pattern = try!(regex.extract_string("pattern").map_err(Error::Json));
                        let flags = try!(regex.extract_string("flags").map_err(Error::Json));
                        ExprData::RegExp(pattern, flags.chars().collect())
                    }
                    _ => { return type_error("null, number, boolean, string, or object", json.ty()); }
                }
            }
            Tag::BinaryExpression => {
                let str = try!(self.extract_string("operator").map_err(Error::Json));
                let op: Binop = match str.parse() {
                    Ok(op) => op,
                    Err(_) => { return string_error("binary operator", str); }
                };
                let left = try!(self.extract_expr("left"));
                let right = try!(self.extract_expr("right"));
                ExprData::Binop(op, Box::new(left), Box::new(right))
            }
            Tag::AssignmentExpression => {
                let str = try!(self.extract_string("operator").map_err(Error::Json));
                let op: Assop = match str.parse() {
                    Ok(op) => op,
                    Err(_) => { return string_error("assignment operator", str); }
                };
                let left = try!(self.extract_apatt("left"));
                let right = try!(self.extract_expr("right"));
                ExprData::Assign(op, left, Box::new(right))
            }
            Tag::LogicalExpression => {
                let str = try!(self.extract_string("operator").map_err(Error::Json));
                let op: Logop = match str.parse() {
                    Ok(op) => op,
                    Err(_) => { return string_error("logical operator", str); }
                };
                let left = try!(self.extract_expr("left"));
                let right = try!(self.extract_expr("right"));
                ExprData::Logop(op, Box::new(left), Box::new(right))
            }
            Tag::UnaryExpression => {
                let str = try!(self.extract_string("operator").map_err(Error::Json));
                let op: Unop = match str.parse() {
                    Ok(op) => op,
                    Err(_) => { return string_error("unary operator", str); }
                };
                let arg = try!(self.extract_expr("argument"));
                ExprData::Unop(op, Box::new(arg))
            }
            Tag::UpdateExpression => {
                let op = try!(self.extract_string("operator").map_err(Error::Json));
                let arg = Box::new(try!(self.extract_expr("argument")));
                let prefix = try!(self.extract_bool("prefix").map_err(Error::Json));
                match (&op[..], prefix) {
                    ("++", true)  => ExprData::PreInc(arg),
                    ("++", false) => ExprData::PostInc(arg),
                    ("--", true)  => ExprData::PreDec(arg),
                    ("--", false) => ExprData::PostDec(arg),
                    _ => { return string_error("'++' or '--'", op); }
                }
            }
            Tag::MemberExpression => {
                let obj = Box::new(try!(self.extract_expr("object")));
                if try!(self.extract_bool("computed").map_err(Error::Json)) {
                    let prop = Box::new(try!(self.extract_expr("property")));
                    ExprData::Brack(obj, prop)
                } else {
                    let id = try!(try!(self.extract_object("property").map_err(Error::Json)).into_id());
                    let key = DotKeyData(id.value.name.into_string()).tracked(None);
                    ExprData::Dot(obj, key)
                }
            }
            Tag::CallExpression => {
                let callee = Box::new(try!(self.extract_expr("callee")));
                let args = try!(self.extract_expr_list("arguments"));
                ExprData::Call(callee, args)
            }
            Tag::NewExpression => {
                let callee = Box::new(try!(self.extract_expr("callee")));
                let args = try!(self.extract_expr_list("arguments"));
                ExprData::New(callee, Some(args))
            }
            Tag::ArrayExpression => {
                let elts = try!(self.extract_expr_opt_list("elements"));
                ExprData::Arr(elts)
            }
            Tag::FunctionExpression => {
                let fun = try!(self.into_fun());
                ExprData::Fun(fun)
            }
            Tag::SequenceExpression => {
                let exprs = try!(self.extract_expr_list("expressions"));
                ExprData::Seq(exprs)
            }
            Tag::ObjectExpression => {
                let props = try!(self.extract_prop_list("properties"));
                ExprData::Obj(props)
            }
            Tag::ConditionalExpression => {
                let test = Box::new(try!(self.extract_expr("test")));
                let cons = Box::new(try!(self.extract_expr("consequent")));
                let alt = Box::new(try!(self.extract_expr("alternate")));
                ExprData::Cond(test, cons, alt)
            }
            Tag::ThisExpression => ExprData::This,
            _ => { return node_type_error("expression", tag); }
        }.tracked(None))
    }

    fn into_lit(mut self) -> Result<Expr> {
        let json = try!(self.extract_field("value").map_err(Error::Json));
        Ok(match json {
            Value::Null => ExprData::Null,
            Value::Bool(val) => if val { ExprData::True } else { ExprData::False },
            Value::String(value) => {
                // FIXME: this hack is pretty close to correct but it's inefficient and the only dependency on rustc-serialize
                let source = Json::String(value.to_string()).pretty().to_string(); // FIXME: deal with \u2028, \u2029
                ExprData::String(StringLiteral {
                    source: source,
                    value: value
                })
            }
            Value::I64(val) => ExprData::Number(NumberLiteral::DecimalInt(val.to_string(), None)),
            Value::U64(val) => ExprData::Number(NumberLiteral::DecimalInt(val.to_string(), None)),
            Value::F64(val) => {
                let s = val.to_string();
                let v: Vec<&str> = s.split('.').collect();
                let int_part = Some(v[0].to_owned());
                let fract_part = if v.len() > 1 { Some(v[1].to_owned()) } else { None };
                ExprData::Number(NumberLiteral::Float(int_part, fract_part, None))
            }
            Value::Object(_) => {
                let mut regex = try!(self.extract_object("regex").map_err(Error::Json));
                let pattern = try!(regex.extract_string("pattern").map_err(Error::Json));
                let flags = try!(regex.extract_string("flags").map_err(Error::Json));
                ExprData::RegExp(pattern, flags.chars().collect())
            }
            _ => { return type_error("null, number, boolean, string, or object", json.ty()); }
        }.tracked(None))
    }
}