<commit_before>#[cfg(feature = "trace_log")]
extern crate env_logger;

extern crate radeco_lib;
extern crate r2pipe;
extern crate r2api;
extern crate base64;

mod cli;

use radeco_lib::analysis::cse::cse::CSE;
use radeco_lib::analysis::interproc::fixcall::CallFixer;
use radeco_lib::analysis::sccp;
use radeco_lib::backend::lang_c::c_ast::CAST;
use radeco_lib::backend::lang_c::c_cfg::CCFGVerifier;
use radeco_lib::backend::lang_c::c_cfg_builder;
use radeco_lib::frontend::radeco_containers::ProjectLoader;
use radeco_lib::frontend::radeco_source::FileSource;
use radeco_lib::middle::{dce, dot};
use radeco_lib::middle::ir_reader::parse_il;
use radeco_lib::middle::ir_writer;
use radeco_lib::middle::ssa::verifier;
use std::env;
use std::fs::{self, File};
use std::io::Write;
use std::path::PathBuf;
use std::process;
use std::rc::Rc;
use std::str;

const USAGE: &'static str = "
Usage:
  radeco [-f <names>...] [--filesource] <target>
  radeco \
                             (-h | --help)

Options:
    -h, --help          Show this screen.
    \
                             -f, --functions     Analayze only some functions.
    --filesource        \
                             Use json files as input instead of an executable.
";

fn main() {
    #[cfg(feature = "trace_log")] env_logger::init();

    let (requested_functions, is_filesource) = cli::init_for_args(USAGE);

    let proj_name = env::args().nth(env::args().len() - 1).unwrap();
    let mut rproj = {
        if is_filesource {
            let source = FileSource::open(&proj_name);
            ProjectLoader::new().source(Rc::new(source)).load()
        } else {
            ProjectLoader::new().path(&proj_name).load()
        }
    };
    let regfile = rproj.regfile().clone();
    for mut xy in rproj.iter_mut() {
        let rmod = &mut xy.module;
        let func_name_map = rmod.functions
            .iter()
            .map(|(&addr, f)| (addr, f.name.to_string()))
            .collect();
        let strings = rmod.strings()
            .iter()
            .filter(|ref s| s.vaddr.is_some() && s.string.is_some())
            .cloned()
            .map(|s| {
                let (addr, _s) = (s.vaddr.unwrap(), s.string.unwrap());
                let bytes = base64::decode(&_s).unwrap_or(Vec::new());
                let ret_string = match str::from_utf8(bytes.as_slice()) {
                    Ok(v) => v.to_string(),
                    Err(e) => _s,
                };
                (addr, ret_string)
            })
            .collect();
        let mut dir = PathBuf::from(".");
        dir.push(format!("{}_out", proj_name));
        fs::create_dir_all(&dir).expect("Failed to create directory");

        // Reduce the complexity of rmod.functions to just a vec of (u64,&String)
        // for easier extraction and matching

        // Analyze preserved for all functions.
        {
            println!("[*] Fixing Callee Information");
            let bp_name = regfile.get_name_by_alias(&"BP".to_string());
            let bp_name = bp_name.map(|s| s.to_owned());
            let sp_name = regfile.get_name_by_alias(&"SP".to_string());
            let sp_name = sp_name.map(|s| s.to_owned());
            let mut callfixer = CallFixer::new(rmod, bp_name, sp_name);
            callfixer.rounded_analysis();
        }

        // Fix call sites
        radeco_lib::analysis::functions::fix_ssa_opcalls::go(rmod);

        // Infer calling conventions
        radeco_lib::analysis::functions::infer_regusage::run(rmod, &*regfile);

        // Filter the data if the user provided some args to be matched upon
        let matched_func_addrs = if requested_functions.len() != 0 {

            //XXX Impl in more efficient way
            let mut matched_func_vec: Vec<(u64, &str)> = rmod.iter()
                .map(|_f| {
                    let f = _f.function.1;
                    (f.offset.clone(), &*f.name)
                })
                .collect();

            let all_func_names: Vec<(&str)> =
                matched_func_vec.iter().map(|&(_, name)| name).collect();
            matched_func_vec = filter_with(
                &matched_func_vec,
                &requested_functions
                    .iter()
                    .map(|s| &s[..])
                    .collect::<Vec<_>>(),
            );
            cli::print_match_summary(
                &matched_func_vec,
                &requested_functions
                    .iter()
                    .map(|s| &s[..])
                    .collect::<Vec<_>>(),
                &all_func_names,
            );
            matched_func_vec
                .into_iter()
                .map(|(addr, _)| addr)
                .collect::<Vec<_>>()
        } else {
            Vec::new()
        };

        // Main file to contain IRs of all rfns
        let mut ffm = {
            let mut fname = PathBuf::from(&dir);
            fname.push("main");
            File::create(&fname).expect("Unable to create file")
        };

        for addr in matched_func_addrs {

            let ref mut rfn = rmod.functions.get_mut(&addr).unwrap();

            println!("[+] Analyzing: {} @ {:#x}", rfn.name, addr);
            {
                println!("  [*] Eliminating Dead Code");
                dce::collect(rfn.ssa_mut());
            }
            let mut ssa = {
                // Constant Propagation (sccp)
                println!("  [*] Propagating Constants");
                let mut analyzer = sccp::Analyzer::new(rfn.ssa_mut());
                analyzer.analyze();
                analyzer.emit_ssa()
            };
            {
                println!("  [*] Eliminating More DeadCode");
                dce::collect(&mut ssa);
            }
            *rfn.ssa_mut() = ssa;
            {
                // Common SubExpression Elimination (cse)
                println!("  [*] Eliminating Common SubExpressions");
                let mut cse = CSE::new(rfn.ssa_mut());
                cse.run();
            }
            {
                // Verify SSA
                println!("  [*] Verifying SSA's Validity");
                match verifier::verify(rfn.ssa()) {
                    Err(e) => {
                        println!("  [*] Found Error: {}", e);
                        process::exit(255);
                    }
                    Ok(_) => {}
                }
            }
            {
                // TODO
                // // Building memory SSA.
                // let _memory_ssa = {
                //     // Generate MemorySSA
                //     println!("  [*] Generating Memory SSA");
                //     let mut mssa = MemorySSA::new(rfn.ssa());
                //     mssa.gather_variables(rfn.datarefs(), &rfn.locals(),
                //                           &rfn.call_refs(&rmod.callgraph));
                //     mssa.run();
                //     mssa
                // };
            }
            //if false {
            //    if (!rfn.name.eq("sym.main")) & (!rfn.name.eq("main")) {
            //        continue;
            //    }
            //    println!("  [*] Analyzing Value Sets");
            //    let fn_analyzer = FnAnalyzer::from((*rfn).clone());
            //    let a_store_fn = fn_analyzer.analyze_rfn();
            //    for (a_loc, strided_interval) in a_store_fn.store {
            //        if let A_Loc{addr: AbstractAddress::Node{..}, ..} = a_loc {
            //            continue;
            //        };
            //        println!("{}", a_loc);
            //        println!("Strided Interval: {}", strided_interval);
            //    };
            //}

            //{
            //// Expreimental reference marking pass
            //println!("  [*] Unstable> Marking References");
            //let mut rmark = mark_refs::ReferenceMarker { };
            //rmark.resolve_refs(&mut rfn.ssa);
            //}

            let mut fname = PathBuf::from(&dir);
            fname.push(rfn.name.as_ref());

            let fn_ir_str = {
                ///////////////////////////
                // Write out the IR file
                //////////////////////////
                println!("  [*] Writing out IR");
                let mut res = String::new();
                ir_writer::emit_il(&mut res, Some(rfn.name.to_string()), rfn.ssa()).unwrap();
                let mut ff = File::create(&fname).expect("Unable to create file");
                write!(ff, "{}", res).expect("Error writing to file");
                writeln!(ffm, "{}", res).expect("Error writing to file");
                // Set as a comment in radare2
                match rmod.source.as_mut().unwrap().send(format!(
                    "CC, {} @ {}",
                    fname.to_str().unwrap(),
                    addr
                )) {
                    Ok(_) => {}
                    Err(e) => println!("{:?}", e),
                };
                res
            };

            {
                //////////////////////////////
                // Try parsing the IR file
                /////////////////////////////
                println!("  [*] Testing IR parser");
                let parsed = parse_il(&fn_ir_str, regfile.clone());
                let mut res = String::new();
                ir_writer::emit_il(&mut res, Some((*rfn.name).to_owned()), &parsed).unwrap();
                for (orig, roundtrip) in fn_ir_str.lines().zip(res.lines()) {
                    if orig != roundtrip {
                        println!("  FAILED TO ROUND-TRIP: \"{}\" => \"{}\"", orig, roundtrip);
                    }
                }
            }

            {
                ////////////////////////
                // Generate DOT file
                ///////////////////////
                println!("  [*] Generating dot");
                let mut df = File::create(format!("{}.dot", fname.to_string_lossy()))
                    .expect("Unable to create .dot file");
                let dot = dot::emit_dot(rfn.ssa());
                writeln!(df, "{}", dot).expect("Error writing to file");
            }

            let c_cfg = c_cfg_builder::recover_c_cfg(&rfn, &func_name_map, &strings);

            if let Err(err) = CCFGVerifier::verify(&c_cfg) {
                println!("CCFG verification failed");
                println!("{}", err);
            }

            {
                ////////////////////////
                // Generate pseudo-C CFG Dot file
                ///////////////////////
                println!("  [*] Generating psuedo code");
                let mut df = File::create(format!("{}.c.dot", fname.to_string_lossy()))
                    .expect("Unable to create .c file");
                let dot = c_cfg.dot_str();
                writeln!(df, "{}", dot).expect("Error writing to file");
            }

            {
                ////////////////////////
                // Generate pseudo-C code
                ///////////////////////
                println!("  [*] Generating psuedo code");
                let mut df = File::create(format!("{}.c", fname.to_string_lossy()))
                    .expect("Unable to create .c file");
                let code = c_cfg.to_c_ast().print();
                writeln!(df, "{}", code).expect("Error writing to file");
            }
        }

        match rmod.source.as_mut().unwrap().send(
            "e scr.color=true".to_string(),
        ) {
            Ok(()) => {}
            Err(e) => println!("{:?}", e),
        }
    }
}

// Filters the functions that were in Radeco output AND requested by user
fn filter_with<'a>(
    all_funcs: &Vec<(u64, &'a str)>,
    requested: &Vec<&'a str>,
) -> Vec<(u64, &'a str)> {

    all_funcs
        .iter()
        .filter(|&&(_, name)| {
            requested.iter().any(|user_req| &name == user_req)
        })
        .map(|&(addr, name)| (addr, name))
        .collect()
}
<commit_msg>Fix warnings<commit_after>#[cfg(feature = "trace_log")]
extern crate env_logger;

extern crate radeco_lib;
extern crate r2pipe;
extern crate r2api;
extern crate base64;

mod cli;

use radeco_lib::analysis::cse::cse::CSE;
use radeco_lib::analysis::interproc::fixcall::CallFixer;
use radeco_lib::analysis::sccp;
use radeco_lib::backend::lang_c::c_ast::CAST;
use radeco_lib::backend::lang_c::c_cfg::CCFGVerifier;
use radeco_lib::backend::lang_c::c_cfg_builder;
use radeco_lib::frontend::radeco_containers::ProjectLoader;
use radeco_lib::frontend::radeco_source::FileSource;
use radeco_lib::middle::{dce, dot};
use radeco_lib::middle::ir_reader::parse_il;
use radeco_lib::middle::ir_writer;
use radeco_lib::middle::ssa::verifier;
use radeco_lib::backend::lang_c::c_cfg_builder;
use radeco_lib::backend::lang_c::c_cfg::CCFGVerifier;

const USAGE: &'static str = "
Usage:
  radeco [-f <names>...] [--filesource] <target>
  radeco \
                             (-h | --help)

Options:
    -h, --help          Show this screen.
    \
                             -f, --functions     Analayze only some functions.
    --filesource        \
                             Use json files as input instead of an executable.
";

fn main() {
    #[cfg(feature = "trace_log")] env_logger::init();

    let (requested_functions, is_filesource) = cli::init_for_args(USAGE);

    let proj_name = env::args().nth(env::args().len() - 1).unwrap();
    let mut rproj = {
        if is_filesource {
            let source = FileSource::open(&proj_name);
            ProjectLoader::new().source(Rc::new(source)).load()
        } else {
            ProjectLoader::new().path(&proj_name).load()
        }
    };
    let regfile = rproj.regfile().clone();
    for mut xy in rproj.iter_mut() {
        let rmod = &mut xy.module;
        let func_name_map = rmod.functions
            .iter()
            .map(|(&addr, f)| (addr, f.name.to_string()))
            .collect();
        let strings = rmod.strings()
            .iter()
            .filter(|ref s| s.vaddr.is_some() && s.string.is_some())
            .cloned()
            .map(|s| {
                let (addr, _s) = (s.vaddr.unwrap(), s.string.unwrap());
                let bytes = base64::decode(&_s).unwrap_or(Vec::new());
                let ret_string = match str::from_utf8(bytes.as_slice()) {
                    Ok(v) => v.to_string(),
                    Err(_e) => _s,
                };
                (addr, ret_string)
            })
            .collect();
        let mut dir = PathBuf::from(".");
        dir.push(format!("{}_out", proj_name));
        fs::create_dir_all(&dir).expect("Failed to create directory");

        // Reduce the complexity of rmod.functions to just a vec of (u64,&String)
        // for easier extraction and matching

        // Analyze preserved for all functions.
        {
            println!("[*] Fixing Callee Information");
            let bp_name = regfile.get_name_by_alias(&"BP".to_string());
            let bp_name = bp_name.map(|s| s.to_owned());
            let sp_name = regfile.get_name_by_alias(&"SP".to_string());
            let sp_name = sp_name.map(|s| s.to_owned());
            let mut callfixer = CallFixer::new(rmod, bp_name, sp_name);
            callfixer.rounded_analysis();
        }

        // Fix call sites
        radeco_lib::analysis::functions::fix_ssa_opcalls::go(rmod);

        // Infer calling conventions
        radeco_lib::analysis::functions::infer_regusage::run(rmod, &*regfile);

        // Filter the data if the user provided some args to be matched upon
        let matched_func_addrs = if requested_functions.len() != 0 {

            //XXX Impl in more efficient way
            let mut matched_func_vec: Vec<(u64, &str)> = rmod.iter()
                .map(|_f| {
                    let f = _f.function.1;
                    (f.offset.clone(), &*f.name)
                })
                .collect();

            let all_func_names: Vec<(&str)> =
                matched_func_vec.iter().map(|&(_, name)| name).collect();
            matched_func_vec = filter_with(
                &matched_func_vec,
                &requested_functions
                    .iter()
                    .map(|s| &s[..])
                    .collect::<Vec<_>>(),
            );
            cli::print_match_summary(
                &matched_func_vec,
                &requested_functions
                    .iter()
                    .map(|s| &s[..])
                    .collect::<Vec<_>>(),
                &all_func_names,
            );
            matched_func_vec
                .into_iter()
                .map(|(addr, _)| addr)
                .collect::<Vec<_>>()
        } else {
            Vec::new()
        };

        // Main file to contain IRs of all rfns
        let mut ffm = {
            let mut fname = PathBuf::from(&dir);
            fname.push("main");
            File::create(&fname).expect("Unable to create file")
        };

        for addr in matched_func_addrs {

            let ref mut rfn = rmod.functions.get_mut(&addr).unwrap();

            println!("[+] Analyzing: {} @ {:#x}", rfn.name, addr);
            {
                println!("  [*] Eliminating Dead Code");
                dce::collect(rfn.ssa_mut());
            }
            let mut ssa = {
                // Constant Propagation (sccp)
                println!("  [*] Propagating Constants");
                let mut analyzer = sccp::Analyzer::new(rfn.ssa_mut());
                analyzer.analyze();
                analyzer.emit_ssa()
            };
            {
                println!("  [*] Eliminating More DeadCode");
                dce::collect(&mut ssa);
            }
            *rfn.ssa_mut() = ssa;
            {
                // Common SubExpression Elimination (cse)
                println!("  [*] Eliminating Common SubExpressions");
                let mut cse = CSE::new(rfn.ssa_mut());
                cse.run();
            }
            {
                // Verify SSA
                println!("  [*] Verifying SSA's Validity");
                match verifier::verify(rfn.ssa()) {
                    Err(e) => {
                        println!("  [*] Found Error: {}", e);
                        process::exit(255);
                    }
                    Ok(_) => {}
                }
            }
            {
                // TODO
                // // Building memory SSA.
                // let _memory_ssa = {
                //     // Generate MemorySSA
                //     println!("  [*] Generating Memory SSA");
                //     let mut mssa = MemorySSA::new(rfn.ssa());
                //     mssa.gather_variables(rfn.datarefs(), &rfn.locals(),
                //                           &rfn.call_refs(&rmod.callgraph));
                //     mssa.run();
                //     mssa
                // };
            }
            //if false {
            //    if (!rfn.name.eq("sym.main")) & (!rfn.name.eq("main")) {
            //        continue;
            //    }
            //    println!("  [*] Analyzing Value Sets");
            //    let fn_analyzer = FnAnalyzer::from((*rfn).clone());
            //    let a_store_fn = fn_analyzer.analyze_rfn();
            //    for (a_loc, strided_interval) in a_store_fn.store {
            //        if let A_Loc{addr: AbstractAddress::Node{..}, ..} = a_loc {
            //            continue;
            //        };
            //        println!("{}", a_loc);
            //        println!("Strided Interval: {}", strided_interval);
            //    };
            //}

            //{
            //// Expreimental reference marking pass
            //println!("  [*] Unstable> Marking References");
            //let mut rmark = mark_refs::ReferenceMarker { };
            //rmark.resolve_refs(&mut rfn.ssa);
            //}

            let mut fname = PathBuf::from(&dir);
            fname.push(rfn.name.as_ref());

            let fn_ir_str = {
                ///////////////////////////
                // Write out the IR file
                //////////////////////////
                println!("  [*] Writing out IR");
                let mut res = String::new();
                ir_writer::emit_il(&mut res, Some(rfn.name.to_string()), rfn.ssa()).unwrap();
                let mut ff = File::create(&fname).expect("Unable to create file");
                write!(ff, "{}", res).expect("Error writing to file");
                writeln!(ffm, "{}", res).expect("Error writing to file");
                // Set as a comment in radare2
                match rmod.source.as_mut().unwrap().send(format!(
                    "CC, {} @ {}",
                    fname.to_str().unwrap(),
                    addr
                )) {
                    Ok(_) => {}
                    Err(e) => println!("{:?}", e),
                };
                res
            };

            {
                //////////////////////////////
                // Try parsing the IR file
                /////////////////////////////
                println!("  [*] Testing IR parser");
                let parsed = parse_il(&fn_ir_str, regfile.clone());
                let mut res = String::new();
                ir_writer::emit_il(&mut res, Some((*rfn.name).to_owned()), &parsed).unwrap();
                for (orig, roundtrip) in fn_ir_str.lines().zip(res.lines()) {
                    if orig != roundtrip {
                        println!("  FAILED TO ROUND-TRIP: \"{}\" => \"{}\"", orig, roundtrip);
                    }
                }
            }

            {
                ////////////////////////
                // Generate DOT file
                ///////////////////////
                println!("  [*] Generating dot");
                let mut df = File::create(format!("{}.dot", fname.to_string_lossy()))
                    .expect("Unable to create .dot file");
                let dot = dot::emit_dot(rfn.ssa());
                writeln!(df, "{}", dot).expect("Error writing to file");
            }

            let c_cfg = c_cfg_builder::recover_c_cfg(&rfn, &func_name_map, &strings);

            if let Err(err) = CCFGVerifier::verify(&c_cfg) {
                println!("CCFG verification failed");
                println!("{}", err);
            }

            {
                ////////////////////////
                // Generate pseudo-C CFG Dot file
                ///////////////////////
                println!("  [*] Generating psuedo code");
                let mut df = File::create(format!("{}.c.dot", fname.to_string_lossy()))
                    .expect("Unable to create .c file");
                let dot = c_cfg.dot_str();
                writeln!(df, "{}", dot).expect("Error writing to file");
            }

            {
                ////////////////////////
                // Generate pseudo-C code
                ///////////////////////
                println!("  [*] Generating psuedo code");
                let mut df = File::create(format!("{}.c", fname.to_string_lossy()))
                    .expect("Unable to create .c file");
                let code = c_cfg.to_c_ast().print();
                writeln!(df, "{}", code).expect("Error writing to file");
            }
        }

        match rmod.source.as_mut().unwrap().send(
            "e scr.color=true".to_string(),
        ) {
            Ok(()) => {}
            Err(e) => println!("{:?}", e),
        }
    }
}

// Filters the functions that were in Radeco output AND requested by user
fn filter_with<'a>(
    all_funcs: &Vec<(u64, &'a str)>,
    requested: &Vec<&'a str>,
) -> Vec<(u64, &'a str)> {

    all_funcs
        .iter()
        .filter(|&&(_, name)| {
            requested.iter().any(|user_req| &name == user_req)
        })
        .map(|&(addr, name)| (addr, name))
        .collect()
}
<|endoftext|>


<commit_before>// Copyright (c) 2017, Mikhail Vorotilov
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this
//   list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
//   this list of conditions and the following disclaimer in the documentation
//   and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

use super::super::FloatType;
use super::SearchError;
use super::Convergency;
use super::super::find_roots_linear;
use super::super::find_roots_quadratic;
use super::super::find_roots_cubic;
use super::super::find_roots_quartic;
use super::super::find_root_newton_raphson;
use std::fmt::Debug;

trait Polynom<F> {
    fn value(self, x: F) -> F;
    fn derivative_polynom(self) -> Vec<F>;
}

impl<'a, F> Polynom<F> for &'a [F]
    where F: FloatType
{
    fn value(self, x: F) -> F {
        let mut result = F::zero();
        let mut xn = F::one();

        // Sum starting with a0
        for a in self.iter().rev() {
            result = result + *a * xn;
            xn = xn * x;
        }

        // The highest coefficient of the normalized polynom is 1
        result + xn
    }

    fn derivative_polynom(self) -> Vec<F> {
        let mut result = Vec::from(self);
        result.truncate(self.len() - 1);
        let n: F = F::from(self.len() as i16);
        let mut ni = F::one();

        for x in result.iter_mut().rev() {
            *x = (*x * ni) / n;
            ni = ni + F::one();
        }

        result
    }
}

enum RootInterval<F>
    where F: FloatType
{
    First { x: F, y: F },
    Middle { x1: F, y1: F, x2: F, y2: F },
    Last { x: F, y: F },
}

/// Find all roots of the normalized polynom
/// 1*x^n + a[n-1]*x^(n-1) + a[n-2]*x^(n-2) + ... + a[0] = 0.
///
/// # Failures
/// ## ZeroDerivative
/// Two consecutive points have the same value. Algorithm cannot continue.
/// ## NoConvergency
/// Algorithm cannot find a root within the given number of iterations.
/// # Examples
///
/// ```
/// use roots::find_roots_sturm;
///
/// let polynom = &[1f64,1f64,1f64,1f64,1f64,1f64];
///
/// let roots = find_roots_sturm(polynom, &mut 1e-6);
/// // Returns vector of roots;
/// ```
pub fn find_roots_sturm<F>(a: &[F], convergency: &mut Convergency<F>) -> Result<Vec<F>, SearchError>
    where F: FloatType
{
    match a.len() {
        0 => Err(SearchError::ZeroDerivative),
        1 => Ok(find_roots_linear(F::one(), a[0]).as_ref().to_vec()),
        2 => Ok(find_roots_quadratic(F::one(), a[0], a[1]).as_ref().to_vec()),
        3 => Ok(find_roots_cubic(F::one(), a[0], a[1], a[2]).as_ref().to_vec()),
        4 => Ok(find_roots_quartic(F::one(), a[0], a[1], a[2], a[3]).as_ref().to_vec()),
        _ => {
            let derivative = a.derivative_polynom();
            match find_roots_sturm(&derivative, convergency) {
                Ok(derivative_roots) => {
                    let mut result = Vec::new();
                    let mut intervals = Vec::new();
                    let mut negative = a.len() % 2 == 0; // Odd polynoms start negative
                    let mut last_x = F::zero();
                    let mut last_y = F::zero();
                    for derivative_root in derivative_roots.iter() {
                        let extremum = a.value(*derivative_root);
                        if (extremum > F::zero()) == negative {
                            let interval = if intervals.len() == 0 {
                                RootInterval::First {
                                    x: *derivative_root,
                                    y: extremum,
                                }
                            } else {
                                RootInterval::Middle {
                                    x1: last_x,
                                    y1: last_y,
                                    x2: *derivative_root,
                                    y2: extremum,
                                }
                            };
                            intervals.push(interval);
                            last_x = *derivative_root;
                            last_y = extremum;
                            negative = !negative;
                        }
                    }
                    if intervals.len() > 0 && last_y < F::zero() {
                        intervals.push(RootInterval::Last {
                            x: last_x,
                            y: last_y,
                        });
                    }

                    for interval in intervals.iter() {
                        match interval {
                            &RootInterval::First { x, y } => {
                                match find_root_newton_raphson(x - y.abs(),
                                                               |xx| a.value(xx),
                                                               |xx| derivative.value(xx),
                                                               convergency) {
                                    Ok(xxx) => {
                                        if xxx <= x {
                                            result.push(xxx)
                                        }
                                    }
                                    _ => {}
                                }
                            }
                            &RootInterval::Middle { x1, y1, x2, y2 } => {
                                match find_root_newton_raphson(x2 - x2 * (y2 - y1) / (x2 - x1),
                                                               |xx| a.value(xx),
                                                               |xx| derivative.value(xx),
                                                               convergency) {
                                    Ok(xxx) => {
                                        if (xxx >= x1) && (xxx <= x2) {
                                            result.push(xxx)
                                        }
                                    }
                                    _ => {}
                                }
                            }
                            &RootInterval::Last { x, y } => {
                                match find_root_newton_raphson(x + y.abs(),
                                                               |xx| a.value(xx),
                                                               |xx| derivative.value(xx),
                                                               convergency) {
                                    Ok(xxx) => {
                                        if xxx >= x {
                                            result.push(xxx)
                                        }
                                    }
                                    _ => {}
                                }
                            }
                        }
                    }
                    Ok(result)
                }
                Err(err) => Err(err),
            }
        }
    }

}

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

    #[test]
    fn test_find_roots_sturm() {
        let polynom = &[-2f64, 1f64];
        let roots = find_roots_sturm(polynom, &mut 1e-6f64).ok().unwrap();
        assert_float_array_eq!(1e-15, roots, [1f64]);
    }

    #[test]
    fn test_polynom_value() {
        let polynom = [1f64, -2f64, 1f64];
        assert_eq!(1f64, polynom.value(0f64));
        assert_eq!(1f64, polynom.value(1f64));
        assert_eq!(3f64, polynom.value(-1f64));
    }

    #[test]
    fn test_derivative_polynom() {
        let polynom = [1f64, -2f64, 1f64];
        let derivative = polynom.derivative_polynom();
        assert_float_array_eq!(1e-15, derivative, [2f64 / 3f64, -2f64 / 3f64]);
    }
}
<commit_msg>Add untested code to find all roots of polynomials.<commit_after>// Copyright (c) 2017, Mikhail Vorotilov
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this
//   list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
//   this list of conditions and the following disclaimer in the documentation
//   and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

use super::super::find_roots_cubic;
use super::super::find_roots_linear;
use super::super::find_roots_quadratic;
use super::super::find_roots_quartic;
use super::super::FloatType;
use super::Convergency;
use super::Interval;
use super::Sample;
use super::SearchError;
use std::fmt::Debug;

#[derive(Debug, PartialEq)]
struct ValueAndDerivative<F>
where
    F: FloatType,
{
    value: Sample<F>,
    derivative: F,
}

trait Polynom<F>
where
    F: FloatType,
{
    fn value(&self, x: &F) -> F;
    fn value_and_derivative(&self, x: &F) -> ValueAndDerivative<F>;
    fn find_root(&self, bracketed_start: &mut Interval<F>, convergency: &mut Convergency<F>) -> Result<F, SearchError>;
    fn derivative_polynom(&self) -> Vec<F>;
}

impl<F> Polynom<F> for [F]
where
    F: FloatType,
{
    fn value(&self, x: &F) -> F {
        let mut result = F::zero();
        let mut xn = F::one();

        // Sum starting with a0
        for a in self.iter().rev() {
            result = result + *a * xn;
            xn = xn * *x;
        }

        // The highest coefficient of the normalized polynom is 1
        result + xn
    }

    fn value_and_derivative(&self, x: &F) -> ValueAndDerivative<F> {
        let mut xn = F::one(); // x^n for SUM(A(n)*x^(n))
        let mut value = F::zero();

        let mut xn1 = F::zero(); // x^n-1 for SUM(n*A(n-1)*x^(n-1))
        let mut derivative = F::zero();
        let mut n = F::zero();

        // Sum starting with a0
        for a in self.iter().rev() {
            value = value + *a * xn;
            derivative = derivative + *a * n * xn1;
            xn1 = xn;
            xn = xn * *x;
            n = n + F::one();
        }

        // The highest coefficient of the normalized polynom is 1
        ValueAndDerivative {
            value: Sample { x: *x, y: value + xn },
            derivative: derivative + n * xn1,
        }
    }

    fn find_root(&self, bracketed_start: &mut Interval<F>, convergency: &mut Convergency<F>) -> Result<F, SearchError> {
        if bracketed_start.is_bracketed() {
            let interval = bracketed_start;
            let mut iter = 0;
            loop {
                if convergency.is_root_found(interval.begin.y) {
                    break Ok(interval.begin.x);
                } else if convergency.is_root_found(interval.end.y) {
                    break Ok(interval.end.x);
                } else if interval.is_converged(convergency) {
                    break Ok(interval.middle());
                } else {
                    let middle = self.value_and_derivative(&interval.middle());
                    let next_sample = if middle.derivative != F::zero() {
                        let newton_raphson = middle.value.x - middle.value.y / middle.derivative;
                        if newton_raphson >= interval.begin.x && newton_raphson <= interval.end.x {
                            let newton_raphson_value = self.value(&newton_raphson);
                            if newton_raphson_value.abs() < middle.value.y.abs() {
                                Sample {
                                    x: newton_raphson,
                                    y: newton_raphson_value,
                                }
                            } else {
                                middle.value
                            }
                        } else {
                            middle.value
                        }
                    } else {
                        middle.value
                    };
                    if interval.begin.is_bracketed_with(&next_sample) {
                        interval.end = Sample {
                            x: next_sample.x,
                            y: next_sample.y,
                        };
                    } else {
                        interval.begin = Sample {
                            x: next_sample.x,
                            y: next_sample.y,
                        };
                    }
                }
                iter = iter + 1;
                if convergency.is_iteration_limit_reached(iter) {
                    break Err(SearchError::NoConvergency);
                }
            }
        } else {
            Err(SearchError::NoBracketing)
        }
    }

    fn derivative_polynom(&self) -> Vec<F> {
        let mut result = Vec::from(self);
        result.truncate(self.len() - 1);
        let n: F = F::from(self.len() as i16);
        let mut ni = F::one();

        for x in result.iter_mut().rev() {
            *x = (*x * ni) / n;
            ni = ni + F::one();
        }

        result
    }
}

/// Interval for searching roots
enum SearchInterval<F>
where
    F: FloatType,
{
    /// [-infinity .. +infinity]
    Whole,
    /// [-infinity .. x]
    First(Sample<F>),
    /// [x .. +infinity ]
    Last(Sample<F>),
    /// [x1 .. x2 ]
    Middle(Interval<F>),
}

enum BracketingDirection {
    TowardsPositive,
    TowardsNegative,
}

fn initial_bracket<F>(
    initial_sample: &Sample<F>,
    direction: &BracketingDirection,
    polynom: &[F],
    derivative_polynom: &[F],
    convergency: &mut Convergency<F>,
) -> Result<Interval<F>, SearchError>
where
    F: FloatType,
{
    let mut iter = 0;
    let towards_positive = match direction {
        &BracketingDirection::TowardsPositive => true,
        &BracketingDirection::TowardsNegative => false,
    };
    let mut step = if towards_positive { F::one() } else { -F::one() };
    let initial_copy = Sample {
        x: initial_sample.x,
        y: initial_sample.y,
    };
    let mut next_x = initial_sample.x + step;
    let result = loop {
        let mut next_y = polynom.value(&next_x);
        let mut next_sample = Sample { x: next_x, y: next_y };
        if next_sample.is_bracketed_with(&initial_sample) {
            break Ok(if towards_positive {
                Interval {
                    begin: initial_copy,
                    end: next_sample,
                }
            } else {
                Interval {
                    begin: next_sample,
                    end: initial_copy,
                }
            });
        } else {
            let derivative = derivative_polynom.value(&next_x);
            if derivative > F::zero() {
                next_x = next_x - next_y / derivative;
                next_y = polynom.value(&next_x);
                next_sample = Sample { x: next_x, y: next_y };
                if next_sample.is_bracketed_with(&initial_sample) {
                    break Ok(if towards_positive {
                        Interval {
                            begin: initial_copy,
                            end: next_sample,
                        }
                    } else {
                        Interval {
                            begin: next_sample,
                            end: initial_copy,
                        }
                    });
                }
            };
            step = step * F::two();
            next_x = next_x + step;
            iter = iter + 1;
            if convergency.is_iteration_limit_reached(iter) {
                break Err(SearchError::NoConvergency);
            };
        }
    };
    result
}

fn narrow_down<F>(
    initial_interval: &SearchInterval<F>,
    polynom: &[F],
    derivative_polynom: &[F],
    convergency: &mut Convergency<F>,
) -> Result<Interval<F>, SearchError>
where
    F: FloatType,
{
    match initial_interval {
        &SearchInterval::Whole => {
            let zero_sample = Sample {
                x: F::zero(),
                y: polynom.value(&F::zero()),
            };
            let zero_interval = if zero_sample.y > F::zero() {
                SearchInterval::First(zero_sample)
            } else {
                SearchInterval::Last(zero_sample)
            };
            narrow_down(&zero_interval, polynom, derivative_polynom, convergency)
        }
        &SearchInterval::First(ref end) => initial_bracket(
            &end,
            &BracketingDirection::TowardsNegative,
            polynom,
            derivative_polynom,
            convergency,
        ),
        &SearchInterval::Last(ref begin) => initial_bracket(
            &begin,
            &BracketingDirection::TowardsPositive,
            polynom,
            derivative_polynom,
            convergency,
        ),
        &SearchInterval::Middle(ref interval) => {
            if interval.is_bracketed() {
                let middle_x = if interval.begin.y == interval.end.y {
                    (interval.begin.x + interval.end.x) / F::two()
                } else {
                    interval.begin.x
                        - interval.begin.y * (interval.end.x - interval.begin.x) / (interval.end.y - interval.begin.y)
                };
                let mut middle_sample = Sample {
                    x: middle_x,
                    y: polynom.value(&middle_x),
                };
                let derivative = derivative_polynom.value(&middle_x);
                if derivative != F::zero() {
                    let closer_x = middle_sample.x - middle_sample.y / derivative;
                    if closer_x >= interval.begin.x && closer_x <= interval.end.x {
                        middle_sample = Sample {
                            x: closer_x,
                            y: polynom.value(&closer_x),
                        };
                    }
                }
                if interval.begin.is_bracketed_with(&middle_sample) {
                    Ok(Interval {
                        begin: Sample {
                            x: interval.begin.x,
                            y: interval.begin.y,
                        },
                        end: middle_sample,
                    })
                } else {
                    Ok(Interval {
                        begin: middle_sample,
                        end: Sample {
                            x: interval.end.x,
                            y: interval.end.y,
                        },
                    })
                }
            } else {
                Err(SearchError::NoBracketing)
            }
        }
    }
}

fn find_root_intervals<F>(
    polynom: &[F],
    derivative_polynom: &[F],
    convergency: &mut Convergency<F>,
) -> Result<Vec<SearchInterval<F>>, SearchError>
where
    F: FloatType,
{
    let mut result = Vec::new();
    let derivative_roots = find_roots_sturm(&derivative_polynom, convergency);
    let symmetric_polynom = polynom.len() % 2 == 0;
    let mut expect_positive = !symmetric_polynom;
    let mut previous_interval: SearchInterval<F> = SearchInterval::Whole;
    // Iterate through all roots of the derivative polynom
    for derivative_root in derivative_roots.iter().filter_map(|s| match s {
        &Ok(ref x) => Some(x),
        &Err(_) => None,
    }) {
        let value = polynom.value(derivative_root);
        if (expect_positive && value >= F::zero()) || (!expect_positive && value < F::zero()) {
            // Transition found
            let interval_to_add = match &previous_interval {
                &SearchInterval::Whole => SearchInterval::First(Sample {
                    x: *derivative_root,
                    y: value,
                }),
                &SearchInterval::First(ref previous_end) => SearchInterval::Middle(Interval {
                    begin: Sample {
                        x: previous_end.x,
                        y: previous_end.y,
                    },
                    end: Sample {
                        x: *derivative_root,
                        y: value,
                    },
                }),
                _ => panic!("Unexpected type of the previous root interval!"),
            };
            result.push(interval_to_add);
            expect_positive = !expect_positive;
        }
        previous_interval = SearchInterval::First(Sample {
            x: *derivative_root,
            y: value,
        });
    }
    // All roots are checked, now the final step
    match previous_interval {
        SearchInterval::Whole => {
            if !symmetric_polynom {
                result.push(SearchInterval::Whole);
            }
            Ok(result)
        }
        SearchInterval::First(sample) => {
            if sample.x < F::zero() {
                result.push(SearchInterval::Last(sample));
            }
            Ok(result)
        }
        _ => Err(SearchError::NoBracketing),
    }
}

/// Find all roots of the normalized polynom
/// 1*x^n + a[n-1]*x^(n-1) + a[n-2]*x^(n-2) + ... + a[0] = 0.
///
/// # Failures
/// ## ZeroDerivative
/// Two consecutive points have the same value. Algorithm cannot continue.
/// ## NoConvergency
/// Algorithm cannot find a root within the given number of iterations.
/// # Examples
///
/// ```
/// use roots::find_roots_sturm;
///
/// let polynom = &[1f64,1f64,1f64,1f64,1f64,1f64];
///
/// let roots = find_roots_sturm(polynom, &mut 1e-6);
/// // Returns vector of roots;
/// ```
pub fn find_roots_sturm<F>(a: &[F], convergency: &mut Convergency<F>) -> Vec<Result<F, SearchError>>
where
    F: FloatType,
{
    match a.len() {
        0 => Vec::new(),
        1 => find_roots_linear(F::one(), a[0])
            .as_ref()
            .iter()
            .map(|s| Ok(*s))
            .collect(),
        2 => find_roots_quadratic(F::one(), a[0], a[1])
            .as_ref()
            .iter()
            .map(|s| Ok(*s))
            .collect(),
        3 => find_roots_cubic(F::one(), a[0], a[1], a[2])
            .as_ref()
            .iter()
            .map(|s| Ok(*s))
            .collect(),
        4 => find_roots_quartic(F::one(), a[0], a[1], a[2], a[3])
            .as_ref()
            .iter()
            .map(|s| Ok(*s))
            .collect(),
        _ => {
            let mut result = Vec::new();
            let derivative_polynom = a.derivative_polynom();
            match find_root_intervals(a, &derivative_polynom, convergency) {
                Ok(root_intervals) => {
                    for root_interval in &root_intervals {
                        if let Ok(mut narrowed) = narrow_down(&root_interval, a, &derivative_polynom, convergency) {
                            result.push(a.find_root(&mut narrowed, convergency));
                        }
                    }
                }
                Err(error) => {
                    result.push(Err(error));
                }
            }
            result
        }
    }
}

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

    #[test]
    fn test_find_roots_sturm() {
        let polynom = &[-2f64, 1f64];
        let roots = find_roots_sturm(polynom, &mut 1e-6f64);
        assert_eq!(roots, [Ok(1f64)]);
    }

    #[test]
    fn test_polynom_value() {
        let polynom = [1f64, -2f64, 1f64];
        assert_eq!(1f64, polynom.value(&0f64));
        assert_eq!(1f64, polynom.value(&1f64));
        assert_eq!(3f64, polynom.value(&-1f64));
    }

    #[test]
    fn test_polynom_value_and_derivative() {
        let polynom = [1f64, -2f64, 1f64];
        assert_eq!(
            ValueAndDerivative {
                value: Sample { x: 0f64, y: 1f64 },
                derivative: -2f64
            },
            polynom.value_and_derivative(&0f64)
        );
        assert_eq!(
            ValueAndDerivative {
                value: Sample { x: 1f64, y: 1f64 },
                derivative: 3f64
            },
            polynom.value_and_derivative(&1f64)
        );
        assert_eq!(
            ValueAndDerivative {
                value: Sample { x: -1f64, y: 3f64 },
                derivative: -1f64
            },
            polynom.value_and_derivative(&-1f64)
        );
    }

    #[test]
    fn test_derivative_polynom_3() {
        // x^3 + 1*x^2 - 2*x^1 + 1*x^0 => 3*x^2 + 2*x^1 - 2*x^0 => x^2 + (2/3)*x^1 - (2/3)*x^0
        let polynom = [1f64, -2f64, 1f64];
        let derivative = polynom.derivative_polynom();
        assert_float_array_eq!(1e-15, derivative, [2f64 / 3f64, -2f64 / 3f64]);
    }

    #[test]
    fn test_derivative_polynom_5() {
        // x^5 - 2*x^4 - 3*x^3 + 4*x^2 + 0*x^1 + 0*x^0 => 5*x^4 - 8*x^3 - 9*x^2 + 8*x^1 + 0*x^0 => x^4 - (8/5)*x^3 - (9/5)*x^2 + (8/5)*x^1 + 0*x^0
        let polynom = [-2f64, -3f64, 4f64, 0f64, 0f64];
        let derivative = polynom.derivative_polynom();
        assert_float_array_eq!(1e-15, derivative, [-8f64 / 5f64, -9f64 / 5f64, 8f64 / 5f64, 0f64]);
    }
}
<|endoftext|>


<commit_before>// Copyright (C) 2016 Élisabeth HENRY.
//
// This file is part of Crowbook.
//
// Crowbook is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation, either version 2.1 of the License, or
// (at your option) any later version.
//
// Caribon is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received ba copy of the GNU Lesser General Public License
// along with Crowbook.  If not, see <http://www.gnu.org/licenses/>.

use mustache;

use std::error;
use std::result;
use std::fmt;
use std::borrow::Cow;

#[derive(Debug, PartialEq, Clone)]
/// Source of an error file
pub struct Source {
    /// File name of the source
    #[doc(hidden)]
    pub file: Option<String>,

    /// Line number of the source
    #[doc(hidden)]
    pub line: Option<u32>,
}

impl Source {
    /// Create an empty source, with both fields set to None
    pub fn empty() -> Source {
        Source {
            file: None,
            line: None,
        }
    }

    /// Create a new source pointing to file
    pub fn new<S: Into<String>>(s: S) -> Source {
        Source {
            file: Some(s.into()),
            line: None,
        }
    }

    /// Sets line number of a source (with &mut self)
    pub fn set_line(&mut self, line: u32) {
        self.line = Some(line);
    }

    /// Unsets a line number of a source
    #[doc(hidden)]
    pub fn unset_line(&mut self) {
        self.line = None;
    }
}

impl fmt::Display for Source {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        if let Some(ref file) = self.file {
            try!(write!(f, "{}", file));
            if let Some(line) = self.line {
                try!(write!(f, ":{}", line));
            }
        } else {
            try!(write!(f, "<UNKNOWN FILE>"));
        }
        Ok(())
    }
}

impl<'a> From<&'a Source> for Source {
    fn from(s: &'a Source) -> Source {
        s.clone()
    }
}
#[derive(Debug, PartialEq)]
/// Crowbook Error type
pub struct Error {
    /// Origin (file, line) of the error, if there is one
    source: Source,
    inner: Inner,
}

impl Error {
    /// Creates a new default error
    pub fn default<S: Into<Cow<'static, str>>, O: Into<Source>>(source: O, msg: S) -> Error {
        Error {
            source: source.into(),
            inner: Inner::Default(msg.into()),
        }
    }

    /// Creates a new grammar check error
    ///
    /// Used when there is a problem connecting to languagetool
    pub fn grammar_check<S: Into<Cow<'static, str>>, O: Into<Source>>(source: O, msg: S) -> Error {
        Error {
            source: source.into(),
            inner: Inner::GrammarCheck(msg.into()),
        }
    }
    /// Creates a new parser error
    ///
    /// Error when parsing markdown file
    pub fn parser<S: Into<Cow<'static, str>>, O: Into<Source>>(source: O, msg: S) -> Error {
        Error {
            source: source.into(),
            inner: Inner::Parser(msg.into()),
        }
    }

    /// Creates a new config parser error
    ///
    /// Error when parsing book file
    pub fn config_parser<S: Into<Cow<'static, str>>, O: Into<Source>>(source: O, msg: S) -> Error {
        Error {
            source: source.into(),
            inner: Inner::ConfigParser(msg.into()),
        }
    }

    /// Creates a new file not found error
    ///
    /// * msg: description of why the file was needed
    /// * file: file name
    pub fn file_not_found<S1: Into<Cow<'static, str>>, S2: Into<Cow<'static, str>>, O: Into<Source>>
        (source: O,
         msg: S1,
         file: S2)
         -> Error {
        Error {
            source: source.into(),
            inner: Inner::FileNotFound(msg.into(), file.into()),
        }
    }

    /// Creates a new render error
    ///
    /// Error when rendering
    pub fn render<S: Into<Cow<'static, str>>, O: Into<Source>>(source: O, msg: S) -> Error {
        Error {
            source: source.into(),
            inner: Inner::Render(msg.into()),
        }
    }

    /// Creates a new template error
    ///
    /// Error when compiling a mustache template
    pub fn template<S: Into<Cow<'static, str>>, O: Into<Source>>(source: O, msg: S) -> Error {
        Error {
            source: source.into(),
            inner: Inner::Template(msg.into()),
        }
    }

    /// Creates a new invalid option error
    ///
    /// Error when trying to set an option
    pub fn invalid_option<S: Into<Cow<'static, str>>, O: Into<Source>>(source: O, msg: S) -> Error {
        Error {
            source: source.into(),
            inner: Inner::InvalidOption(msg.into()),
        }
    }

    /// Creates a new zipper error
    ///
    /// Error when moving/copying files to temporary dir, e.g. using `zip` commmand
    pub fn zipper<S: Into<Cow<'static, str>>>(msg: S) -> Error {
        Error {
            source: Source::empty(),
            inner: Inner::Zipper(msg.into()),
        }
    }

    /// Creates a new book option error
    ///
    /// Used when converting an error to invalid type
    pub fn book_option<S: Into<Cow<'static, str>>, O: Into<Source>>(source: O, msg: S) -> Error {
        Error {
            source: source.into(),
            inner: Inner::BookOption(msg.into()),
        }
    }

    /// Change the source of an error
    pub fn with_source<O: Into<Source>>(mut self, source: O) -> Error {
        self.source = source.into();
        self
    }

    /// Returns true if self is a default option error, false else
    pub fn is_default(&self) -> bool {
        match self.inner {
            Inner::Default(..) => true,
            _ => false,
        }
    }

    /// Returns true if self is a parser error, false else
    pub fn is_parser(&self) -> bool {
        match self.inner {
            Inner::Parser(..) => true,
            _ => false,
        }
    }

    /// Returns true if self is a config parser error, false else
    pub fn is_config_parser(&self) -> bool {
        match self.inner {
            Inner::ConfigParser(..) => true,
            _ => false,
        }
    }

    /// Returns true if self is a file not found error, false else
    pub fn is_file_not_found(&self) -> bool {
        match self.inner {
            Inner::FileNotFound(..) => true,
            _ => false,
        }
    }

    /// Returns true if self is a render error, false else
    pub fn is_render(&self) -> bool {
        match self.inner {
            Inner::Render(..) => true,
            _ => false,
        }
    }

    /// Returns true if self is a zipper error, false else
    pub fn is_zipper(&self) -> bool {
        match self.inner {
            Inner::Zipper(..) => true,
            _ => false,
        }
    }

    /// Returns true if self is a book option error, false else
    pub fn is_book_option(&self) -> bool {
        match self.inner {
            Inner::BookOption(..) => true,
            _ => false,
        }
    }

    /// Returns true if self is an invalid option error, false else
    pub fn is_invalid_option(&self) -> bool {
        match self.inner {
            Inner::InvalidOption(..) => true,
            _ => false,
        }
    }
}

impl error::Error for Error {
    fn description(&self) -> &str {
        match self.inner {
            Inner::Default(ref s) |
            Inner::Parser(ref s) |
            Inner::Zipper(ref s) |
            Inner::BookOption(ref s) |
            Inner::ConfigParser(ref s) |
            Inner::InvalidOption(ref s) |
            Inner::Render(ref s) |
            Inner::Template(ref s) |
            Inner::GrammarCheck(ref s) => s.as_ref(),

            Inner::FileNotFound(..) => "File not found",
        }
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let source = &self.source;
        if let Some(ref file) = source.file {
            try!(write!(f, "{}", file));
            if let Some(line) = source.line {
                try!(write!(f, ":{}", line));
            }
            try!(write!(f, ": "));
        }

        try!(match self.inner {
            Inner::Default(ref s) => write!(f, "{}", s),
            Inner::GrammarCheck(ref s) => {
                write!(f,
                       "{}",
                       lformat!("Error connecting to language tool server: {error}",
                                error = s))
            }
            Inner::Parser(ref s) => {
                write!(f,
                       "{}",
                       lformat!("Error parsing markdown: {error}", error = s))
            }
            Inner::ConfigParser(ref s) => {
                try!(f.write_str(&lformat!("Error parsing configuration file: ")));
                f.write_str(s)
            }
            Inner::FileNotFound(ref description, ref file) => {
                write!(f,
                       "{}",
                       lformat!("Could not find file '{file}' for {description}",
                                file = file,
                                description = description))
            }
            Inner::Template(ref s) => {
                write!(f,
                       "{}",
                       lformat!("Error compiling template: {template}", template = s))
            }
            Inner::Render(ref s) => {
                try!(f.write_str(&lformat!("Error during rendering: ")));
                f.write_str(s)
            }
            Inner::Zipper(ref s) => {
                try!(f.write_str(&lformat!("Error during temporary files editing: ")));
                f.write_str(s)
            }
            Inner::BookOption(ref s) => {
                try!(f.write_str(&lformat!("Error converting BookOption: ")));
                f.write_str(s)
            }
            Inner::InvalidOption(ref s) => {
                try!(f.write_str(&lformat!("Error accessing book option: ")));
                f.write_str(s)
            }
        });
        Ok(())
    }
}

/// Crowbook's Result type, used by many methods that can fail
pub type Result<T> = result::Result<T, Error>;

/// Implement our Error from mustache::Error
impl From<mustache::Error> for Error {
    fn from(err: mustache::Error) -> Error {
        Error::template(Source::empty(),
                       format!("{}", err))
    }
}

#[derive(Debug, PartialEq)]
enum Inner {
    /// Default variant
    Default(Cow<'static, str>),
    /// An error in Parsing markdown file
    Parser(Cow<'static, str>),
    /// An error in parsing a book configuration file
    ConfigParser(Cow<'static, str>),
    /// An error when a file is not found
    FileNotFound(Cow<'static, str>, Cow<'static, str>), // description, file
    /// An error in a renderer
    Render(Cow<'static, str>),
    /// An error during "zipping" processus
    Zipper(Cow<'static, str>),
    /// An error relative to BookOption convertion (usually a type error)
    BookOption(Cow<'static, str>),
    /// An invalid option
    InvalidOption(Cow<'static, str>),
    /// Error when compiling template
    Template(Cow<'static, str>),
    /// Error when connecting to LanguageTool
    GrammarCheck(Cow<'static, str>),
}
<commit_msg>Make Source's setting methods return &mut Self<commit_after>// Copyright (C) 2016 Élisabeth HENRY.
//
// This file is part of Crowbook.
//
// Crowbook is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation, either version 2.1 of the License, or
// (at your option) any later version.
//
// Caribon is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received ba copy of the GNU Lesser General Public License
// along with Crowbook.  If not, see <http://www.gnu.org/licenses/>.

use mustache;

use std::error;
use std::result;
use std::fmt;
use std::borrow::Cow;

#[derive(Debug, PartialEq, Clone)]
/// Source of an error file
pub struct Source {
    /// File name of the source
    #[doc(hidden)]
    pub file: Option<String>,

    /// Line number of the source
    #[doc(hidden)]
    pub line: Option<u32>,
}

impl Source {
    /// Create an empty source, with both fields set to None
    pub fn empty() -> Source {
        Source {
            file: None,
            line: None,
        }
    }

    /// Create a new source pointing to file
    pub fn new<S: Into<String>>(s: S) -> Source {
        Source {
            file: Some(s.into()),
            line: None,
        }
    }

    /// Sets line number of a source (with &mut self)
    pub fn set_line(&mut self, line: u32) -> &mut Self {
        self.line = Some(line);
        self
    }

    /// Unsets a line number of a source
    #[doc(hidden)]
    pub fn unset_line(&mut self) -> &mut Self {
        self.line = None;
        self
    }
}

impl fmt::Display for Source {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        if let Some(ref file) = self.file {
            try!(write!(f, "{}", file));
            if let Some(line) = self.line {
                try!(write!(f, ":{}", line));
            }
        } else {
            try!(write!(f, "<UNKNOWN FILE>"));
        }
        Ok(())
    }
}

impl<'a> From<&'a Source> for Source {
    fn from(s: &'a Source) -> Source {
        s.clone()
    }
}
#[derive(Debug, PartialEq)]
/// Crowbook Error type
pub struct Error {
    /// Origin (file, line) of the error, if there is one
    source: Source,
    inner: Inner,
}

impl Error {
    /// Creates a new default error
    pub fn default<S: Into<Cow<'static, str>>, O: Into<Source>>(source: O, msg: S) -> Error {
        Error {
            source: source.into(),
            inner: Inner::Default(msg.into()),
        }
    }

    /// Creates a new grammar check error
    ///
    /// Used when there is a problem connecting to languagetool
    pub fn grammar_check<S: Into<Cow<'static, str>>, O: Into<Source>>(source: O, msg: S) -> Error {
        Error {
            source: source.into(),
            inner: Inner::GrammarCheck(msg.into()),
        }
    }
    /// Creates a new parser error
    ///
    /// Error when parsing markdown file
    pub fn parser<S: Into<Cow<'static, str>>, O: Into<Source>>(source: O, msg: S) -> Error {
        Error {
            source: source.into(),
            inner: Inner::Parser(msg.into()),
        }
    }

    /// Creates a new config parser error
    ///
    /// Error when parsing book file
    pub fn config_parser<S: Into<Cow<'static, str>>, O: Into<Source>>(source: O, msg: S) -> Error {
        Error {
            source: source.into(),
            inner: Inner::ConfigParser(msg.into()),
        }
    }

    /// Creates a new file not found error
    ///
    /// * msg: description of why the file was needed
    /// * file: file name
    pub fn file_not_found<S1: Into<Cow<'static, str>>, S2: Into<Cow<'static, str>>, O: Into<Source>>
        (source: O,
         msg: S1,
         file: S2)
         -> Error {
        Error {
            source: source.into(),
            inner: Inner::FileNotFound(msg.into(), file.into()),
        }
    }

    /// Creates a new render error
    ///
    /// Error when rendering
    pub fn render<S: Into<Cow<'static, str>>, O: Into<Source>>(source: O, msg: S) -> Error {
        Error {
            source: source.into(),
            inner: Inner::Render(msg.into()),
        }
    }

    /// Creates a new template error
    ///
    /// Error when compiling a mustache template
    pub fn template<S: Into<Cow<'static, str>>, O: Into<Source>>(source: O, msg: S) -> Error {
        Error {
            source: source.into(),
            inner: Inner::Template(msg.into()),
        }
    }

    /// Creates a new invalid option error
    ///
    /// Error when trying to set an option
    pub fn invalid_option<S: Into<Cow<'static, str>>, O: Into<Source>>(source: O, msg: S) -> Error {
        Error {
            source: source.into(),
            inner: Inner::InvalidOption(msg.into()),
        }
    }

    /// Creates a new zipper error
    ///
    /// Error when moving/copying files to temporary dir, e.g. using `zip` commmand
    pub fn zipper<S: Into<Cow<'static, str>>>(msg: S) -> Error {
        Error {
            source: Source::empty(),
            inner: Inner::Zipper(msg.into()),
        }
    }

    /// Creates a new book option error
    ///
    /// Used when converting an error to invalid type
    pub fn book_option<S: Into<Cow<'static, str>>, O: Into<Source>>(source: O, msg: S) -> Error {
        Error {
            source: source.into(),
            inner: Inner::BookOption(msg.into()),
        }
    }

    /// Change the source of an error
    pub fn with_source<O: Into<Source>>(mut self, source: O) -> Error {
        self.source = source.into();
        self
    }

    /// Returns true if self is a default option error, false else
    pub fn is_default(&self) -> bool {
        match self.inner {
            Inner::Default(..) => true,
            _ => false,
        }
    }

    /// Returns true if self is a parser error, false else
    pub fn is_parser(&self) -> bool {
        match self.inner {
            Inner::Parser(..) => true,
            _ => false,
        }
    }

    /// Returns true if self is a config parser error, false else
    pub fn is_config_parser(&self) -> bool {
        match self.inner {
            Inner::ConfigParser(..) => true,
            _ => false,
        }
    }

    /// Returns true if self is a file not found error, false else
    pub fn is_file_not_found(&self) -> bool {
        match self.inner {
            Inner::FileNotFound(..) => true,
            _ => false,
        }
    }

    /// Returns true if self is a render error, false else
    pub fn is_render(&self) -> bool {
        match self.inner {
            Inner::Render(..) => true,
            _ => false,
        }
    }

    /// Returns true if self is a zipper error, false else
    pub fn is_zipper(&self) -> bool {
        match self.inner {
            Inner::Zipper(..) => true,
            _ => false,
        }
    }

    /// Returns true if self is a book option error, false else
    pub fn is_book_option(&self) -> bool {
        match self.inner {
            Inner::BookOption(..) => true,
            _ => false,
        }
    }

    /// Returns true if self is an invalid option error, false else
    pub fn is_invalid_option(&self) -> bool {
        match self.inner {
            Inner::InvalidOption(..) => true,
            _ => false,
        }
    }
}

impl error::Error for Error {
    fn description(&self) -> &str {
        match self.inner {
            Inner::Default(ref s) |
            Inner::Parser(ref s) |
            Inner::Zipper(ref s) |
            Inner::BookOption(ref s) |
            Inner::ConfigParser(ref s) |
            Inner::InvalidOption(ref s) |
            Inner::Render(ref s) |
            Inner::Template(ref s) |
            Inner::GrammarCheck(ref s) => s.as_ref(),

            Inner::FileNotFound(..) => "File not found",
        }
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let source = &self.source;
        if let Some(ref file) = source.file {
            try!(write!(f, "{}", file));
            if let Some(line) = source.line {
                try!(write!(f, ":{}", line));
            }
            try!(write!(f, ": "));
        }

        try!(match self.inner {
            Inner::Default(ref s) => write!(f, "{}", s),
            Inner::GrammarCheck(ref s) => {
                write!(f,
                       "{}",
                       lformat!("Error connecting to language tool server: {error}",
                                error = s))
            }
            Inner::Parser(ref s) => {
                write!(f,
                       "{}",
                       lformat!("Error parsing markdown: {error}", error = s))
            }
            Inner::ConfigParser(ref s) => {
                try!(f.write_str(&lformat!("Error parsing configuration file: ")));
                f.write_str(s)
            }
            Inner::FileNotFound(ref description, ref file) => {
                write!(f,
                       "{}",
                       lformat!("Could not find file '{file}' for {description}",
                                file = file,
                                description = description))
            }
            Inner::Template(ref s) => {
                write!(f,
                       "{}",
                       lformat!("Error compiling template: {template}", template = s))
            }
            Inner::Render(ref s) => {
                try!(f.write_str(&lformat!("Error during rendering: ")));
                f.write_str(s)
            }
            Inner::Zipper(ref s) => {
                try!(f.write_str(&lformat!("Error during temporary files editing: ")));
                f.write_str(s)
            }
            Inner::BookOption(ref s) => {
                try!(f.write_str(&lformat!("Error converting BookOption: ")));
                f.write_str(s)
            }
            Inner::InvalidOption(ref s) => {
                try!(f.write_str(&lformat!("Error accessing book option: ")));
                f.write_str(s)
            }
        });
        Ok(())
    }
}

/// Crowbook's Result type, used by many methods that can fail
pub type Result<T> = result::Result<T, Error>;

/// Implement our Error from mustache::Error
impl From<mustache::Error> for Error {
    fn from(err: mustache::Error) -> Error {
        Error::template(Source::empty(),
                       format!("{}", err))
    }
}

#[derive(Debug, PartialEq)]
enum Inner {
    /// Default variant
    Default(Cow<'static, str>),
    /// An error in Parsing markdown file
    Parser(Cow<'static, str>),
    /// An error in parsing a book configuration file
    ConfigParser(Cow<'static, str>),
    /// An error when a file is not found
    FileNotFound(Cow<'static, str>, Cow<'static, str>), // description, file
    /// An error in a renderer
    Render(Cow<'static, str>),
    /// An error during "zipping" processus
    Zipper(Cow<'static, str>),
    /// An error relative to BookOption convertion (usually a type error)
    BookOption(Cow<'static, str>),
    /// An invalid option
    InvalidOption(Cow<'static, str>),
    /// Error when compiling template
    Template(Cow<'static, str>),
    /// Error when connecting to LanguageTool
    GrammarCheck(Cow<'static, str>),
}
<|endoftext|>


<commit_before>// Copyright (C) 2016 Élisabeth HENRY.
//
// This file is part of Crowbook.
//
// Crowbook is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation, either version 2.1 of the License, or
// (at your option) any later version.
//
// Caribon is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received ba copy of the GNU Lesser General Public License
// along with Crowbook.  If not, see <http://www.gnu.org/licenses/>.

use book::{Book, Number};
use error::{Error,Result};
use token::Token;
use zipper::Zipper;
use escape::escape_tex;

use std::path::Path;
use std::iter::Iterator;

use mustache;

/// LaTeX renderer
pub struct LatexRenderer<'a> {
    book: &'a Book,
    current_chapter: Number,
}

impl<'a> LatexRenderer<'a> {
    /// Creates new LatexRenderer
    pub fn new(book: &'a Book) -> LatexRenderer<'a> {
        LatexRenderer {
            book: book,
            current_chapter: Number::Default,
        }
    }

    /// Render pdf in a file
    pub fn render_pdf(&mut self) -> Result<String> {
        if let Ok(pdf_file) = self.book.get_str("output.pdf") {
            let base_file = try!(Path::new(pdf_file).file_stem().ok_or(Error::Render("could not stem pdf filename")));
            let tex_file = format!("{}.tex", base_file.to_str().unwrap());
            let content = try!(self.render_book());
            let mut zipper = try!(Zipper::new(self.book.get_str("temp_dir").unwrap()));
            try!(zipper.write(&tex_file, &content.as_bytes(), false));
            zipper.generate_pdf(&self.book.get_str("tex.command").unwrap(), &tex_file, pdf_file)
        } else {
            Err(Error::Render("no output pdf file specified in book config"))
        }
    }

    /// Render latex in a string
    pub fn render_book(&mut self) -> Result<String> {
        let mut content = String::from("");

        // set tex numbering and toc display to book's parameters
        let numbering = self.book.get_i32("numbering").unwrap() - 1;
        content.push_str(&format!("\\setcounter{{tocdepth}}{{{}}}
\\setcounter{{secnumdepth}}{{{}}}\n",
                                  numbering, numbering));

        if self.book.get_bool("display_toc").unwrap() {
            content.push_str("\\tableofcontents\n");
        }

        for &(n, ref v) in &self.book.chapters {
            self.current_chapter = n;
            content.push_str(&self.render_vec(v, true));
        }

        let tex_lang = String::from(match self.book.get_str("lang").unwrap() {
            "en" => "english",
            "fr" => "francais",
            _ => {
                self.book.debug(&format!("Warning: can't find a tex equivalent for lang '{}', fallbacking on english", self.book.get_str("lang").unwrap()));
                "english"
            }
        });

        let template = mustache::compile_str(try!(self.book.get_template("tex.template")).as_ref());
        let data = self.book.get_mapbuilder("tex")
            .insert_str("content", content)
            .insert_str("tex_lang", tex_lang)
            .build();
        let mut res:Vec<u8> = vec!();
        template.render_data(&mut res, &data);
        match String::from_utf8(res) {
            Err(_) => Err(Error::Render("generated LaTeX was not valid utf-8")),
            Ok(res) => Ok(res)
        }
    }

    /// Transform a vector of `Token`s to LaTeX
    fn render_vec(&mut self, tokens: &[Token], escape: bool) -> String {
        let mut res = String::new();

        for token in tokens {
            res.push_str(&self.parse_token(&token, escape));
        }
        res
    }

    fn parse_token(&mut self, token: &Token, escape: bool) -> String {
        match *token {
            Token::Str(ref text) => if escape {escape_tex(text)} else {text.clone()},
            Token::Paragraph(ref vec) => format!("{}\n\n",
                                                 self.render_vec(vec, escape)),
            Token::Header(n, ref vec) => {
                let mut content = String::new();
                if n == 1 && self.current_chapter == Number::Hidden {
                    return String::new();
                }
                if n == 1 {
                    if let Number::Specified(n) = self.current_chapter {
                        content.push_str(r"\setcounter{chapter}{");
                        content.push_str(&format!("{}", n - 1));
                        content.push_str("}\n");
                    }
                }
                match n {
                    1 => content.push_str(r"\chapter"),
                    2 => content.push_str(r"\section"),
                    3 => content.push_str(r"\subsection"),
                    4 => content.push_str(r"\subsubsection"),
                    _ => content.push_str(r"\paragraph"),
                }
                if self.current_chapter == Number::Unnumbered {
                    content.push_str("*");
                }
                content.push_str(r"{");
                content.push_str(&self.render_vec(vec, true));
                content.push_str("}\n");
                content
            },
            Token::Emphasis(ref vec) => format!("\\emph{{{}}}", self.render_vec(vec, escape)),
            Token::Strong(ref vec) => format!("\\textbf{{{}}}", self.render_vec(vec, escape)),
            Token::Code(ref vec) => format!("\\texttt{{{}}}", self.render_vec(vec, escape)),
            Token::BlockQuote(ref vec) => format!("\\begin{{quotation}}\n{}\\end{{quotation}}\n", self.render_vec(vec, escape)),
            Token::CodeBlock(_, ref vec) => format!("\\begin{{verbatim}}\n{}\\end{{verbatim}}\n", self.render_vec(vec, false)),
            Token::Rule => String::from("\\HRule\n"),
            Token::SoftBreak => String::from(" "),
            Token::HardBreak => String::from("\n"),
            Token::List(ref vec) => format!("\\begin{{itemize}}\n{}\\end{{itemize}}", self.render_vec(vec, escape)),
            Token::OrderedList(_, ref vec) => format!("\\begin{{enumerate}}\n{}\\end{{enumerate}}\n", self.render_vec(vec, escape)),
            Token::Item(ref vec) => format!("\\item {}\n", self.render_vec(vec, escape)),
            Token::Link(ref url, _, ref vec) => {
                let content = self.render_vec(vec, escape);
                let url = escape_tex(url);
                println!("content: {}, url: {}", content, url);
                if content == url {
                    println!("content = url");
                    format!("\\url{{{}}}", content)
                } else {
                    if self.book.get_bool("tex.links_as_footnotes").unwrap() {
                        format!("\\href{{{}}}{{{}}}\\footnote{{\\url{{{}}}}}", url, content, url)
                    } else {
                        format!("\\href{{{}}}{{{}}}", url, content)
                    }
                }
            },
            Token::Image(_, _, _) => {
                self.book.debug("warning: including images is not yet supported for tex output");
                String::from(" ")
            },
            Token::Footnote(ref vec) => format!("\\footnote{{{}}}", self.render_vec(vec, escape)),
            Token::Table(n, ref vec) => {
                let mut cols = String::new();
                for _ in 0..n {
                    cols.push_str("|c");
                }
                cols.push_str("|");
                format!("\\begin{{center}}
\\begin{{tabular}}{{{}}}
\\hline
{}
\\hline
\\end{{tabular}}
\\end{{center}}\n\n", cols, self.render_vec(vec, escape))
            },
            Token::TableRow(ref vec) | Token::TableHead(ref vec) => {
                let mut res:String = vec.iter().map(|v| {self.parse_token(v, escape)})
                    .collect::<Vec<_>>()
                    .join(" & ");
                res.push_str("\\\\ \n");
                if let Token::TableHead(_) = *token {
                    res.push_str("\\hline\n");
                }
                res
            }
            Token::TableCell(ref vec) => self.render_vec(vec, escape),
        }
    }
}
<commit_msg>removed debugging println<commit_after>// Copyright (C) 2016 Élisabeth HENRY.
//
// This file is part of Crowbook.
//
// Crowbook is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation, either version 2.1 of the License, or
// (at your option) any later version.
//
// Caribon is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received ba copy of the GNU Lesser General Public License
// along with Crowbook.  If not, see <http://www.gnu.org/licenses/>.

use book::{Book, Number};
use error::{Error,Result};
use token::Token;
use zipper::Zipper;
use escape::escape_tex;

use std::path::Path;
use std::iter::Iterator;

use mustache;

/// LaTeX renderer
pub struct LatexRenderer<'a> {
    book: &'a Book,
    current_chapter: Number,
}

impl<'a> LatexRenderer<'a> {
    /// Creates new LatexRenderer
    pub fn new(book: &'a Book) -> LatexRenderer<'a> {
        LatexRenderer {
            book: book,
            current_chapter: Number::Default,
        }
    }

    /// Render pdf in a file
    pub fn render_pdf(&mut self) -> Result<String> {
        if let Ok(pdf_file) = self.book.get_str("output.pdf") {
            let base_file = try!(Path::new(pdf_file).file_stem().ok_or(Error::Render("could not stem pdf filename")));
            let tex_file = format!("{}.tex", base_file.to_str().unwrap());
            let content = try!(self.render_book());
            let mut zipper = try!(Zipper::new(self.book.get_str("temp_dir").unwrap()));
            try!(zipper.write(&tex_file, &content.as_bytes(), false));
            zipper.generate_pdf(&self.book.get_str("tex.command").unwrap(), &tex_file, pdf_file)
        } else {
            Err(Error::Render("no output pdf file specified in book config"))
        }
    }

    /// Render latex in a string
    pub fn render_book(&mut self) -> Result<String> {
        let mut content = String::from("");

        // set tex numbering and toc display to book's parameters
        let numbering = self.book.get_i32("numbering").unwrap() - 1;
        content.push_str(&format!("\\setcounter{{tocdepth}}{{{}}}
\\setcounter{{secnumdepth}}{{{}}}\n",
                                  numbering, numbering));

        if self.book.get_bool("display_toc").unwrap() {
            content.push_str("\\tableofcontents\n");
        }

        for &(n, ref v) in &self.book.chapters {
            self.current_chapter = n;
            content.push_str(&self.render_vec(v, true));
        }

        let tex_lang = String::from(match self.book.get_str("lang").unwrap() {
            "en" => "english",
            "fr" => "francais",
            _ => {
                self.book.debug(&format!("Warning: can't find a tex equivalent for lang '{}', fallbacking on english", self.book.get_str("lang").unwrap()));
                "english"
            }
        });

        let template = mustache::compile_str(try!(self.book.get_template("tex.template")).as_ref());
        let data = self.book.get_mapbuilder("tex")
            .insert_str("content", content)
            .insert_str("tex_lang", tex_lang)
            .build();
        let mut res:Vec<u8> = vec!();
        template.render_data(&mut res, &data);
        match String::from_utf8(res) {
            Err(_) => Err(Error::Render("generated LaTeX was not valid utf-8")),
            Ok(res) => Ok(res)
        }
    }

    /// Transform a vector of `Token`s to LaTeX
    fn render_vec(&mut self, tokens: &[Token], escape: bool) -> String {
        let mut res = String::new();

        for token in tokens {
            res.push_str(&self.parse_token(&token, escape));
        }
        res
    }

    fn parse_token(&mut self, token: &Token, escape: bool) -> String {
        match *token {
            Token::Str(ref text) => if escape {escape_tex(text)} else {text.clone()},
            Token::Paragraph(ref vec) => format!("{}\n\n",
                                                 self.render_vec(vec, escape)),
            Token::Header(n, ref vec) => {
                let mut content = String::new();
                if n == 1 && self.current_chapter == Number::Hidden {
                    return String::new();
                }
                if n == 1 {
                    if let Number::Specified(n) = self.current_chapter {
                        content.push_str(r"\setcounter{chapter}{");
                        content.push_str(&format!("{}", n - 1));
                        content.push_str("}\n");
                    }
                }
                match n {
                    1 => content.push_str(r"\chapter"),
                    2 => content.push_str(r"\section"),
                    3 => content.push_str(r"\subsection"),
                    4 => content.push_str(r"\subsubsection"),
                    _ => content.push_str(r"\paragraph"),
                }
                if self.current_chapter == Number::Unnumbered {
                    content.push_str("*");
                }
                content.push_str(r"{");
                content.push_str(&self.render_vec(vec, true));
                content.push_str("}\n");
                content
            },
            Token::Emphasis(ref vec) => format!("\\emph{{{}}}", self.render_vec(vec, escape)),
            Token::Strong(ref vec) => format!("\\textbf{{{}}}", self.render_vec(vec, escape)),
            Token::Code(ref vec) => format!("\\texttt{{{}}}", self.render_vec(vec, escape)),
            Token::BlockQuote(ref vec) => format!("\\begin{{quotation}}\n{}\\end{{quotation}}\n", self.render_vec(vec, escape)),
            Token::CodeBlock(_, ref vec) => format!("\\begin{{verbatim}}\n{}\\end{{verbatim}}\n", self.render_vec(vec, false)),
            Token::Rule => String::from("\\HRule\n"),
            Token::SoftBreak => String::from(" "),
            Token::HardBreak => String::from("\n"),
            Token::List(ref vec) => format!("\\begin{{itemize}}\n{}\\end{{itemize}}", self.render_vec(vec, escape)),
            Token::OrderedList(_, ref vec) => format!("\\begin{{enumerate}}\n{}\\end{{enumerate}}\n", self.render_vec(vec, escape)),
            Token::Item(ref vec) => format!("\\item {}\n", self.render_vec(vec, escape)),
            Token::Link(ref url, _, ref vec) => {
                let content = self.render_vec(vec, escape);
                let url = escape_tex(url);
                if content == url {
                    format!("\\url{{{}}}", content)
                } else {
                    if self.book.get_bool("tex.links_as_footnotes").unwrap() {
                        format!("\\href{{{}}}{{{}}}\\footnote{{\\url{{{}}}}}", url, content, url)
                    } else {
                        format!("\\href{{{}}}{{{}}}", url, content)
                    }
                }
            },
            Token::Image(_, _, _) => {
                self.book.debug("warning: including images is not yet supported for tex output");
                String::from(" ")
            },
            Token::Footnote(ref vec) => format!("\\footnote{{{}}}", self.render_vec(vec, escape)),
            Token::Table(n, ref vec) => {
                let mut cols = String::new();
                for _ in 0..n {
                    cols.push_str("|c");
                }
                cols.push_str("|");
                format!("\\begin{{center}}
\\begin{{tabular}}{{{}}}
\\hline
{}
\\hline
\\end{{tabular}}
\\end{{center}}\n\n", cols, self.render_vec(vec, escape))
            },
            Token::TableRow(ref vec) | Token::TableHead(ref vec) => {
                let mut res:String = vec.iter().map(|v| {self.parse_token(v, escape)})
                    .collect::<Vec<_>>()
                    .join(" & ");
                res.push_str("\\\\ \n");
                if let Token::TableHead(_) = *token {
                    res.push_str("\\hline\n");
                }
                res
            }
            Token::TableCell(ref vec) => self.render_vec(vec, escape),
        }
    }
}
<|endoftext|>


<commit_before>#![crate_id="gdal#0.1"]
#![crate_type="lib"]
#![feature(unsafe_destructor)]

extern crate sync;
extern crate libc;
#[cfg(test)] extern crate test;

use std::str::raw;
use libc::c_char;

pub mod raster;
pub mod vector;
pub mod proj;
pub mod geom;
pub mod warp;

#[link(name="gdal")]
extern {
    fn GDALVersionInfo(key: *const c_char) -> *const c_char;
}

pub fn version_info(key: &str) -> String {
    let info = key.with_c_str(|c_key| {
        unsafe {
            let rv = GDALVersionInfo(c_key);
            return raw::from_c_str(rv);
        };
    });
    return info;
}

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

    #[test]
    fn test_version_info() {
        let release_date = version_info("RELEASE_DATE");
        let release_name = version_info("RELEASE_NAME");
        let version_text = version_info("--version");

        let expected_text: String = format!(
            "GDAL {}, released {}/{}/{}",
            release_name,
            release_date.as_slice().slice(0, 4),
            release_date.as_slice().slice(4, 6),
            release_date.as_slice().slice(6, 8),
        );

        assert_eq!(version_text.into_string(), expected_text);
    }
}
<commit_msg>update crate name<commit_after>#![crate_name="gdal"]
#![crate_type="lib"]
#![feature(unsafe_destructor)]

extern crate sync;
extern crate libc;
#[cfg(test)] extern crate test;

use std::str::raw;
use libc::c_char;

pub mod raster;
pub mod vector;
pub mod proj;
pub mod geom;
pub mod warp;

#[link(name="gdal")]
extern {
    fn GDALVersionInfo(key: *const c_char) -> *const c_char;
}

pub fn version_info(key: &str) -> String {
    let info = key.with_c_str(|c_key| {
        unsafe {
            let rv = GDALVersionInfo(c_key);
            return raw::from_c_str(rv);
        };
    });
    return info;
}

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

    #[test]
    fn test_version_info() {
        let release_date = version_info("RELEASE_DATE");
        let release_name = version_info("RELEASE_NAME");
        let version_text = version_info("--version");

        let expected_text: String = format!(
            "GDAL {}, released {}/{}/{}",
            release_name,
            release_date.as_slice().slice(0, 4),
            release_date.as_slice().slice(4, 6),
            release_date.as_slice().slice(6, 8),
        );

        assert_eq!(version_text.into_string(), expected_text);
    }
}
<|endoftext|>