cplex-sys 0.3.0

Low level bindings to the Cplex C-API
use std::env;
use std::fs::File;
use std::io::{Read, Write, Result};
use std::path::Path;
use std::collections::HashMap;
use std::iter::FromIterator;

extern crate regex;
use regex::Regex;

struct Constants {
    consts: HashMap<String, String>,
    args: HashMap<String, String>,
}

fn main() {
    let out_dir = env::var("OUT_DIR").unwrap();
    let out_dir = Path::new(&out_dir);
    let cplex_dir = match env::var("CPLEX_HOME") {
        Ok(cpx) => {
            println!("cargo:rustc-link-search={}/lib/x86-64_sles10_4.1/static_pic", cpx);
            println!("cargo:rustc-link-search={}/lib/x86-64_linux/static_pic", cpx);
            println!("cargo:rustc-link-lib=cplex");
            Path::new(&cpx).join("include").join("ilcplex")
        },
        Err(_) => panic!("CPLEX_HOME environment variable not set"),
    };

    let constants = {
        let mut text = String::new();
        File::open(cplex_dir.join("cpxconst.h")).unwrap().read_to_string(&mut text).unwrap();
        Constants {
            args: parse_cpxconst_args(&text),
            consts: parse_cpxconst_enum(&text),
        }
    };

    {
        let mut f = File::create(out_dir.join("cplex-extern.rs")).unwrap();
        parse_cplex(cplex_dir.join("cplex.h").to_str().unwrap(), &constants, &mut f).unwrap();
    }
}

fn parse_cpxconst_args(text: &str) -> HashMap<String, String> {
    let re_args = Regex::new(r"#define\s+(CALLBACK_[A-Z]+_ARGS)\s+((?:[^\\\n]|\\\n)*)").unwrap();
    HashMap::from_iter(re_args.captures_iter(text).map(|caps| {
        (caps[1].to_string(), caps[2].to_string())
    }))
}

fn parse_cpxconst_enum(text: &str) -> HashMap<String, String> {
    let re_args = Regex::new(&format!(r#"#define\s+CPX_([A-Z_]+)\s*([-+eE0-9.]+[lL]*|'[^']'|"[^\n"]*")\n"#)).unwrap();
    HashMap::from_iter(re_args.captures_iter(text).map(|caps| {
        (caps[1].to_string(), caps[2].to_string())
    }))
}


fn parse_cplex<W: Write>(filename: &str, constants: &Constants, f: &mut W) -> Result<()> {
    let mut text = String::new();
    File::open(filename)?.read_to_string(&mut text)?;

    let re = Regex::new(r"CPXLIBAPI\s+(?P<return>\w+)\s+CPXPUBLIC\s+(?P<name>CPX[[:alpha:]]+)\s*\((?P<params>[^;]+)\)\s*;").unwrap();
    let re_params = Regex::new(r"(?P<type>\w[^,()]*)(?P<name>\b[0-9a-zA-Z_]+)\s*(?:,|$)|(?P<freturn>\w[^()]*)\(CPXPUBLIC\s*(?P<ftype>\*+)\s*(?P<fname>\w+)\s*\)\s*\((?P<fparams>[^)]*)\)\s*(?:,|$)").unwrap();

    writeln!(f, "pub enum Env {{}}")?;
    writeln!(f, "pub enum Lp {{}}")?;
    writeln!(f, "pub enum Net {{}}")?;
    writeln!(f, "pub enum Channel {{}}")?;
    writeln!(f, "pub enum Serializer {{}}")?;
    writeln!(f, "pub enum Deserializer {{}}")?;

    let mut stats = HashMap::new();
    let mut params = HashMap::new();
    let mut algs = HashMap::new();
    let mut defines = HashMap::new();

    for (var, val) in constants.consts.iter() {
        if var.starts_with("STAT_") {
            stats.insert(&var[5..], val.parse::<i64>().unwrap());
        } else if var.starts_with("PARAM_") {
            params.insert(&var[6..], val.parse::<i64>().unwrap());
        } else if var.starts_with("ALG_") {
            algs.insert(&var[4..], val.parse::<i64>().unwrap());
        } else {
            defines.insert(var, val);
        }
    }

    write_enum(f, "Stat", &stats)?;
    write_enum(f, "Param", &params)?;
    write_enum(f, "Alg", &algs)?;

    for (var, &val) in defines.iter() {
        let (typ, val, postfix) = if val.starts_with("'") && val.ends_with("'") {
            ("c_char", &val[..], " as c_char")
        } else if val.starts_with("\"") && val.ends_with("\"") {
            ("&'static str", &val[..], "")
        } else if val.ends_with("LL") {
            ("c_longlong", &val[..val.len() - 2], "")
        } else if val.contains('e') || val.contains('E') {
            ("c_double", &val[..], "")
        } else {
            ("c_int", &val[..], "")
        };
        writeln!(f, "pub const {} : {} = {}{};", var, typ, val, postfix)?;
    }

    writeln!(f, "extern \"C\" {{")?;

    for caps in re.captures_iter(&text) {

        writeln!(f, "    #[link_name = \"{}\"]", &caps["name"])?;
        write!(f, "    pub fn {}(", &caps["name"][3..])?;

        for param in re_params.captures_iter(&caps["params"]) {
            if let Some(name) = param.name("name").map(|m| m.as_str()) {
                write!(f, "{}: {}, ", c_to_rust_name(&name), c_to_rust_type(&param["type"]))?;
            } else {
                match &param["ftype"] {
                    "*" => write!(f, "{}: Option<extern fn(", &param["fname"])?,
                    "**" => write!(f, "{}: *mut Option<extern fn(", &param["fname"])?,
                    _ => panic!("Unsupported function pointer type for {}", &caps["name"]),
                }

                if let Some(fparams) = constants.args.get(&param["fparams"]) {
                    for fparam in re_params.captures_iter(fparams) {
                        write!(f, "{}, ", c_to_rust_type(&fparam["type"]))?;
                    }
                } else {
                    for fparam in param["fparams"].split(',').map(|fp| fp.trim()) {
                        write!(f, "{}, ", c_to_rust_type(&fparam))?;
                    }
                }
                if param["freturn"].trim() == "void" {
                    write!(f, ")>, ")?;
                } else {
                    write!(f, ") -> {}>, ", c_to_rust_type(&param["freturn"]))?;
                }
            }
        }

        write!(f, ")")?;

        if caps["return"].trim() != "void" {
            write!(f, " -> {}", c_to_rust_type(&caps["return"]))?;
        }

        writeln!(f, ";")?;
    }
    writeln!(f, "}}")?;

    Ok(())
}

fn c_to_rust_type(ctype: &str) -> &str {
    let ctype = ctype.trim();
    match ctype {
        "int" | "CPXINT" => "c_int",
        "int *" | "CPXINT *" | "volatile int *" => "*mut c_int",
        "const int *" | "int const *" => "*const c_int",
        "CPXLONG" => "c_longlong",
        "CPXLONG *" => "*mut c_longlong",
        "CPXLONG const *" => "*const c_longlong",
        "double" => "c_double",
        "double *" => "*mut c_double",
        "const double *" | "double const *" => "*const c_double",
        "char" => "c_char",
        "const char *" | "char const *" | "CPXCCHARptr" => "*const c_char",
        "char *" | "CPXCHARptr" => "*mut c_char",
        "char **" | "char  **" => "*const *const c_char",
        "void *" => "*mut c_void",
        "void const *" => "*const c_void",
        "void **" | "void  **" => "*mut *mut c_void",
        "CPXENVptr" => "*mut Env",
        "CPXENVptr *" => "*mut *mut Env",
        "CPXCENVptr" => "*const Env",
        "CPXLPptr" => "*mut Lp",
        "CPXLPptr *" => "*mut *mut Lp",
        "CPXCLPptr" => "*const Lp",
        "CPXCLPptr *" => "*mut *const Lp",
        "CPXNETptr" => "*mut Net",
        "CPXNETptr *" => "*mut *mut Net",
        "CPXCNETptr" => "*const Net",
        "CPXCHANNELptr" => "*mut Channel",
        "CPXCHANNELptr *" => "*mut *mut Channel",
        "CPXFILEptr" => "*mut File",
        "CPXFILEptr *" => "*mut *mut File",
        "CPXSERIALIZERptr" => "*mut Serializer",
        "CPXSERIALIZERptr *" => "*mut *mut Serializer",
        "CPXCSERIALIZERptr" => "*const Serializer",
        "CPXDESERIALIZERptr" => "*mut Deserializer",
        "CPXDESERIALIZERptr *" => "*mut *mut Deserializer",
        "CPXCDESERIALIZERptr" => "*const Deserializer",
        _ => panic!("Unknown C type: {}", ctype),
    }
}

fn c_to_rust_name(name: &str) -> &str {
    match name {
        "type" => "typ",
        _ => name,
    }
}

fn to_camel_case(s: &str) -> String {
    let mut result = String::new();
    let mut upcase = true;
    for c in s.chars() {
        if c == '_' {
            upcase = true;
        } else if upcase {
            result.extend(c.to_uppercase());
            upcase = false;
        } else {
            result.extend(c.to_lowercase());
            upcase = false;
        }
    }
    result
}

fn write_enum<W: Write>(f: &mut W, name: &str, values: &HashMap<&str, i64>) -> Result<()> {

    writeln!(f, "#[derive(Clone, Copy, PartialEq, Eq, Debug)]\npub enum {} {{", name)?;
    let mut values = values.iter().collect::<Vec<_>>();
    values.sort_by_key(|x| x.1);
    for &(name, num) in &values {writeln!(f, "    {} = {},", to_camel_case(name), num)?;}
    writeln!(f, "}}")?;

    writeln!(f, "impl {} {{ pub fn to_c(self) -> c_int {{ self as c_int }} }}", name)?;

    Ok(())
}