use std::collections::HashMap;
use std::env;
use std::fs::File;
use std::io::{Read, Result, Write};
use std::iter::FromIterator;
use std::path::Path;
extern crate regex;
use regex::Regex;
struct Constants {
consts: HashMap<String, String>,
args: HashMap<String, String>,
enums: HashMap<String, Vec<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),
enums: parse_cpxconst_enums(&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_cpxconst_enums(text: &str) -> HashMap<String, Vec<String>> {
let re_enums = Regex::new(r#"typedef\s+enum\s*\{((?:\s+CPX[A-Z_]+,?)+\s*)\}\s*(CPX[A-Z_]+)\s*;"#).unwrap();
let re_fields = Regex::new(r#"CPX[A-Z]+_([A-Z_]+)"#).unwrap();
HashMap::from_iter(re_enums.captures_iter(text).map(|caps| {
(
caps[2].to_string(),
re_fields
.captures_iter(&caps[1])
.map(|field| field[1].to_string())
.collect(),
)
}))
}
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 {{}}")?;
writeln!(f, "pub enum CallbackContext {{}}")?;
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", ¶ms)?;
write_enum(f, "Alg", &algs)?;
for (name, fields) in &constants.enums {
write_c_enum(f, &name, &fields)?;
}
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("\"") {
("&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(¶m["type"]))?;
} else {
match ¶m["ftype"] {
"*" => write!(f, "{}: Option<extern fn(", ¶m["fname"])?,
"**" => write!(f, "{}: *mut Option<extern fn(", ¶m["fname"])?,
_ => panic!("Unsupported function pointer type for {}", &caps["name"]),
}
if let Some(fparams) = constants.args.get(¶m["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(¶m["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",
"CPXINFOTYPE" => "InfoType",
"CPXCALLBACKCONTEXTptr" => "*mut CallbackContext",
"CPXCALLBACKFUNC" => "extern fn(*mut CallbackContext, c_longlong, *mut c_void) -> c_int",
"CPXCALLBACKFUNC **" => "*mut extern fn(*mut CallbackContext, c_longlong, *mut c_void) -> c_int",
"CPXCALLBACKINFO" => "CallbackInfo",
"CPXCALLBACKSOLUTIONSTRATEGY" => "CallbackSolutionStrategy",
"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(())
}
fn write_c_enum<W: Write>(f: &mut W, name: &str, fields: &[String]) -> Result<()> {
writeln!(
f,
"#[derive(Clone, Copy, PartialEq, Eq, Debug)]\n#[repr(C)]\npub enum {} {{",
c_to_rust_type(name),
)?;
for field in fields {
writeln!(f, " {},", to_camel_case(field))?;
}
writeln!(f, "}}")?;
Ok(())
}