1use super::LanguageParser;
2use cnvx_core::{LinExpr, Model, Objective, VarId};
3
4#[derive(Default)]
5pub struct GMPLLanguage;
6
7impl GMPLLanguage {
8 pub fn new() -> Self {
9 Self {}
10 }
11}
12
13impl LanguageParser for GMPLLanguage {
14 fn parse(&self, src: &str) -> Result<Model, String> {
15 let mut model = Model::new();
16 let mut vars: Vec<VarId> = vec![];
17
18 for line in src.lines() {
19 let line = line.trim();
20 if line.is_empty() || line.starts_with('#') {
21 continue;
22 }
23
24 if line.starts_with("var ") {
25 let parts: Vec<_> = line.split_whitespace().collect();
26 if parts.len() >= 2 {
27 let v = model.add_var().finish();
28 vars.push(v);
29 }
30 } else if line.to_lowercase().starts_with("maximize") {
31 let after_colon = line.split(':').nth(1).unwrap_or("").trim();
32 let expr = parse_expression(after_colon, &vars)?;
33 model.add_objective(Objective::maximize(expr).name("Z"));
34 } else if line.to_lowercase().starts_with("subject to") {
35 let rest = line.split(':').nth(1).unwrap_or("").trim();
36 let (lhs, rhs, cmp) = parse_constraint(rest, &vars)?;
37 match cmp {
38 "<=" => model += lhs.leq(rhs),
39 ">=" => model += lhs.geq(rhs),
40 "=" => model += lhs.eq(rhs),
41 _ => return Err(format!("unknown constraint type '{}'", cmp)),
42 }
43 }
44 }
45
46 Ok(model)
47 }
48}
49
50fn parse_expression(expr: &str, vars: &[VarId]) -> Result<LinExpr, String> {
51 let mut le = LinExpr::constant(0.0);
52
53 for tok in expr.split('+') {
54 let tok = tok.trim();
55 if tok.is_empty() {
56 continue;
57 }
58
59 let (coef, varname) = if tok.contains('*') {
60 let parts: Vec<_> = tok.split('*').collect();
61 let coef = parts[0]
62 .parse::<f64>()
63 .map_err(|_| format!("invalid coefficient '{}'", parts[0]))?;
64 (coef, parts[1])
65 } else if let Some(rest) = tok.strip_prefix('-') {
66 (-1.0, rest)
67 } else {
68 (1.0, tok)
69 };
70
71 let varname = varname.trim().trim_end_matches(';');
72
73 let idx = varname[1..]
74 .parse::<usize>()
75 .map_err(|_| format!("invalid variable '{}'", varname))?;
76 if idx == 0 || idx > vars.len() {
77 return Err(format!("unknown variable '{}'", varname));
78 }
79 le += coef * vars[idx - 1];
80 }
81
82 Ok(le)
83}
84
85fn parse_constraint<'a>(
86 line: &'a str,
87 vars: &[VarId],
88) -> Result<(LinExpr, f64, &'a str), String> {
89 let cmp: &'a str = if line.contains("<=") {
90 "<="
91 } else if line.contains(">=") {
92 ">="
93 } else if line.contains('=') {
94 "="
95 } else {
96 return Err("invalid constraint".into());
97 };
98 let parts: Vec<&str> = line.split(cmp).collect();
99 if parts.len() != 2 {
100 return Err("invalid constraint format".into());
101 }
102
103 let lhs = parse_expression(parts[0].trim(), vars)?;
104 let rhs = parts[1]
105 .trim()
106 .trim_end_matches(';') .parse::<f64>()
108 .map_err(|_| "invalid RHS".to_string())?;
109
110 Ok((lhs, rhs, cmp))
111}