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;
const DEPRECATED_PARAMS: [(&str, &str); 2] = [
("Benders_Tolerances_feasibilitycut", "Benders_Tolerances_FeasibilityCut"),
("Benders_Tolerances_optimalitycut", "Benders_Tolerances_OptimalityCut"),
];
struct Constants<'a> {
consts: HashMap<&'a str, &'a str>,
args: HashMap<&'a str, &'a str>,
enums: HashMap<&'a str, Vec<(&'a str, Option<&'a str>)>>,
}
fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
let out_dir = env::var("OUT_DIR")?;
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(_) => {
println!("cargo:rustc-link-lib=cplex");
if let Some(dir) = ["/usr/include", "/usr/local/include"].iter().find(|dir| {
let p = Path::new(dir).join("ilcplex");
let f = p.join("cplex.h");
f.exists()
}) {
Path::new(dir).join("ilcplex")
} else {
panic!("Can't find include file `ilcplex/cplex.h` (maybe set CPLEX_HOME environment variable)");
}
}
};
let mut text = String::new();
File::open(cplex_dir.join("cpxconst.h"))?.read_to_string(&mut text)?;
let constants = {
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"))?;
parse_cplex(cplex_dir.join("cplex.h").to_str().unwrap(), &constants, &mut f)?;
}
Ok(())
}
fn parse_cpxconst_args(text: &str) -> HashMap<&str, &str> {
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.get(1).unwrap().as_str(), caps.get(2).unwrap().as_str())),
)
}
fn parse_cpxconst_enum(text: &str) -> HashMap<&str, &str> {
let re_args = Regex::new(
r#"#define\s+CPX(_[A-Za-z_]+|(?:MIP|PARAM)_[A-Za-z_]+)\s*([-+eE0-9.]+[lL]*|NAN|'[^']'|"[^\n"]*")\n"#,
)
.unwrap();
HashMap::from_iter(re_args.captures_iter(text).filter_map(|caps| {
let name = caps.get(1).unwrap().as_str();
let value = caps.get(2).unwrap().as_str();
if name.starts_with("_PARAM") || name == "PARAM_H" {
None
} else if name.starts_with("PARAM") || name.starts_with("MIP") {
Some((name, value))
} else {
Some((&name[1..], value))
}
}))
}
fn parse_cpxconst_enums(text: &str) -> HashMap<&str, Vec<(&str, Option<&str>)>> {
let re_enums = Regex::new(r"typedef\s+enum\s*\{((?:\s+CPX[- \tA-Z_=0-9]+,?)+\s*)}\s*(CPX[A-Z_]+)\s*;").unwrap();
let re_fields = Regex::new(r"CPX[A-Z]+_([A-Z_]+)(?:\s+=\s+([-0-9]+))?").unwrap();
HashMap::from_iter(re_enums.captures_iter(text).map(|caps| {
(
caps.get(2).unwrap().as_str(),
re_fields
.captures_iter(caps.get(1).unwrap().as_str())
.map(|field| (field.get(1).unwrap().as_str(), field.get(2).map(|s| s.as_str())))
.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, "use std::f64::NAN;")?;
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 {{}}")?;
writeln!(f, "pub enum ParamSet {{}}")?;
let mut stats = HashMap::new();
let mut mip = 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 let Some(var) = var.strip_prefix("STAT_") {
stats.insert(var, val.parse::<i64>().unwrap());
} else if let Some(var) = var.strip_prefix("MIP_") {
mip.insert(var, val.parse::<i64>().unwrap());
} else if let Some(var) = var.strip_prefix("PARAM_") {
params.insert(var, val.parse::<i64>().unwrap());
} else if let Some(var) = var.strip_prefix("ALG_") {
algs.insert(var, val.parse::<i64>().unwrap());
} else {
defines.insert(var, val);
}
}
fix_params(&mut params);
write_enum(f, "Stat", &stats)?;
write_enum(f, "Mip", &mip)?;
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 let Some(val) = val.strip_suffix("LL") {
("c_longlong", val, "")
} else if val.contains('e') || val.contains('E') || val == &"NAN" {
("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",
"CPXMODELASSTCALLBACKFUNC" => "extern fn(c_int, *const c_char, *mut c_void) -> c_int",
"CPXMODELASSTCALLBACKFUNC **" => "*mut extern fn(c_int, *const c_char, *mut c_void) -> c_int",
"CPXCNETptr" => "*const Net",
"CPXCHANNELptr" => "*mut Channel",
"CPXCHANNELptr *" => "*mut *mut Channel",
"CPXCPARAMSETptr" => "*const ParamSet",
"CPXPARAMSETptr" => "*mut ParamSet",
"CPXPARAMSETptr *" => "*mut *mut ParamSet",
"CPXCPARAMSETptr const *" => "*const *const ParamSet",
"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 {
if let Some(name) = s.strip_suffix("INForUNBD") {
return to_camel_case(&format!("{}_INF_OR_UNBD", name));
}
if s.chars().any(|c| c.is_lowercase()) {
s.to_string()
} else {
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, "#[allow(non_camel_case_types)]")?;
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: &[(&str, Option<&str>)]) -> Result<()> {
writeln!(
f,
"#[derive(Clone, Copy, PartialEq, Eq, Debug)]\n#[repr(C)]\npub enum {} {{",
c_to_rust_type(name),
)?;
for field in fields {
write!(f, " {}", to_camel_case(field.0))?;
if let Some(default) = &field.1 {
write!(f, " = {}", default)?;
}
writeln!(f, ",")?;
}
writeln!(f, "}}")?;
Ok(())
}
fn fix_params(params: &mut HashMap<&str, i64>) {
for &(old, new) in &DEPRECATED_PARAMS[..] {
if let Some(old_val) = params.remove(old) {
if let Some(new_val) = params.insert(new, old_val) {
if new_val != old_val {
panic!("Different values for {} and {}", old, new);
}
}
}
}
}