<commit_before>use value::{InternedString, Pair, Value};
use value::Value::*;
use cell_gc::{GcHeapSession, GcLeaf};
use cell_gc::collections::VecRef;
use std::fmt;

#[derive(IntoHeap)]
pub enum Expr<'h> {
    /// A constant (`quote` expressions produce this, but also numbers and
    /// other self-evaluating values).
    Con(Value<'h>),

    /// A variable-expression (evaluates to the variable's value).
    Var(GcLeaf<InternedString>),

    /// A lambda expression.
    Fun(CodeRef<'h>),

    /// A function call expression (an application).
    App(VecRef<'h, Expr<'h>>),

    /// A sequence-expression (`begin` used as an expression).
    Seq(VecRef<'h, Expr<'h>>),

    /// A conditional expression (`if`).
    If(IfRef<'h>),

    /// A definition.
    Def(DefRef<'h>),

    /// An assignment expression (`set!`).
    Set(DefRef<'h>),

    /// A `letrec*` expression.
    Letrec(LetrecRef<'h>),

    /// Evaluates to the current global environment.
    /// Used to implement (interactive-environment), for (eval).
    ToplevelEnv,
}

impl<'h> fmt::Debug for Expr<'h> {
    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        match *self {
            Expr::Con(ref v) => write!(f, "'{}", v),
            Expr::Var(ref s) => write!(f, "{}", Value::Symbol(s.clone())),
            Expr::Fun(ref c) => write!(f, "(lambda ... {:?})", c.body()),
            Expr::App(ref args) =>
                write!(f, "({})",
                       (0..args.len())
                       .map(|i| format!("{:?}", args.get(i)))
                       .collect::<Vec<String>>()
                       .join(" ")),
            Expr::Seq(ref exprs) =>
                write!(f, "(begin {})",
                       (0..exprs.len())
                       .map(|i| format!("{:?}", exprs.get(i)))
                       .collect::<Vec<String>>()
                       .join(" ")),
            Expr::If(ref r) =>
                write!(f, "(if {:?} {:?} {:?})", r.cond(), r.t_expr(), r.f_expr()),
            Expr::Def(ref r) =>
                write!(f, "(define {} {:?})", Value::Symbol(r.name()), r.value()),
            Expr::Set(ref r) =>
                write!(f, "(set! {} {:?})", Value::Symbol(r.name()), r.value()),
            Expr::Letrec(ref r) =>
                write!(f, "(letrec ({}) {:?})",
                       (0..r.names().len())
                       .map(|i| format!("({} {:?})",
                                        Value::Symbol(r.names().get(i)),
                                        r.exprs().get(i)))
                       .collect::<Vec<String>>()
                       .join(" "),
                       r.body()),
            Expr::ToplevelEnv => write!(f, "#<toplevel-env>"),
        }
    }
}

#[derive(IntoHeap)]
pub struct Code<'h> {
    pub params: VecRef<'h, GcLeaf<InternedString>>,
    pub rest: bool,
    pub body: Expr<'h>,
}

#[derive(IntoHeap)]
pub struct Def<'h> {
    pub name: GcLeaf<InternedString>,
    pub value: Expr<'h>
}

#[derive(IntoHeap)]
pub struct If<'h> {
    pub cond: Expr<'h>,
    pub t_expr: Expr<'h>,
    pub f_expr: Expr<'h>,
}

#[derive(IntoHeap)]
pub struct Letrec<'h> {
    pub names: VecRef<'h, GcLeaf<InternedString>>,
    pub exprs: VecRef<'h, Expr<'h>>,
    pub body: Expr<'h>
}

// Convert the linked list of a `<body>` to a vector; also splice in the
// contents of `(begin)` expressions nested within the `<body>`.
fn flatten_body<'h>(forms: Value<'h>, out: &mut Vec<Value<'h>>) -> Result<(), String> {
    for form_res in forms {
        let form = form_res?;
        if let Cons(ref pair) = form {
            if let Symbol(op) = pair.car() {
                if op.as_str() == "begin" {
                    flatten_body(pair.cdr(), out)?;
                    continue;
                }
            }
        }
        out.push(form);
    }
    Ok(())
}

fn is_definition<'h>(form: &Value<'h>) -> bool {
    if let Cons(ref pair) = *form {
        if let Symbol(op) = pair.car() {
            if op.as_str() == "define" {
                return true;
            }
        }
    }
    false
}

// Compile the body of a lambda or letrec*.
fn compile_body<'h>(
    hs: &mut GcHeapSession<'h>,
    body_list: Value<'h>,
) -> Result<Expr<'h>, String> {
    let mut forms = vec![];
    flatten_body(body_list, &mut forms)?;

    let mut names = vec![];
    let mut exprs = vec![];

    let mut i = 0;
    while i < forms.len() && is_definition(&forms[i]) {
        let (name, expr) = parse_define(hs, forms[i].clone())?;
        names.push(name);
        exprs.push(expr);
        i += 1;
    }

    if i == forms.len() {
        return Err("expression required".into());
    }

    let names = hs.alloc(names);
    let exprs = hs.alloc(exprs);
    let body_exprs: Result<Vec<Expr>, String> = forms
        .drain(i..)
        .map(|form| compile_expr(hs, form))
        .collect();
    let body = seq(hs, body_exprs?);
    Ok(Expr::Letrec(hs.alloc(Letrec {
        names,
        exprs,
        body,
    })))
}

fn seq<'h>(hs: &mut GcHeapSession<'h>, mut exprs: Vec<Expr<'h>>) -> Expr<'h> {
    if exprs.len() == 0 {
        Expr::Con(Value::Unspecified)
    } else if exprs.len() == 1 {
        exprs.pop().unwrap()
    } else {
        Expr::Seq(hs.alloc(exprs))
    }
}

/// On success, returns the two parts of a `(define)` that we care about: the
/// name to define and the compiled expression to populate it.
fn parse_define<'h>(hs: &mut GcHeapSession<'h>, mut defn: Value<'h>)
    -> Result<(GcLeaf<InternedString>, Expr<'h>), String>
{
    loop {
        let (define_symbol, tail) = defn.as_pair("internal error")?;
        let (pattern, rest) = tail.as_pair("(define) with no name")?;
        match pattern {
            Symbol(ident) => {
                let (expr, rest) = rest.as_pair("(define) with no value")?;
                match rest {
                    Nil => {}
                    _ => {
                        return Err(
                            "too many arguments in (define)".to_string(),
                        )
                    }
                };

                let value = compile_expr(hs, expr)?;
                return Ok((ident, value));
            }
            Cons(pair) => {
                // Build desugared definition and compile that.
                let name = pair.car();
                let formals = pair.cdr();

                // Transform `(define (,name ,@formals) ,@rest)
                // to        `(define ,name (lambda ,formals ,@rest))
                let lambda_cdr = Cons(hs.alloc(Pair {
                    car: formals,
                    cdr: rest,
                }));
                let lambda = Cons(hs.alloc(Pair {
                    car: Symbol(GcLeaf::new(InternedString::get("lambda"))),
                    cdr: lambda_cdr,
                }));
                let defn_cddr = Cons(hs.alloc(Pair {
                    car: lambda,
                    cdr: Nil,
                }));
                let defn_cdr = Cons(hs.alloc(Pair {
                    car: name,
                    cdr: defn_cddr,
                }));
                defn = Cons(hs.alloc(Pair {
                    car: define_symbol,
                    cdr: defn_cdr
                }));
            }
            _ => return Err("(define) with a non-symbol name".to_string())
        }
    }
}

pub fn compile_toplevel<'h>(
    hs: &mut GcHeapSession<'h>,
    expr: Value<'h>,
) -> Result<Expr<'h>, String> {
    // TODO: support (begin) here
    if is_definition(&expr) {
        let (name, value) = parse_define(hs, expr)?;
        Ok(Expr::Def(hs.alloc(Def { name, value })))
    } else {
        compile_expr(hs, expr)
    }
}

pub fn compile_expr<'h>(
    hs: &mut GcHeapSession<'h>,
    expr: Value<'h>,
) -> Result<Expr<'h>, String> {
    match expr {
        Symbol(s) => Ok(Expr::Var(s)),

        Cons(p) => {
            let f = p.car();
            if let Symbol(ref s) = f {
                if s.as_str() == "lambda" {
                    let (mut param_list, body_forms) = p.cdr().as_pair("syntax error in lambda")?;

                    let mut names = vec![];
                    while let Cons(pair) = param_list {
                        if let Symbol(s) = pair.car() {
                            names.push(s);
                        } else {
                            return Err("syntax error in lambda arguments".to_string());
                        }
                        param_list = pair.cdr();
                    }
                    let rest =
                        match param_list {
                            Nil => false,
                            Symbol(rest_name) => {
                                names.push(rest_name);
                                true
                            }
                            _ => return Err("syntax error in lambda arguments".to_string())
                        };

                    let params = hs.alloc(names);
                    let body = compile_body(hs, body_forms)?;
                    return Ok(Expr::Fun(hs.alloc(Code { params, rest, body })));
                } else if s.as_str() == "quote" {
                    let (datum, rest) = p.cdr().as_pair("(quote) with no arguments")?;
                    if !rest.is_nil() {
                        return Err("too many arguments to (quote)".to_string());
                    }
                    return Ok(Expr::Con(datum));
                } else if s.as_str() == "if" {
                    let (cond, rest) = p.cdr().as_pair("(if) with no arguments")?;
                    let cond = compile_expr(hs, cond)?;
                    let (tc, rest) = rest.as_pair("missing arguments after (if COND)")?;
                    let t_expr = compile_expr(hs, tc)?;
                    let f_expr =
                        if rest == Nil {
                            Expr::Con(Unspecified)
                        } else {
                            let (fc, rest) =
                                rest.as_pair("missing 'else' argument after (if COND X)")?;
                            if !rest.is_nil() {
                                return Err("too many arguments in (if) expression".to_string());
                            }
                            compile_expr(hs, fc)?
                        };
                    return Ok(Expr::If(hs.alloc(If { cond, t_expr, f_expr })));
                } else if s.as_str() == "begin" {
                    // In expression context, this is sequencing, not splicing.
                    let mut exprs = vec![];
                    for expr_result in p.cdr() {
                        let expr = expr_result?;
                        exprs.push(compile_expr(hs, expr)?);
                    }
                    return Ok(Expr::Seq(hs.alloc(exprs)));
                } else if s.as_str() == "define" {
                    // In expression context, definitions aren't allowed.
                    return Err("(define) is allowed only at toplevel or in the body \
                                of a function or let-form".into());
                } else if s.as_str() == "letrec" || s.as_str() == "letrec*" {
                    // Treat (letrec) forms just like (letrec*). Nonstandard in
                    // R6RS, which requires implementations to detect invalid
                    // references to letrec bindings before they're bound. But
                    // R5RS does not require this, and anyway well-behaved
                    // programs won't care.
                    let (bindings, body_forms) = p.cdr().as_pair("letrec*: bindings required")?;
                    let mut names = vec![];
                    let mut exprs = vec![];
                    for binding_result in bindings {
                        let binding = binding_result?;
                        let (name_v, rest) = binding.as_pair("letrec*: invalid binding")?;
                        let name = name_v.as_symbol("letrec*: name required")?;
                        let (expr, rest) = rest.as_pair("letrec*: value required for binding")?;
                        if !rest.is_nil() {
                            return Err("(letrec*): too many arguments".to_string());
                        }
                        names.push(GcLeaf::new(name));
                        exprs.push(compile_expr(hs, expr)?);
                    }
                    let names = hs.alloc(names);
                    let exprs = hs.alloc(exprs);
                    let body = compile_body(hs, body_forms)?;
                    return Ok(Expr::Letrec(hs.alloc(Letrec {
                        names,
                        exprs,
                        body,
                    })));
                } else if s.as_str() == "set!" {
                    let (first, rest) = p.cdr().as_pair("(set!) with no name")?;
                    let name = first.as_symbol("(set!) first argument must be a name")?;
                    let (expr, rest) = rest.as_pair("(set!) with no value")?;
                    if !rest.is_nil() {
                        return Err("(set!): too many arguments".to_string());
                    }
                    let value = compile_expr(hs, expr)?;
                    return Ok(Expr::Set(hs.alloc(Def {
                        name: GcLeaf::new(name),
                        value: value
                    })));
                }
            }

            let subexprs: Vec<Expr<'h>> =
                Cons(p)
                    .into_iter()
                    .map(|v| compile_expr(hs, v?))
                    .collect::<Result<_, String>>()?;
            Ok(Expr::App(hs.alloc(subexprs)))
        }

        // Self-evaluating values.
        Bool(v) => Ok(Expr::Con(Bool(v))),
        Int(v) => Ok(Expr::Con(Int(v))),
        Char(v) => Ok(Expr::Con(Char(v))),
        ImmString(v) => Ok(Expr::Con(ImmString(v))),

        // Note: Not sure what R6RS says about "three-dimensional" code,
        // eval code containing "constants" (either quoted or self-evaluating)
        // that are passed through. Possibly both this and the (quote) case
        // should do more work, to "flatten" the constants.
        StringObj(v) => Ok(Expr::Con(StringObj(v))),

        // Everything else is an error.
        _ => Err(format!("not an expression")),
    }
}
<commit_msg>lisp: Some very simple compile-time optimizations, heaven help us.<commit_after>use value::{InternedString, Pair, Value};
use value::Value::*;
use cell_gc::{GcHeapSession, GcLeaf};
use cell_gc::collections::VecRef;
use std::fmt;

#[derive(IntoHeap)]
pub enum Expr<'h> {
    /// A constant (`quote` expressions produce this, but also numbers and
    /// other self-evaluating values).
    Con(Value<'h>),

    /// A variable-expression (evaluates to the variable's value).
    Var(GcLeaf<InternedString>),

    /// A lambda expression.
    Fun(CodeRef<'h>),

    /// A function call expression (an application).
    App(VecRef<'h, Expr<'h>>),

    /// A sequence-expression (`begin` used as an expression).
    Seq(VecRef<'h, Expr<'h>>),

    /// A conditional expression (`if`).
    If(IfRef<'h>),

    /// A definition.
    Def(DefRef<'h>),

    /// An assignment expression (`set!`).
    Set(DefRef<'h>),

    /// A `letrec*` expression.
    Letrec(LetrecRef<'h>),

    /// Evaluates to the current global environment.
    /// Used to implement (interactive-environment), for (eval).
    ToplevelEnv,
}

impl<'h> fmt::Debug for Expr<'h> {
    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        match *self {
            Expr::Con(ref v) => write!(f, "'{}", v),
            Expr::Var(ref s) => write!(f, "{}", Value::Symbol(s.clone())),
            Expr::Fun(ref c) => write!(f, "(lambda ... {:?})", c.body()),
            Expr::App(ref args) =>
                write!(f, "({})",
                       (0..args.len())
                       .map(|i| format!("{:?}", args.get(i)))
                       .collect::<Vec<String>>()
                       .join(" ")),
            Expr::Seq(ref exprs) =>
                write!(f, "(begin {})",
                       (0..exprs.len())
                       .map(|i| format!("{:?}", exprs.get(i)))
                       .collect::<Vec<String>>()
                       .join(" ")),
            Expr::If(ref r) =>
                write!(f, "(if {:?} {:?} {:?})", r.cond(), r.t_expr(), r.f_expr()),
            Expr::Def(ref r) =>
                write!(f, "(define {} {:?})", Value::Symbol(r.name()), r.value()),
            Expr::Set(ref r) =>
                write!(f, "(set! {} {:?})", Value::Symbol(r.name()), r.value()),
            Expr::Letrec(ref r) =>
                write!(f, "(letrec ({}) {:?})",
                       (0..r.names().len())
                       .map(|i| format!("({} {:?})",
                                        Value::Symbol(r.names().get(i)),
                                        r.exprs().get(i)))
                       .collect::<Vec<String>>()
                       .join(" "),
                       r.body()),
            Expr::ToplevelEnv => write!(f, "#<toplevel-env>"),
        }
    }
}

#[derive(IntoHeap)]
pub struct Code<'h> {
    pub params: VecRef<'h, GcLeaf<InternedString>>,
    pub rest: bool,
    pub body: Expr<'h>,
}

#[derive(IntoHeap)]
pub struct Def<'h> {
    pub name: GcLeaf<InternedString>,
    pub value: Expr<'h>
}

#[derive(IntoHeap)]
pub struct If<'h> {
    pub cond: Expr<'h>,
    pub t_expr: Expr<'h>,
    pub f_expr: Expr<'h>,
}

#[derive(IntoHeap)]
pub struct Letrec<'h> {
    pub names: VecRef<'h, GcLeaf<InternedString>>,
    pub exprs: VecRef<'h, Expr<'h>>,
    pub body: Expr<'h>
}

fn seq<'h>(hs: &mut GcHeapSession<'h>, mut exprs: Vec<Expr<'h>>) -> Expr<'h> {
    if exprs.len() == 0 {
        Expr::Con(Value::Unspecified)
    } else if exprs.len() == 1 {
        exprs.pop().unwrap()
    } else {
        Expr::Seq(hs.alloc(exprs))
    }
}

fn letrec<'h>(
    hs: &mut GcHeapSession<'h>,
    names: Vec<GcLeaf<InternedString>>,
    exprs: Vec<Expr<'h>>,
    body: Expr<'h>,
) -> Expr<'h> {
    if names.is_empty() {
        body
    } else {
        let names = hs.alloc(names);
        let exprs = hs.alloc(exprs);
        Expr::Letrec(hs.alloc(Letrec {
            names,
            exprs,
            body,
        }))
    }
}

// Convert the linked list of a `<body>` to a vector; also splice in the
// contents of `(begin)` expressions nested within the `<body>`.
fn flatten_body<'h>(forms: Value<'h>, out: &mut Vec<Value<'h>>) -> Result<(), String> {
    for form_res in forms {
        let form = form_res?;
        if let Cons(ref pair) = form {
            if let Symbol(op) = pair.car() {
                if op.as_str() == "begin" {
                    flatten_body(pair.cdr(), out)?;
                    continue;
                }
            }
        }
        out.push(form);
    }
    Ok(())
}

fn is_definition<'h>(form: &Value<'h>) -> bool {
    if let Cons(ref pair) = *form {
        if let Symbol(op) = pair.car() {
            if op.as_str() == "define" {
                return true;
            }
        }
    }
    false
}

// Compile the body of a lambda or letrec*.
fn compile_body<'h>(
    hs: &mut GcHeapSession<'h>,
    body_list: Value<'h>,
) -> Result<Expr<'h>, String> {
    let mut forms = vec![];
    flatten_body(body_list, &mut forms)?;

    let mut names = vec![];
    let mut exprs = vec![];

    let mut i = 0;
    while i < forms.len() && is_definition(&forms[i]) {
        let (name, expr) = parse_define(hs, forms[i].clone())?;
        names.push(name);
        exprs.push(expr);
        i += 1;
    }

    if i == forms.len() {
        return Err("expression required".into());
    }

    let body_exprs: Result<Vec<Expr>, String> = forms
        .drain(i..)
        .map(|form| compile_expr(hs, form))
        .collect();
    let body = seq(hs, body_exprs?);
    Ok(letrec(hs, names, exprs, body))
}

/// On success, returns the two parts of a `(define)` that we care about: the
/// name to define and the compiled expression to populate it.
fn parse_define<'h>(hs: &mut GcHeapSession<'h>, mut defn: Value<'h>)
    -> Result<(GcLeaf<InternedString>, Expr<'h>), String>
{
    loop {
        let (define_symbol, tail) = defn.as_pair("internal error")?;
        let (pattern, rest) = tail.as_pair("(define) with no name")?;
        match pattern {
            Symbol(ident) => {
                let (expr, rest) = rest.as_pair("(define) with no value")?;
                match rest {
                    Nil => {}
                    _ => {
                        return Err(
                            "too many arguments in (define)".to_string(),
                        )
                    }
                };

                let value = compile_expr(hs, expr)?;
                return Ok((ident, value));
            }
            Cons(pair) => {
                // Build desugared definition and compile that.
                let name = pair.car();
                let formals = pair.cdr();

                // Transform `(define (,name ,@formals) ,@rest)
                // to        `(define ,name (lambda ,formals ,@rest))
                let lambda_cdr = Cons(hs.alloc(Pair {
                    car: formals,
                    cdr: rest,
                }));
                let lambda = Cons(hs.alloc(Pair {
                    car: Symbol(GcLeaf::new(InternedString::get("lambda"))),
                    cdr: lambda_cdr,
                }));
                let defn_cddr = Cons(hs.alloc(Pair {
                    car: lambda,
                    cdr: Nil,
                }));
                let defn_cdr = Cons(hs.alloc(Pair {
                    car: name,
                    cdr: defn_cddr,
                }));
                defn = Cons(hs.alloc(Pair {
                    car: define_symbol,
                    cdr: defn_cdr
                }));
            }
            _ => return Err("(define) with a non-symbol name".to_string())
        }
    }
}

pub fn compile_toplevel<'h>(
    hs: &mut GcHeapSession<'h>,
    expr: Value<'h>,
) -> Result<Expr<'h>, String> {
    // TODO: support (begin) here
    if is_definition(&expr) {
        let (name, value) = parse_define(hs, expr)?;
        Ok(Expr::Def(hs.alloc(Def { name, value })))
    } else {
        compile_expr(hs, expr)
    }
}

pub fn compile_expr<'h>(
    hs: &mut GcHeapSession<'h>,
    expr: Value<'h>,
) -> Result<Expr<'h>, String> {
    match expr {
        Symbol(s) => Ok(Expr::Var(s)),

        Cons(p) => {
            let f = p.car();
            if let Symbol(ref s) = f {
                if s.as_str() == "lambda" {
                    let (mut param_list, body_forms) = p.cdr().as_pair("syntax error in lambda")?;

                    let mut names = vec![];
                    while let Cons(pair) = param_list {
                        if let Symbol(s) = pair.car() {
                            names.push(s);
                        } else {
                            return Err("syntax error in lambda arguments".to_string());
                        }
                        param_list = pair.cdr();
                    }
                    let rest =
                        match param_list {
                            Nil => false,
                            Symbol(rest_name) => {
                                names.push(rest_name);
                                true
                            }
                            _ => return Err("syntax error in lambda arguments".to_string())
                        };

                    let params = hs.alloc(names);
                    let body = compile_body(hs, body_forms)?;
                    return Ok(Expr::Fun(hs.alloc(Code { params, rest, body })));
                } else if s.as_str() == "quote" {
                    let (datum, rest) = p.cdr().as_pair("(quote) with no arguments")?;
                    if !rest.is_nil() {
                        return Err("too many arguments to (quote)".to_string());
                    }
                    return Ok(Expr::Con(datum));
                } else if s.as_str() == "if" {
                    let (cond, rest) = p.cdr().as_pair("(if) with no arguments")?;
                    let cond = compile_expr(hs, cond)?;
                    let (tc, rest) = rest.as_pair("missing arguments after (if COND)")?;
                    let t_expr = compile_expr(hs, tc)?;
                    let f_expr =
                        if rest == Nil {
                            Expr::Con(Unspecified)
                        } else {
                            let (fc, rest) =
                                rest.as_pair("missing 'else' argument after (if COND X)")?;
                            if !rest.is_nil() {
                                return Err("too many arguments in (if) expression".to_string());
                            }
                            compile_expr(hs, fc)?
                        };
                    return Ok(Expr::If(hs.alloc(If { cond, t_expr, f_expr })));
                } else if s.as_str() == "begin" {
                    // In expression context, this is sequencing, not splicing.
                    let mut exprs = vec![];
                    for expr_result in p.cdr() {
                        let expr = expr_result?;
                        exprs.push(compile_expr(hs, expr)?);
                    }
                    return Ok(seq(hs, exprs));
                } else if s.as_str() == "define" {
                    // In expression context, definitions aren't allowed.
                    return Err("(define) is allowed only at toplevel or in the body \
                                of a function or let-form".into());
                } else if s.as_str() == "letrec" || s.as_str() == "letrec*" {
                    // Treat (letrec) forms just like (letrec*). Nonstandard in
                    // R6RS, which requires implementations to detect invalid
                    // references to letrec bindings before they're bound. But
                    // R5RS does not require this, and anyway well-behaved
                    // programs won't care.
                    let (bindings, body_forms) = p.cdr().as_pair("letrec*: bindings required")?;
                    let mut names = vec![];
                    let mut exprs = vec![];
                    for binding_result in bindings {
                        let binding = binding_result?;
                        let (name_v, rest) = binding.as_pair("letrec*: invalid binding")?;
                        let name = name_v.as_symbol("letrec*: name required")?;
                        let (expr, rest) = rest.as_pair("letrec*: value required for binding")?;
                        if !rest.is_nil() {
                            return Err("(letrec*): too many arguments".to_string());
                        }
                        names.push(GcLeaf::new(name));
                        exprs.push(compile_expr(hs, expr)?);
                    }
                    let body = compile_body(hs, body_forms)?;
                    return Ok(letrec(hs, names, exprs, body));
                } else if s.as_str() == "set!" {
                    let (first, rest) = p.cdr().as_pair("(set!) with no name")?;
                    let name = first.as_symbol("(set!) first argument must be a name")?;
                    let (expr, rest) = rest.as_pair("(set!) with no value")?;
                    if !rest.is_nil() {
                        return Err("(set!): too many arguments".to_string());
                    }
                    let value = compile_expr(hs, expr)?;
                    return Ok(Expr::Set(hs.alloc(Def {
                        name: GcLeaf::new(name),
                        value: value
                    })));
                }
            }

            let subexprs: Vec<Expr<'h>> =
                Cons(p)
                    .into_iter()
                    .map(|v| compile_expr(hs, v?))
                    .collect::<Result<_, String>>()?;
            Ok(Expr::App(hs.alloc(subexprs)))
        }

        // Self-evaluating values.
        Bool(v) => Ok(Expr::Con(Bool(v))),
        Int(v) => Ok(Expr::Con(Int(v))),
        Char(v) => Ok(Expr::Con(Char(v))),
        ImmString(v) => Ok(Expr::Con(ImmString(v))),

        // Note: Not sure what R6RS says about "three-dimensional" code,
        // eval code containing "constants" (either quoted or self-evaluating)
        // that are passed through. Possibly both this and the (quote) case
        // should do more work, to "flatten" the constants.
        StringObj(v) => Ok(Expr::Con(StringObj(v))),

        // Everything else is an error.
        _ => Err(format!("not an expression")),
    }
}
<|endoftext|>


<commit_before>#[cfg(feature = "serialization")]
use serde::{Deserialize, Serialize};
use std::num::NonZeroU32;
use std::{error, fmt};

use crate::{ByteIndex, ColumnIndex, LineIndex, LineOffset, Location, RawIndex, Span};

#[derive(Debug, PartialEq)]
pub struct LineIndexOutOfBoundsError {
    pub given: LineIndex,
    pub max: LineIndex,
}

impl error::Error for LineIndexOutOfBoundsError {}

impl fmt::Display for LineIndexOutOfBoundsError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "Line index out of bounds - given: {}, max: {}",
            self.given, self.max
        )
    }
}

#[derive(Debug, PartialEq)]
pub enum LocationError {
    OutOfBounds { given: ByteIndex, span: Span },
    InvalidCharBoundary { given: ByteIndex },
}

impl error::Error for LocationError {}

impl fmt::Display for LocationError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            LocationError::OutOfBounds { given, span } => write!(
                f,
                "Byte index out of bounds - given: {}, span: {}",
                given, span
            ),
            LocationError::InvalidCharBoundary { given } => {
                write!(f, "Byte index within character boundary - given: {}", given)
            },
        }
    }
}

#[derive(Debug, PartialEq)]
pub struct SpanOutOfBoundsError {
    pub given: Span,
    pub span: Span,
}

impl error::Error for SpanOutOfBoundsError {}

impl fmt::Display for SpanOutOfBoundsError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "Span out of bounds - given: {}, span: {}",
            self.given, self.span
        )
    }
}

/// A handle that points to a file in the database.
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serialization", derive(Deserialize, Serialize))]
#[cfg_attr(feature = "memory_usage", derive(heapsize_derive::HeapSizeOf))]
pub struct FileId(NonZeroU32);

impl FileId {
    /// Offset of our `FileId`'s numeric value to an index on `Files::files`.
    ///
    /// This is to ensure the first `FileId` is non-zero for memory layout optimisations (e.g.
    /// `Option<FileId>` is 4 bytes)
    const OFFSET: u32 = 1;

    fn new(index: usize) -> FileId {
        FileId(NonZeroU32::new(index as u32 + Self::OFFSET).unwrap())
    }

    fn get(self) -> usize {
        (self.0.get() - Self::OFFSET) as usize
    }
}

/// A database of source files.
#[derive(Clone, Debug, Default)]
pub struct Files {
    files: Vec<File>,
}

impl Files {
    /// Create a new, empty database of files.
    pub fn new() -> Files {
        Files::default()
    }

    /// Add a file to the database, returning the handle that can be used to
    /// refer to it again.
    pub fn add(&mut self, name: impl Into<String>, source: impl Into<String>) -> FileId {
        let file_id = FileId::new(self.files.len());
        self.files.push(File::new(name.into(), source.into()));
        file_id
    }

    /// Update a source file in place.
    ///
    /// This will mean that any outstanding byte indexes will now point to
    /// invalid locations.
    pub fn update(&mut self, file_id: FileId, source: impl Into<String>) {
        self.get_mut(file_id).update(source.into())
    }

    /// Get a the source file using the file id.
    // FIXME: return an option or result?
    fn get(&self, file_id: FileId) -> &File {
        &self.files[file_id.get()]
    }

    /// Get a the source file using the file id.
    // FIXME: return an option or result?
    fn get_mut(&mut self, file_id: FileId) -> &mut File {
        &mut self.files[file_id.get()]
    }

    /// Get the name of the source file.
    ///
    /// ```rust
    /// use codespan::Files;
    ///
    /// let name = "test";
    ///
    /// let mut files = Files::new();
    /// let file_id = files.add(name, "hello world!");
    ///
    /// assert_eq!(files.name(file_id), name);
    /// ```
    pub fn name(&self, file_id: FileId) -> &str {
        self.get(file_id).name()
    }

    /// Get the span at the given line index.
    ///
    /// ```rust
    /// use codespan::{Files, LineIndex, LineIndexOutOfBoundsError, Span};
    ///
    /// let mut files = Files::new();
    /// let file_id = files.add("test", "foo\nbar\r\n\nbaz");
    ///
    /// let line_sources = (0..5)
    ///     .map(|line| files.line_span(file_id, line))
    ///     .collect::<Vec<_>>();
    ///
    /// assert_eq!(
    ///     line_sources,
    ///     [
    ///         Ok(Span::new(0, 4)),    // 0: "foo\n"
    ///         Ok(Span::new(4, 9)),    // 1: "bar\r\n"
    ///         Ok(Span::new(9, 10)),   // 2: ""
    ///         Ok(Span::new(10, 13)),  // 3: "baz"
    ///         Err(LineIndexOutOfBoundsError {
    ///             given: LineIndex::from(5),
    ///             max: LineIndex::from(4),
    ///         }),
    ///     ]
    /// );
    /// ```
    pub fn line_span(
        &self,
        file_id: FileId,
        line_index: impl Into<LineIndex>,
    ) -> Result<Span, LineIndexOutOfBoundsError> {
        self.get(file_id).line_span(line_index.into())
    }

    /// Get the location at the given byte index in the source file.
    ///
    /// ```rust
    /// use codespan::{ByteIndex, Files, Location, LocationError, Span};
    ///
    /// let mut files = Files::new();
    /// let file_id = files.add("test", "foo\nbar\r\n\nbaz");
    ///
    /// assert_eq!(files.location(file_id, 0), Ok(Location::new(0, 0)));
    /// assert_eq!(files.location(file_id, 7), Ok(Location::new(1, 3)));
    /// assert_eq!(files.location(file_id, 8), Ok(Location::new(1, 4)));
    /// assert_eq!(files.location(file_id, 9), Ok(Location::new(2, 0)));
    /// assert_eq!(
    ///     files.location(file_id, 100),
    ///     Err(LocationError::OutOfBounds {
    ///         given: ByteIndex::from(100),
    ///         span: Span::new(0, 13),
    ///     }),
    /// );
    /// ```
    pub fn location(
        &self,
        file_id: FileId,
        byte_index: impl Into<ByteIndex>,
    ) -> Result<Location, LocationError> {
        self.get(file_id).location(byte_index.into())
    }

    /// Get the source of the file.
    ///
    /// ```rust
    /// use codespan::Files;
    ///
    /// let source = "hello world!";
    ///
    /// let mut files = Files::new();
    /// let file_id = files.add("test", source);
    ///
    /// assert_eq!(files.source(file_id), source);
    /// ```
    pub fn source(&self, file_id: FileId) -> &str {
        self.get(file_id).source()
    }

    /// Return the span of the full source.
    ///
    /// ```rust
    /// use codespan::{Files, Span};
    ///
    /// let source = "hello world!";
    ///
    /// let mut files = Files::new();
    /// let file_id = files.add("test", source);
    ///
    /// assert_eq!(files.source_span(file_id), Span::from_str(source));
    /// ```
    pub fn source_span(&self, file_id: FileId) -> Span {
        self.get(file_id).source_span()
    }

    /// Return a slice of the source file, given a span.
    ///
    /// ```rust
    /// use codespan::{Files, Span};
    ///
    /// let mut files = Files::new();
    /// let file_id = files.add("test",  "hello world!");
    ///
    /// assert_eq!(files.source_slice(file_id, Span::new(0, 5)), Ok("hello"));
    /// assert!(files.source_slice(file_id, Span::new(0, 100)).is_err());
    /// ```
    pub fn source_slice(
        &self,
        file_id: FileId,
        span: impl Into<Span>,
    ) -> Result<&str, SpanOutOfBoundsError> {
        self.get(file_id).source_slice(span.into())
    }
}

/// A file that is stored in the database.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serialization", derive(Deserialize, Serialize))]
#[cfg_attr(feature = "memory_usage", derive(heapsize_derive::HeapSizeOf))]
struct File {
    /// The name of the file.
    name: String,
    /// The source code of the file.
    source: String,
    /// The starting byte indices in the source code.
    line_starts: Vec<ByteIndex>,
}

// FIXME: Check file size
fn compute_line_starts(source: &str) -> Vec<ByteIndex> {
    std::iter::once(0)
        .chain(source.match_indices('\n').map(|(i, _)| i as u32 + 1))
        .map(ByteIndex::from)
        .collect()
}

impl File {
    fn new(name: String, source: String) -> File {
        let line_starts = compute_line_starts(&source);

        File {
            name,
            source,
            line_starts,
        }
    }

    fn update(&mut self, source: String) {
        let line_starts = compute_line_starts(&source);
        self.source = source;
        self.line_starts = line_starts;
    }

    fn name(&self) -> &str {
        &self.name
    }

    fn line_start(&self, line_index: LineIndex) -> Result<ByteIndex, LineIndexOutOfBoundsError> {
        use std::cmp::Ordering;

        match line_index.cmp(&self.last_line_index()) {
            Ordering::Less => Ok(self.line_starts[line_index.to_usize()]),
            Ordering::Equal => Ok(self.source_span().end()),
            Ordering::Greater => Err(LineIndexOutOfBoundsError {
                given: line_index,
                max: self.last_line_index(),
            }),
        }
    }

    fn last_line_index(&self) -> LineIndex {
        LineIndex::from(self.line_starts.len() as RawIndex)
    }

    fn line_span(&self, line_index: LineIndex) -> Result<Span, LineIndexOutOfBoundsError> {
        let line_start = self.line_start(line_index)?;
        let next_line_start = self.line_start(line_index + LineOffset::from(1))?;

        Ok(Span::new(line_start, next_line_start))
    }

    fn location(&self, byte_index: ByteIndex) -> Result<Location, LocationError> {
        use unicode_segmentation::UnicodeSegmentation;

        match self.line_starts.binary_search(&byte_index) {
            // Found the start of a line
            Ok(line) => Ok(Location::new(line as u32, 0)),
            // Found something in the middle of a line
            Err(next_line) => {
                let line_index = LineIndex::from(next_line as u32 - 1);
                let line_start_index =
                    self.line_start(line_index)
                        .map_err(|_| LocationError::OutOfBounds {
                            given: byte_index,
                            span: self.source_span(),
                        })?;
                let line_src = self
                    .source()
                    .get(line_start_index.to_usize()..byte_index.to_usize())
                    .ok_or_else(|| {
                        let given = byte_index;
                        if given >= self.source_span().end() {
                            let span = self.source_span();
                            LocationError::OutOfBounds { given, span }
                        } else {
                            LocationError::InvalidCharBoundary { given }
                        }
                    })?;

                Ok(Location {
                    line: line_index,
                    column: ColumnIndex::from(line_src.graphemes(true).count() as u32),
                })
            },
        }
    }

    fn source(&self) -> &str {
        &self.source
    }

    fn source_span(&self) -> Span {
        Span::from_str(self.source())
    }

    fn source_slice(&self, span: Span) -> Result<&str, SpanOutOfBoundsError> {
        let start = span.start().to_usize();
        let end = span.end().to_usize();

        self.source.get(start..end).ok_or_else(|| {
            let span = Span::from_str(self.source());
            SpanOutOfBoundsError { given: span, span }
        })
    }
}

#[cfg(test)]
mod test {
    use pretty_assertions::assert_eq;

    use super::*;

    const TEST_SOURCE: &str = "foo\nbar\r\n\nbaz";

    #[test]
    fn line_starts() {
        let mut files = Files::new();
        let file_id = files.add("test", TEST_SOURCE);

        assert_eq!(
            files.get(file_id).line_starts,
            [
                ByteIndex::from(0),  // "foo\n"
                ByteIndex::from(4),  // "bar\r\n"
                ByteIndex::from(9),  // ""
                ByteIndex::from(10), // "baz"
            ],
        );
    }

    #[test]
    fn line_span_sources() {
        let mut files = Files::new();
        let file_id = files.add("test", TEST_SOURCE);

        let line_sources = (0..4)
            .map(|line| {
                let line_span = files.line_span(file_id, line).unwrap();
                files.source_slice(file_id, line_span)
            })
            .collect::<Vec<_>>();

        assert_eq!(
            line_sources,
            [Ok("foo\n"), Ok("bar\r\n"), Ok("\n"), Ok("baz")],
        );
    }
}
<commit_msg>Implement heapsize::HeapSizeOf for FileId<commit_after>#[cfg(feature = "serialization")]
use serde::{Deserialize, Serialize};
use std::num::NonZeroU32;
use std::{error, fmt};

use crate::{ByteIndex, ColumnIndex, LineIndex, LineOffset, Location, RawIndex, Span};

#[derive(Debug, PartialEq)]
pub struct LineIndexOutOfBoundsError {
    pub given: LineIndex,
    pub max: LineIndex,
}

impl error::Error for LineIndexOutOfBoundsError {}

impl fmt::Display for LineIndexOutOfBoundsError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "Line index out of bounds - given: {}, max: {}",
            self.given, self.max
        )
    }
}

#[derive(Debug, PartialEq)]
pub enum LocationError {
    OutOfBounds { given: ByteIndex, span: Span },
    InvalidCharBoundary { given: ByteIndex },
}

impl error::Error for LocationError {}

impl fmt::Display for LocationError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            LocationError::OutOfBounds { given, span } => write!(
                f,
                "Byte index out of bounds - given: {}, span: {}",
                given, span
            ),
            LocationError::InvalidCharBoundary { given } => {
                write!(f, "Byte index within character boundary - given: {}", given)
            },
        }
    }
}

#[derive(Debug, PartialEq)]
pub struct SpanOutOfBoundsError {
    pub given: Span,
    pub span: Span,
}

impl error::Error for SpanOutOfBoundsError {}

impl fmt::Display for SpanOutOfBoundsError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "Span out of bounds - given: {}, span: {}",
            self.given, self.span
        )
    }
}

/// A handle that points to a file in the database.
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serialization", derive(Deserialize, Serialize))]
pub struct FileId(NonZeroU32);

// `HeapSizeOf` isn't implemented for `NonZeroU32` and we can't add it due to trait orphan rules
#[cfg(feature = "memory_usage")]
impl heapsize::HeapSizeOf for FileId {
    fn heap_size_of_children(&self) -> usize {
        0
    }
}

impl FileId {
    /// Offset of our `FileId`'s numeric value to an index on `Files::files`.
    ///
    /// This is to ensure the first `FileId` is non-zero for memory layout optimisations (e.g.
    /// `Option<FileId>` is 4 bytes)
    const OFFSET: u32 = 1;

    fn new(index: usize) -> FileId {
        FileId(NonZeroU32::new(index as u32 + Self::OFFSET).unwrap())
    }

    fn get(self) -> usize {
        (self.0.get() - Self::OFFSET) as usize
    }
}

/// A database of source files.
#[derive(Clone, Debug, Default)]
pub struct Files {
    files: Vec<File>,
}

impl Files {
    /// Create a new, empty database of files.
    pub fn new() -> Files {
        Files::default()
    }

    /// Add a file to the database, returning the handle that can be used to
    /// refer to it again.
    pub fn add(&mut self, name: impl Into<String>, source: impl Into<String>) -> FileId {
        let file_id = FileId::new(self.files.len());
        self.files.push(File::new(name.into(), source.into()));
        file_id
    }

    /// Update a source file in place.
    ///
    /// This will mean that any outstanding byte indexes will now point to
    /// invalid locations.
    pub fn update(&mut self, file_id: FileId, source: impl Into<String>) {
        self.get_mut(file_id).update(source.into())
    }

    /// Get a the source file using the file id.
    // FIXME: return an option or result?
    fn get(&self, file_id: FileId) -> &File {
        &self.files[file_id.get()]
    }

    /// Get a the source file using the file id.
    // FIXME: return an option or result?
    fn get_mut(&mut self, file_id: FileId) -> &mut File {
        &mut self.files[file_id.get()]
    }

    /// Get the name of the source file.
    ///
    /// ```rust
    /// use codespan::Files;
    ///
    /// let name = "test";
    ///
    /// let mut files = Files::new();
    /// let file_id = files.add(name, "hello world!");
    ///
    /// assert_eq!(files.name(file_id), name);
    /// ```
    pub fn name(&self, file_id: FileId) -> &str {
        self.get(file_id).name()
    }

    /// Get the span at the given line index.
    ///
    /// ```rust
    /// use codespan::{Files, LineIndex, LineIndexOutOfBoundsError, Span};
    ///
    /// let mut files = Files::new();
    /// let file_id = files.add("test", "foo\nbar\r\n\nbaz");
    ///
    /// let line_sources = (0..5)
    ///     .map(|line| files.line_span(file_id, line))
    ///     .collect::<Vec<_>>();
    ///
    /// assert_eq!(
    ///     line_sources,
    ///     [
    ///         Ok(Span::new(0, 4)),    // 0: "foo\n"
    ///         Ok(Span::new(4, 9)),    // 1: "bar\r\n"
    ///         Ok(Span::new(9, 10)),   // 2: ""
    ///         Ok(Span::new(10, 13)),  // 3: "baz"
    ///         Err(LineIndexOutOfBoundsError {
    ///             given: LineIndex::from(5),
    ///             max: LineIndex::from(4),
    ///         }),
    ///     ]
    /// );
    /// ```
    pub fn line_span(
        &self,
        file_id: FileId,
        line_index: impl Into<LineIndex>,
    ) -> Result<Span, LineIndexOutOfBoundsError> {
        self.get(file_id).line_span(line_index.into())
    }

    /// Get the location at the given byte index in the source file.
    ///
    /// ```rust
    /// use codespan::{ByteIndex, Files, Location, LocationError, Span};
    ///
    /// let mut files = Files::new();
    /// let file_id = files.add("test", "foo\nbar\r\n\nbaz");
    ///
    /// assert_eq!(files.location(file_id, 0), Ok(Location::new(0, 0)));
    /// assert_eq!(files.location(file_id, 7), Ok(Location::new(1, 3)));
    /// assert_eq!(files.location(file_id, 8), Ok(Location::new(1, 4)));
    /// assert_eq!(files.location(file_id, 9), Ok(Location::new(2, 0)));
    /// assert_eq!(
    ///     files.location(file_id, 100),
    ///     Err(LocationError::OutOfBounds {
    ///         given: ByteIndex::from(100),
    ///         span: Span::new(0, 13),
    ///     }),
    /// );
    /// ```
    pub fn location(
        &self,
        file_id: FileId,
        byte_index: impl Into<ByteIndex>,
    ) -> Result<Location, LocationError> {
        self.get(file_id).location(byte_index.into())
    }

    /// Get the source of the file.
    ///
    /// ```rust
    /// use codespan::Files;
    ///
    /// let source = "hello world!";
    ///
    /// let mut files = Files::new();
    /// let file_id = files.add("test", source);
    ///
    /// assert_eq!(files.source(file_id), source);
    /// ```
    pub fn source(&self, file_id: FileId) -> &str {
        self.get(file_id).source()
    }

    /// Return the span of the full source.
    ///
    /// ```rust
    /// use codespan::{Files, Span};
    ///
    /// let source = "hello world!";
    ///
    /// let mut files = Files::new();
    /// let file_id = files.add("test", source);
    ///
    /// assert_eq!(files.source_span(file_id), Span::from_str(source));
    /// ```
    pub fn source_span(&self, file_id: FileId) -> Span {
        self.get(file_id).source_span()
    }

    /// Return a slice of the source file, given a span.
    ///
    /// ```rust
    /// use codespan::{Files, Span};
    ///
    /// let mut files = Files::new();
    /// let file_id = files.add("test",  "hello world!");
    ///
    /// assert_eq!(files.source_slice(file_id, Span::new(0, 5)), Ok("hello"));
    /// assert!(files.source_slice(file_id, Span::new(0, 100)).is_err());
    /// ```
    pub fn source_slice(
        &self,
        file_id: FileId,
        span: impl Into<Span>,
    ) -> Result<&str, SpanOutOfBoundsError> {
        self.get(file_id).source_slice(span.into())
    }
}

/// A file that is stored in the database.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serialization", derive(Deserialize, Serialize))]
#[cfg_attr(feature = "memory_usage", derive(heapsize_derive::HeapSizeOf))]
struct File {
    /// The name of the file.
    name: String,
    /// The source code of the file.
    source: String,
    /// The starting byte indices in the source code.
    line_starts: Vec<ByteIndex>,
}

// FIXME: Check file size
fn compute_line_starts(source: &str) -> Vec<ByteIndex> {
    std::iter::once(0)
        .chain(source.match_indices('\n').map(|(i, _)| i as u32 + 1))
        .map(ByteIndex::from)
        .collect()
}

impl File {
    fn new(name: String, source: String) -> File {
        let line_starts = compute_line_starts(&source);

        File {
            name,
            source,
            line_starts,
        }
    }

    fn update(&mut self, source: String) {
        let line_starts = compute_line_starts(&source);
        self.source = source;
        self.line_starts = line_starts;
    }

    fn name(&self) -> &str {
        &self.name
    }

    fn line_start(&self, line_index: LineIndex) -> Result<ByteIndex, LineIndexOutOfBoundsError> {
        use std::cmp::Ordering;

        match line_index.cmp(&self.last_line_index()) {
            Ordering::Less => Ok(self.line_starts[line_index.to_usize()]),
            Ordering::Equal => Ok(self.source_span().end()),
            Ordering::Greater => Err(LineIndexOutOfBoundsError {
                given: line_index,
                max: self.last_line_index(),
            }),
        }
    }

    fn last_line_index(&self) -> LineIndex {
        LineIndex::from(self.line_starts.len() as RawIndex)
    }

    fn line_span(&self, line_index: LineIndex) -> Result<Span, LineIndexOutOfBoundsError> {
        let line_start = self.line_start(line_index)?;
        let next_line_start = self.line_start(line_index + LineOffset::from(1))?;

        Ok(Span::new(line_start, next_line_start))
    }

    fn location(&self, byte_index: ByteIndex) -> Result<Location, LocationError> {
        use unicode_segmentation::UnicodeSegmentation;

        match self.line_starts.binary_search(&byte_index) {
            // Found the start of a line
            Ok(line) => Ok(Location::new(line as u32, 0)),
            // Found something in the middle of a line
            Err(next_line) => {
                let line_index = LineIndex::from(next_line as u32 - 1);
                let line_start_index =
                    self.line_start(line_index)
                        .map_err(|_| LocationError::OutOfBounds {
                            given: byte_index,
                            span: self.source_span(),
                        })?;
                let line_src = self
                    .source()
                    .get(line_start_index.to_usize()..byte_index.to_usize())
                    .ok_or_else(|| {
                        let given = byte_index;
                        if given >= self.source_span().end() {
                            let span = self.source_span();
                            LocationError::OutOfBounds { given, span }
                        } else {
                            LocationError::InvalidCharBoundary { given }
                        }
                    })?;

                Ok(Location {
                    line: line_index,
                    column: ColumnIndex::from(line_src.graphemes(true).count() as u32),
                })
            },
        }
    }

    fn source(&self) -> &str {
        &self.source
    }

    fn source_span(&self) -> Span {
        Span::from_str(self.source())
    }

    fn source_slice(&self, span: Span) -> Result<&str, SpanOutOfBoundsError> {
        let start = span.start().to_usize();
        let end = span.end().to_usize();

        self.source.get(start..end).ok_or_else(|| {
            let span = Span::from_str(self.source());
            SpanOutOfBoundsError { given: span, span }
        })
    }
}

#[cfg(test)]
mod test {
    use pretty_assertions::assert_eq;

    use super::*;

    const TEST_SOURCE: &str = "foo\nbar\r\n\nbaz";

    #[test]
    fn line_starts() {
        let mut files = Files::new();
        let file_id = files.add("test", TEST_SOURCE);

        assert_eq!(
            files.get(file_id).line_starts,
            [
                ByteIndex::from(0),  // "foo\n"
                ByteIndex::from(4),  // "bar\r\n"
                ByteIndex::from(9),  // ""
                ByteIndex::from(10), // "baz"
            ],
        );
    }

    #[test]
    fn line_span_sources() {
        let mut files = Files::new();
        let file_id = files.add("test", TEST_SOURCE);

        let line_sources = (0..4)
            .map(|line| {
                let line_span = files.line_span(file_id, line).unwrap();
                files.source_slice(file_id, line_span)
            })
            .collect::<Vec<_>>();

        assert_eq!(
            line_sources,
            [Ok("foo\n"), Ok("bar\r\n"), Ok("\n"), Ok("baz")],
        );
    }
}
<|endoftext|>


<commit_before>use rustc::ty;
use rustc_ast::ast::{self, MetaItem};
use rustc_span::symbol::{sym, Symbol};

pub(crate) use self::drop_flag_effects::*;
pub use self::framework::{
    visit_results, Analysis, AnalysisDomain, BorrowckFlowState, BorrowckResults, BottomValue,
    Engine, GenKill, GenKillAnalysis, Results, ResultsCursor, ResultsRefCursor, ResultsVisitor,
};
pub use self::impls::{
    borrows::Borrows, DefinitelyInitializedPlaces, EverInitializedPlaces, MaybeBorrowedLocals,
    MaybeInitializedPlaces, MaybeMutBorrowedLocals, MaybeRequiresStorage, MaybeStorageLive,
    MaybeUninitializedPlaces,
};

use self::move_paths::MoveData;

pub mod drop_flag_effects;
pub mod framework;
mod impls;
pub mod move_paths;

pub(crate) mod indexes {
    pub(crate) use super::{
        impls::borrows::BorrowIndex,
        move_paths::{InitIndex, MoveOutIndex, MovePathIndex},
    };
}

pub struct MoveDataParamEnv<'tcx> {
    pub(crate) move_data: MoveData<'tcx>,
    pub(crate) param_env: ty::ParamEnv<'tcx>,
}

pub(crate) fn has_rustc_mir_with(attrs: &[ast::Attribute], name: Symbol) -> Option<MetaItem> {
    for attr in attrs {
        if attr.check_name(sym::rustc_mir) {
            let items = attr.meta_item_list();
            for item in items.iter().flat_map(|l| l.iter()) {
                match item.meta_item() {
                    Some(mi) if mi.check_name(name) => return Some(mi.clone()),
                    _ => continue,
                }
            }
        }
    }
    None
}
<commit_msg>Make `framework` a private module<commit_after>use rustc::ty;
use rustc_ast::ast::{self, MetaItem};
use rustc_span::symbol::{sym, Symbol};

pub(crate) use self::drop_flag_effects::*;
pub use self::framework::{
    visit_results, Analysis, AnalysisDomain, BorrowckFlowState, BorrowckResults, BottomValue,
    Engine, GenKill, GenKillAnalysis, Results, ResultsCursor, ResultsRefCursor, ResultsVisitor,
};
pub use self::impls::{
    borrows::Borrows, DefinitelyInitializedPlaces, EverInitializedPlaces, MaybeBorrowedLocals,
    MaybeInitializedPlaces, MaybeMutBorrowedLocals, MaybeRequiresStorage, MaybeStorageLive,
    MaybeUninitializedPlaces,
};

use self::move_paths::MoveData;

pub mod drop_flag_effects;
mod framework;
mod impls;
pub mod move_paths;

pub(crate) mod indexes {
    pub(crate) use super::{
        impls::borrows::BorrowIndex,
        move_paths::{InitIndex, MoveOutIndex, MovePathIndex},
    };
}

pub struct MoveDataParamEnv<'tcx> {
    pub(crate) move_data: MoveData<'tcx>,
    pub(crate) param_env: ty::ParamEnv<'tcx>,
}

pub(crate) fn has_rustc_mir_with(attrs: &[ast::Attribute], name: Symbol) -> Option<MetaItem> {
    for attr in attrs {
        if attr.check_name(sym::rustc_mir) {
            let items = attr.meta_item_list();
            for item in items.iter().flat_map(|l| l.iter()) {
                match item.meta_item() {
                    Some(mi) if mi.check_name(name) => return Some(mi.clone()),
                    _ => continue,
                }
            }
        }
    }
    None
}
<|endoftext|>


<commit_before>use super::super::{Y, X, Coord, Grid};
use super::*;

pub struct Field {
    size: i32,
    n_alpha: i32,
    cand: Grid<Cand>,
    value: Grid<Value>,
    clue_front: Vec<Clue>,
    clue_back: Vec<Clue>,
    inconsistent: bool,
    solved: bool,
}

impl Field {
    pub fn empty_board(size: i32, n_alpha: i32) -> Field {
        assert!(n_alpha >= 2);
        Field {
            size,
            n_alpha,
            cand: Grid::new(size, size, Cand((1u32 << n_alpha) - 1)),
            value: Grid::new(size, size, UNDECIDED),
            clue_front: vec![NO_CLUE; (2 * size) as usize],
            clue_back: vec![NO_CLUE; (2 * size) as usize],
            inconsistent: false,
            solved: false,
        }
    }
    pub fn decide(&mut self, cell: Coord, val: Value) {
        let current = self.value[cell];
        if current != UNDECIDED {
            if current != val {
                self.inconsistent = true;
            }
            return;
        }

        self.value[cell] = val;

        if val == EMPTY {
            self.limit_cand(cell, Cand(0));
        } else if val == SOME {
            self.inspect_cell(cell);
        } else if val != UNDECIDED {
            self.limit_cand(cell, Cand::singleton(val.0));

            let (Y(y), X(x)) = cell;
            let limit = !Cand::singleton(val.0);
            for y2 in 0..self.size {
                if y != y2 { self.limit_cand((Y(y2), X(x)), limit); }
            }
            for x2 in 0..self.size {
                if x != x2 { self.limit_cand((Y(y), X(x2)), limit); }
            }
        }
    }
    fn get_clue(&self, loc: ClueLoc, idx: i32) -> Clue {
        match loc {
            ClueLoc::Left => self.clue_front[idx as usize],
            ClueLoc::Right => self.clue_back[idx as usize],
            ClueLoc::Top => self.clue_front[(idx + self.size) as usize],
            ClueLoc::Bottom => self.clue_back[(idx + self.size) as usize],
        }
    }
    fn set_clue(&mut self, loc: ClueLoc, idx: i32, clue: Clue) {
        let current = self.get_clue(loc, idx);
        if current != NO_CLUE {
            if current != clue {
                self.inconsistent = true;
            }
            return;
        }
        let size = self.size;
        match loc {
            ClueLoc::Left => self.clue_front[idx as usize] = clue,
            ClueLoc::Right => self.clue_back[idx as usize] = clue,
            ClueLoc::Top => self.clue_front[(idx + size) as usize] = clue,
            ClueLoc::Bottom => self.clue_back[(idx + size) as usize] = clue,
        }
        if loc == ClueLoc::Left || loc == ClueLoc::Right {
            self.inspect_row(idx);
        } else {
            self.inspect_row(idx + size);
        }
    }
    /// Returns `pos`-th cell of group `gid`.
    fn group(&self, gid: i32, pos: i32) -> Coord {
        if gid < self.size {
            (Y(gid), X(pos))
        } else {
            (Y(pos), X(gid - self.size))
        }
    }
    /// Returns `pos`-th cell of group `gid` with reversed indexing of cells when `dir` is `true`.
    fn directed_group(&self, gid: i32, pos: i32, dir: bool) -> Coord {
        self.group(gid, if dir { self.size - pos - 1 } else { pos })
    }
    fn limit_cand(&mut self, cell: Coord, lim: Cand) {
        let current_cand = self.cand[cell];

        if (current_cand & lim) == current_cand {
            return;
        }

        let new_cand = current_cand & lim;
        self.cand[cell] = new_cand;

        if self.cand[cell] == Cand(0) {
            self.decide(cell, EMPTY);
        }
        self.inspect_cell(cell);
    }
    fn inspect_cell(&mut self, cell: Coord) {
        if self.value[cell] == SOME && self.cand[cell].count_set_cands() == 1 {
            let val = Value(self.cand[cell].smallest_set_cand());
            self.decide(cell, val);
        }
        let size = self.size;
        let (Y(y), X(x)) = cell;
        self.inspect_row(y);
        self.inspect_row(x + size);
    }
    fn inspect_row(&mut self, group: i32) {
        let size = self.size;
        let n_alpha = self.n_alpha;

        let mut n_some = 0;
        let mut n_empty = 0;

        for i in 0..size {
            let v = self.value[self.group(group, i)];
            if v == EMPTY {
                n_empty += 1;
            } else if v != UNDECIDED {
                n_some += 1;
            }
        }

        if n_some == n_alpha {
            for i in 0..size {
                let c = self.group(group, i);
                if self.value[c] == UNDECIDED {
                    self.decide(c, EMPTY);
                }
            }
        }
        if n_empty == size - n_alpha {
            for i in 0..size {
                let c = self.group(group, i);
                if self.value[c] == UNDECIDED {
                    self.decide(c, SOME);
                }
            }
        }

        for a in 0..n_alpha {
            let mut loc = -1;
            for i in 0..size {
                if self.cand[self.group(group, i)].is_set(a) {
                    if loc == -1 {
                        loc = i;
                    } else {
                        loc = -2;
                        break;
                    }
                }
            }
            if loc == -1 {
                self.inconsistent = true;
                return;
            } else if loc != -2 {
                let c = self.group(group, loc);
                self.decide(c, Value(a));
            }
        }
        for &dir in [true, false].iter() {
            let clue = if !dir { self.clue_front[group as usize] } else { self.clue_back[group as usize] };
            if clue == NO_CLUE {
                continue;
            }

            let mut first_nonempty_id = -1;
            for i in 0..size {
                let v = self.value[self.directed_group(group, i, dir)];
                if v != EMPTY {
                    first_nonempty_id = i;
                    break;
                }
            }
            if first_nonempty_id == -1 {
                self.inconsistent = true;
                return;
            }
            let first_nonempty_cell = self.directed_group(group, first_nonempty_id, dir);
            self.limit_cand(first_nonempty_cell, Cand::singleton(clue.0));

            let mut first_diff_id = -1;
            for i in 0..size {
                let v = self.value[self.directed_group(group, i, dir)];
                if v != UNDECIDED && !self.cand[self.directed_group(group, i, dir)].is_set(clue.0) {
                    first_diff_id = i;
                }
            }
            if first_diff_id != -1 {
                for i in (first_diff_id + 1)..size {
                    let c = self.directed_group(group, i, dir);
                    self.limit_cand(c, !Cand::singleton(clue.0));
                }
            }

            let mut n_back_diff = n_alpha - 1;
            for i in 0..size {
                let c = self.directed_group(group, i, !dir);
                let v = self.value[c];
                if v != EMPTY {
                    self.limit_cand(c, !Cand::singleton(clue.0));
                    n_back_diff -= 1;
                    if n_back_diff == 0 { break; }
                }
            }
        }
    }
}

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

    #[test]
    fn test_deduction() {
        {
            // a symbol shouldn't occur more than once in a row / a column
            let mut field = Field::empty_board(5, 3);

            assert_eq!(field.cand[(Y(0), X(0))], Cand(7));
            assert_eq!(field.cand[(Y(0), X(1))], Cand(7));
            assert_eq!(field.cand[(Y(1), X(1))], Cand(7));

            field.decide((Y(0), X(0)), Value(0));

            assert_eq!(field.inconsistent, false);
            assert_eq!(field.cand[(Y(0), X(0))], Cand(1));
            assert_eq!(field.cand[(Y(0), X(1))], Cand(6));
            assert_eq!(field.cand[(Y(1), X(0))], Cand(6));
            assert_eq!(field.cand[(Y(1), X(1))], Cand(7));
        }
        {
            // there must be exactly `n_alpha` symbols in a row / a column
            let mut field = Field::empty_board(5, 3);

            field.decide((Y(1), X(0)), SOME);
            field.decide((Y(1), X(1)), SOME);
            field.decide((Y(1), X(2)), SOME);

            assert_eq!(field.inconsistent, false);
            assert_eq!(field.value[(Y(1), X(3))], EMPTY);
        }
        {
            // there must be exactly `n_alpha` symbols in a row / a column
            let mut field = Field::empty_board(5, 3);

            field.decide((Y(3), X(2)), EMPTY);
            field.decide((Y(4), X(2)), EMPTY);

            assert_eq!(field.inconsistent, false);
            assert_eq!(field.value[(Y(1), X(2))], SOME);
        }
        {
            let mut field = Field::empty_board(5, 3);

            field.limit_cand((Y(0), X(2)), Cand(5));
            field.limit_cand((Y(2), X(2)), Cand(5));
            field.limit_cand((Y(3), X(2)), Cand(5));
            field.limit_cand((Y(4), X(2)), Cand(5));

            assert_eq!(field.inconsistent, false);
            assert_eq!(field.value[(Y(1), X(2))], Value(1));
            assert_eq!(field.cand[(Y(1), X(3))], Cand(5));
        }
    }

    #[test]
    fn test_clue() {
        {
            let mut field = Field::empty_board(5, 3);

            field.set_clue(ClueLoc::Left, 0, Clue(0));

            assert_eq!(field.cand[(Y(0), X(0))], Cand(1));
            assert_eq!(field.cand[(Y(0), X(3))], Cand(6));
            assert_eq!(field.cand[(Y(0), X(4))], Cand(6));
        }
    }
}
<commit_msg>Endview: bug fix<commit_after>use super::super::{Y, X, Coord, Grid};
use super::*;

pub struct Field {
    size: i32,
    n_alpha: i32,
    cand: Grid<Cand>,
    value: Grid<Value>,
    clue_front: Vec<Clue>,
    clue_back: Vec<Clue>,
    inconsistent: bool,
    solved: bool,
}

impl Field {
    pub fn empty_board(size: i32, n_alpha: i32) -> Field {
        assert!(n_alpha >= 2);
        Field {
            size,
            n_alpha,
            cand: Grid::new(size, size, Cand((1u32 << n_alpha) - 1)),
            value: Grid::new(size, size, UNDECIDED),
            clue_front: vec![NO_CLUE; (2 * size) as usize],
            clue_back: vec![NO_CLUE; (2 * size) as usize],
            inconsistent: false,
            solved: false,
        }
    }
    pub fn decide(&mut self, cell: Coord, val: Value) {
        let current = self.value[cell];
        if current != UNDECIDED && !(current == SOME && val != EMPTY) {
            if current != val {
                self.inconsistent = true;
            }
            return;
        }

        self.value[cell] = val;

        if val == EMPTY {
            self.limit_cand(cell, Cand(0));
        } else if val == SOME {
            self.inspect_cell(cell);
        } else if val != UNDECIDED {
            self.limit_cand(cell, Cand::singleton(val.0));

            let (Y(y), X(x)) = cell;
            let limit = !Cand::singleton(val.0);
            for y2 in 0..self.size {
                if y != y2 { self.limit_cand((Y(y2), X(x)), limit); }
            }
            for x2 in 0..self.size {
                if x != x2 { self.limit_cand((Y(y), X(x2)), limit); }
            }
        }
    }
    fn get_clue(&self, loc: ClueLoc, idx: i32) -> Clue {
        match loc {
            ClueLoc::Left => self.clue_front[idx as usize],
            ClueLoc::Right => self.clue_back[idx as usize],
            ClueLoc::Top => self.clue_front[(idx + self.size) as usize],
            ClueLoc::Bottom => self.clue_back[(idx + self.size) as usize],
        }
    }
    fn set_clue(&mut self, loc: ClueLoc, idx: i32, clue: Clue) {
        let current = self.get_clue(loc, idx);
        if current != NO_CLUE {
            if current != clue {
                self.inconsistent = true;
            }
            return;
        }
        let size = self.size;
        match loc {
            ClueLoc::Left => self.clue_front[idx as usize] = clue,
            ClueLoc::Right => self.clue_back[idx as usize] = clue,
            ClueLoc::Top => self.clue_front[(idx + size) as usize] = clue,
            ClueLoc::Bottom => self.clue_back[(idx + size) as usize] = clue,
        }
        if loc == ClueLoc::Left || loc == ClueLoc::Right {
            self.inspect_row(idx);
        } else {
            self.inspect_row(idx + size);
        }
    }
    /// Returns `pos`-th cell of group `gid`.
    fn group(&self, gid: i32, pos: i32) -> Coord {
        if gid < self.size {
            (Y(gid), X(pos))
        } else {
            (Y(pos), X(gid - self.size))
        }
    }
    /// Returns `pos`-th cell of group `gid` with reversed indexing of cells when `dir` is `true`.
    fn directed_group(&self, gid: i32, pos: i32, dir: bool) -> Coord {
        self.group(gid, if dir { self.size - pos - 1 } else { pos })
    }
    fn limit_cand(&mut self, cell: Coord, lim: Cand) {
        let current_cand = self.cand[cell];

        if (current_cand & lim) == current_cand {
            return;
        }

        let new_cand = current_cand & lim;
        self.cand[cell] = new_cand;

        if self.cand[cell] == Cand(0) {
            self.decide(cell, EMPTY);
        }
        self.inspect_cell(cell);
    }
    fn inspect_cell(&mut self, cell: Coord) {
        if self.value[cell] == SOME && self.cand[cell].count_set_cands() == 1 {
            let val = Value(self.cand[cell].smallest_set_cand());
            self.decide(cell, val);
        }
        let size = self.size;
        let (Y(y), X(x)) = cell;
        self.inspect_row(y);
        self.inspect_row(x + size);
    }
    fn inspect_row(&mut self, group: i32) {
        let size = self.size;
        let n_alpha = self.n_alpha;

        let mut n_some = 0;
        let mut n_empty = 0;

        for i in 0..size {
            let v = self.value[self.group(group, i)];
            if v == EMPTY {
                n_empty += 1;
            } else if v != UNDECIDED {
                n_some += 1;
            }
        }

        if n_some == n_alpha {
            for i in 0..size {
                let c = self.group(group, i);
                if self.value[c] == UNDECIDED {
                    self.decide(c, EMPTY);
                }
            }
        }
        if n_empty == size - n_alpha {
            for i in 0..size {
                let c = self.group(group, i);
                if self.value[c] == UNDECIDED {
                    self.decide(c, SOME);
                }
            }
        }

        for a in 0..n_alpha {
            let mut loc = -1;
            for i in 0..size {
                if self.cand[self.group(group, i)].is_set(a) {
                    if loc == -1 {
                        loc = i;
                    } else {
                        loc = -2;
                        break;
                    }
                }
            }
            if loc == -1 {
                self.inconsistent = true;
                return;
            } else if loc != -2 {
                let c = self.group(group, loc);
                self.decide(c, Value(a));
            }
        }
        for &dir in [true, false].iter() {
            let clue = if !dir { self.clue_front[group as usize] } else { self.clue_back[group as usize] };
            if clue == NO_CLUE {
                continue;
            }

            let mut first_nonempty_id = -1;
            for i in 0..size {
                let v = self.value[self.directed_group(group, i, dir)];
                if v != EMPTY {
                    first_nonempty_id = i;
                    break;
                }
            }
            if first_nonempty_id == -1 {
                self.inconsistent = true;
                return;
            }
            let first_nonempty_cell = self.directed_group(group, first_nonempty_id, dir);
            self.limit_cand(first_nonempty_cell, Cand::singleton(clue.0));

            let mut first_diff_id = -1;
            for i in 0..size {
                let v = self.value[self.directed_group(group, i, dir)];
                if v != UNDECIDED && !self.cand[self.directed_group(group, i, dir)].is_set(clue.0) {
                    first_diff_id = i;
                }
            }
            if first_diff_id != -1 {
                for i in (first_diff_id + 1)..size {
                    let c = self.directed_group(group, i, dir);
                    self.limit_cand(c, !Cand::singleton(clue.0));
                }
            }

            let mut n_back_diff = n_alpha - 1;
            for i in 0..size {
                let c = self.directed_group(group, i, !dir);
                let v = self.value[c];
                if v != EMPTY {
                    self.limit_cand(c, !Cand::singleton(clue.0));
                    n_back_diff -= 1;
                    if n_back_diff == 0 { break; }
                }
            }
        }
    }
}

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

    #[test]
    fn test_deduction() {
        {
            // a symbol shouldn't occur more than once in a row / a column
            let mut field = Field::empty_board(5, 3);

            assert_eq!(field.cand[(Y(0), X(0))], Cand(7));
            assert_eq!(field.cand[(Y(0), X(1))], Cand(7));
            assert_eq!(field.cand[(Y(1), X(1))], Cand(7));

            field.decide((Y(0), X(0)), Value(0));

            assert_eq!(field.inconsistent, false);
            assert_eq!(field.cand[(Y(0), X(0))], Cand(1));
            assert_eq!(field.cand[(Y(0), X(1))], Cand(6));
            assert_eq!(field.cand[(Y(1), X(0))], Cand(6));
            assert_eq!(field.cand[(Y(1), X(1))], Cand(7));
        }
        {
            // there must be exactly `n_alpha` symbols in a row / a column
            let mut field = Field::empty_board(5, 3);

            field.decide((Y(1), X(0)), SOME);
            field.decide((Y(1), X(1)), SOME);
            field.decide((Y(1), X(2)), SOME);

            assert_eq!(field.inconsistent, false);
            assert_eq!(field.value[(Y(1), X(3))], EMPTY);
        }
        {
            // there must be exactly `n_alpha` symbols in a row / a column
            let mut field = Field::empty_board(5, 3);

            field.decide((Y(3), X(2)), EMPTY);
            field.decide((Y(4), X(2)), EMPTY);

            assert_eq!(field.inconsistent, false);
            assert_eq!(field.value[(Y(1), X(2))], SOME);
        }
        {
            let mut field = Field::empty_board(5, 3);

            field.limit_cand((Y(0), X(2)), Cand(5));
            field.limit_cand((Y(2), X(2)), Cand(5));
            field.limit_cand((Y(3), X(2)), Cand(5));
            field.limit_cand((Y(4), X(2)), Cand(5));

            assert_eq!(field.inconsistent, false);
            assert_eq!(field.value[(Y(1), X(2))], Value(1));
            assert_eq!(field.cand[(Y(1), X(3))], Cand(5));
        }
    }

    #[test]
    fn test_clue() {
        {
            let mut field = Field::empty_board(5, 3);

            field.set_clue(ClueLoc::Left, 0, Clue(0));

            assert_eq!(field.cand[(Y(0), X(0))], Cand(1));
            assert_eq!(field.cand[(Y(0), X(3))], Cand(6));
            assert_eq!(field.cand[(Y(0), X(4))], Cand(6));
        }
    }
}
<|endoftext|>


<commit_before>#[cfg(not(target_os = "redox"))]
use libc::{c_char, c_int, link, rename, unlink, O_CLOEXEC, O_CREAT, O_EXCL, O_RDWR};
use std::env;
use std::ffi::{CString, OsStr};
use std::fs::{self, File, OpenOptions};
use std::io;
use std::os::unix::ffi::OsStrExt;
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
use std::path::Path;
use util;

#[cfg(all(lfs_support, target_os = "linux"))]
use libc::{fstat64 as fstat, open64 as open, stat64 as stat_t};

#[cfg(not(any(all(lfs_support, target_os = "linux"), target_os = "redox")))]
use libc::{fstat, open, stat as stat_t};

#[cfg(target_os = "redox")]
use syscall::{self, fstat, open, Stat as stat_t, O_CLOEXEC, O_CREAT, O_EXCL, O_RDWR};

#[cfg(not(target_os = "redox"))]
#[inline(always)]
pub fn cvt_err(result: c_int) -> io::Result<c_int> {
    if result == -1 {
        Err(io::Error::last_os_error())
    } else {
        Ok(result)
    }
}

#[cfg(target_os = "redox")]
#[inline(always)]
pub fn cvt_err(result: Result<usize, syscall::Error>) -> io::Result<usize> {
    result.map_err(|err| io::Error::from_raw_os_error(err.errno))
}

// Stolen from std.
pub fn cstr(path: &Path) -> io::Result<CString> {
    CString::new(path.as_os_str().as_bytes())
        .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "path contained a null"))
}

#[cfg(not(target_os = "redox"))]
pub fn create_named(path: &Path) -> io::Result<File> {
    unsafe {
        let path = cstr(path)?;
        let fd = cvt_err(open(
            path.as_ptr() as *const c_char,
            O_CLOEXEC | O_EXCL | O_RDWR | O_CREAT,
            0o600,
        ))?;
        Ok(FromRawFd::from_raw_fd(fd))
    }
}

#[cfg(target_os = "redox")]
pub fn create_named(path: &Path) -> io::Result<File> {
    unsafe {
        let fd = cvt_err(open(
            path.as_os_str().as_bytes(),
            O_CLOEXEC | O_EXCL | O_RDWR | O_CREAT | 0o600,
        ))?;
        Ok(FromRawFd::from_raw_fd(fd))
    }
}

fn create_unlinked(path: &Path) -> io::Result<File> {
    let tmp;
    // shadow this to decrease the lifetime. It can't live longer than `tmp`.
    let mut path = path;
    if !path.is_absolute() {
        let cur_dir = env::current_dir()?;
        tmp = cur_dir.join(path);
        path = &tmp;
    }

    let f = create_named(path)?;
    // don't care whether the path has already been unlinked,
    // but perhaps there are some IO error conditions we should send up?
    let _ = fs::remove_file(path);
    Ok(f)
}

#[cfg(target_os = "linux")]
pub fn create(dir: &Path) -> io::Result<File> {
    use libc::O_TMPFILE;
    match unsafe {
        let path = cstr(dir)?;
        open(
            path.as_ptr() as *const c_char,
            O_CLOEXEC | O_EXCL | O_TMPFILE | O_RDWR,
            0o600,
        )
    } {
        -1 => create_unix(dir),
        fd => Ok(unsafe { FromRawFd::from_raw_fd(fd) }),
    }
}

#[cfg(not(target_os = "linux"))]
pub fn create(dir: &Path) -> io::Result<File> {
    create_unix(dir)
}

fn create_unix(dir: &Path) -> io::Result<File> {
    util::create_helper(
        dir,
        OsStr::new(".tmp"),
        OsStr::new(""),
        ::NUM_RAND_CHARS,
        |path| create_unlinked(&path),
    )
}

unsafe fn stat(fd: RawFd) -> io::Result<stat_t> {
    let mut meta: stat_t = ::std::mem::zeroed();
    cvt_err(fstat(fd, &mut meta))?;
    Ok(meta)
}

pub fn reopen(file: &File, path: &Path) -> io::Result<File> {
    let new_file = OpenOptions::new().read(true).write(true).open(path)?;
    unsafe {
        let old_meta = stat(file.as_raw_fd())?;
        let new_meta = stat(new_file.as_raw_fd())?;
        if old_meta.st_dev != new_meta.st_dev || old_meta.st_ino != new_meta.st_ino {
            return Err(io::Error::new(
                io::ErrorKind::NotFound,
                "original tempfile has been replaced",
            ));
        }
        Ok(new_file)
    }
}

#[cfg(not(target_os = "redox"))]
pub fn persist(old_path: &Path, new_path: &Path, overwrite: bool) -> io::Result<()> {
    unsafe {
        let old_path = cstr(old_path)?;
        let new_path = cstr(new_path)?;
        if overwrite {
            cvt_err(rename(
                old_path.as_ptr() as *const c_char,
                new_path.as_ptr() as *const c_char,
            ))?;
        } else {
            cvt_err(link(
                old_path.as_ptr() as *const c_char,
                new_path.as_ptr() as *const c_char,
            ))?;
            // Ignore unlink errors. Can we do better?
            // On recent linux, we can use renameat2 to do this atomically.
            let _ = unlink(old_path.as_ptr() as *const c_char);
        }
        Ok(())
    }
}

#[cfg(target_os = "redox")]
pub fn persist(old_path: &Path, new_path: &Path, overwrite: bool) -> io::Result<()> {
    // XXX implement when possible
    Err(io::Error::from_raw_os_error(syscall::ENOSYS))
}
<commit_msg>fix lfs support<commit_after>use std::env;
use std::ffi::{CString, OsStr};
use std::fs::{self, File, OpenOptions};
use std::io;
use std::os::unix::ffi::OsStrExt;
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
use std::path::Path;
use util;

#[cfg(target_os = "linux")]
use libc::{fstat64 as fstat, open64 as open, stat64 as stat_t};

#[cfg(target_os = "redox")]
use syscall::{self, fstat, open, Stat as stat_t, O_CLOEXEC, O_CREAT, O_EXCL, O_RDWR};

#[cfg(not(target_os = "redox"))]
use libc::{c_char, c_int, link, rename, unlink, O_CLOEXEC, O_CREAT, O_EXCL, O_RDWR};

#[cfg(not(any(target_os = "linux", target_os = "redox")))]
use libc::{fstat, open, stat as stat_t};

#[cfg(not(target_os = "redox"))]
#[inline(always)]
pub fn cvt_err(result: c_int) -> io::Result<c_int> {
    if result == -1 {
        Err(io::Error::last_os_error())
    } else {
        Ok(result)
    }
}

#[cfg(target_os = "redox")]
#[inline(always)]
pub fn cvt_err(result: Result<usize, syscall::Error>) -> io::Result<usize> {
    result.map_err(|err| io::Error::from_raw_os_error(err.errno))
}

// Stolen from std.
pub fn cstr(path: &Path) -> io::Result<CString> {
    CString::new(path.as_os_str().as_bytes())
        .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "path contained a null"))
}

#[cfg(not(target_os = "redox"))]
pub fn create_named(path: &Path) -> io::Result<File> {
    unsafe {
        let path = cstr(path)?;
        let fd = cvt_err(open(
            path.as_ptr() as *const c_char,
            O_CLOEXEC | O_EXCL | O_RDWR | O_CREAT,
            0o600,
        ))?;
        Ok(FromRawFd::from_raw_fd(fd))
    }
}

#[cfg(target_os = "redox")]
pub fn create_named(path: &Path) -> io::Result<File> {
    unsafe {
        let fd = cvt_err(open(
            path.as_os_str().as_bytes(),
            O_CLOEXEC | O_EXCL | O_RDWR | O_CREAT | 0o600,
        ))?;
        Ok(FromRawFd::from_raw_fd(fd))
    }
}

fn create_unlinked(path: &Path) -> io::Result<File> {
    let tmp;
    // shadow this to decrease the lifetime. It can't live longer than `tmp`.
    let mut path = path;
    if !path.is_absolute() {
        let cur_dir = env::current_dir()?;
        tmp = cur_dir.join(path);
        path = &tmp;
    }

    let f = create_named(path)?;
    // don't care whether the path has already been unlinked,
    // but perhaps there are some IO error conditions we should send up?
    let _ = fs::remove_file(path);
    Ok(f)
}

#[cfg(target_os = "linux")]
pub fn create(dir: &Path) -> io::Result<File> {
    use libc::O_TMPFILE;
    match unsafe {
        let path = cstr(dir)?;
        open(
            path.as_ptr() as *const c_char,
            O_CLOEXEC | O_EXCL | O_TMPFILE | O_RDWR,
            0o600,
        )
    } {
        -1 => create_unix(dir),
        fd => Ok(unsafe { FromRawFd::from_raw_fd(fd) }),
    }
}

#[cfg(not(target_os = "linux"))]
pub fn create(dir: &Path) -> io::Result<File> {
    create_unix(dir)
}

fn create_unix(dir: &Path) -> io::Result<File> {
    util::create_helper(
        dir,
        OsStr::new(".tmp"),
        OsStr::new(""),
        ::NUM_RAND_CHARS,
        |path| create_unlinked(&path),
    )
}

unsafe fn stat(fd: RawFd) -> io::Result<stat_t> {
    let mut meta: stat_t = ::std::mem::zeroed();
    cvt_err(fstat(fd, &mut meta))?;
    Ok(meta)
}

pub fn reopen(file: &File, path: &Path) -> io::Result<File> {
    let new_file = OpenOptions::new().read(true).write(true).open(path)?;
    unsafe {
        let old_meta = stat(file.as_raw_fd())?;
        let new_meta = stat(new_file.as_raw_fd())?;
        if old_meta.st_dev != new_meta.st_dev || old_meta.st_ino != new_meta.st_ino {
            return Err(io::Error::new(
                io::ErrorKind::NotFound,
                "original tempfile has been replaced",
            ));
        }
        Ok(new_file)
    }
}

#[cfg(not(target_os = "redox"))]
pub fn persist(old_path: &Path, new_path: &Path, overwrite: bool) -> io::Result<()> {
    unsafe {
        let old_path = cstr(old_path)?;
        let new_path = cstr(new_path)?;
        if overwrite {
            cvt_err(rename(
                old_path.as_ptr() as *const c_char,
                new_path.as_ptr() as *const c_char,
            ))?;
        } else {
            cvt_err(link(
                old_path.as_ptr() as *const c_char,
                new_path.as_ptr() as *const c_char,
            ))?;
            // Ignore unlink errors. Can we do better?
            // On recent linux, we can use renameat2 to do this atomically.
            let _ = unlink(old_path.as_ptr() as *const c_char);
        }
        Ok(())
    }
}

#[cfg(target_os = "redox")]
pub fn persist(old_path: &Path, new_path: &Path, overwrite: bool) -> io::Result<()> {
    // XXX implement when possible
    Err(io::Error::from_raw_os_error(syscall::ENOSYS))
}
<|endoftext|>