Skip to main content

jia_parse/jia_lang/parser/
domain.rs

1//! Domains block parser for the CP language.
2//!
3//! Parses domain statements like:
4//! ```text
5//! domains {
6//!   duration(a, b) = 3
7//!   start(a) in 0..10
8//!   optional(x, y)
9//!   makespan in 0..100
10//!   machine1 = {a, b, c}
11//!   demand(a, res1) = 2
12//! }
13//! ```
14
15use super::Parser;
16use crate::error::ParseError;
17use crate::jia_lang::ast::{Domain, DomainStmt};
18use crate::jia_lang::lexer::TokenKind;
19
20#[derive(Clone, Copy)]
21enum IntervalAttr {
22    Duration,
23    Start,
24    End,
25}
26
27impl IntervalAttr {
28    fn from_name(name: &str) -> Option<Self> {
29        match name {
30            "duration" => Some(Self::Duration),
31            "start" => Some(Self::Start),
32            "end" => Some(Self::End),
33            _ => None,
34        }
35    }
36
37    fn to_stmt(self, intervals: Vec<String>, domain: Domain) -> DomainStmt {
38        match self {
39            Self::Duration => DomainStmt::IntervalDuration { intervals, domain },
40            Self::Start => DomainStmt::IntervalStart { intervals, domain },
41            Self::End => DomainStmt::IntervalEnd { intervals, domain },
42        }
43    }
44}
45
46impl<'a> Parser<'a> {
47    /// Parse a `domains { ... }` block.
48    pub fn parse_domains_block(&mut self) -> Result<Vec<DomainStmt>, ParseError> {
49        self.expect_ident_matching("domains")?;
50        self.expect_token(TokenKind::LBrace)?;
51
52        let mut stmts = Vec::new();
53        while self.peek_kind() != Some(TokenKind::RBrace) {
54            stmts.push(self.parse_domain_stmt()?);
55        }
56        self.expect_token(TokenKind::RBrace)?;
57        Ok(stmts)
58    }
59
60    /// Parse a single domain statement. Dispatches based on the first token.
61    fn parse_domain_stmt(&mut self) -> Result<DomainStmt, ParseError> {
62        let name = match self.peek_kind() {
63            Some(TokenKind::Ident(s)) => s,
64            _ => {
65                return Err(ParseError::new(
66                    "expected domain statement",
67                    self.current_span(),
68                ))
69            }
70        };
71
72        if let Some(attr) = IntervalAttr::from_name(name.as_str()) {
73            return self.parse_interval_attr_stmt(attr);
74        }
75
76        match name.as_str() {
77            "optional" => self.parse_optional_stmt(),
78            "demand" => self.parse_demand_stmt(),
79            _ => {
80                // Could be: `name in ...` (integer domain) or `name = {...}` (set domain)
81                let ident = self.expect_ident()?;
82                match self.peek_kind() {
83                    Some(TokenKind::Ident(kw)) if kw.as_str() == "in" => {
84                        self.parse_integer_domain(ident)
85                    }
86                    Some(TokenKind::Eq) => self.parse_set_domain(ident),
87                    _ => Err(ParseError::new(
88                        format!("expected 'in' or '=' after '{ident}'"),
89                        self.current_span(),
90                    )),
91                }
92            }
93        }
94    }
95
96    /// Parse an interval attribute statement: `duration(a, b) = 3` or `start(a) in 0..10`
97    fn parse_interval_attr_stmt(&mut self, attr: IntervalAttr) -> Result<DomainStmt, ParseError> {
98        self.expect_ident()?;
99        self.expect_token(TokenKind::LParen)?;
100        let intervals = self.parse_ident_list()?;
101        self.expect_token(TokenKind::RParen)?;
102        let domain = self.parse_domain_spec()?;
103
104        Ok(attr.to_stmt(intervals, domain))
105    }
106
107    /// Parse `optional(x, y, z)`
108    fn parse_optional_stmt(&mut self) -> Result<DomainStmt, ParseError> {
109        self.expect_ident_matching("optional")?;
110        self.expect_token(TokenKind::LParen)?;
111        let intervals = self.parse_ident_list()?;
112        self.expect_token(TokenKind::RParen)?;
113        Ok(DomainStmt::IntervalOptional { intervals })
114    }
115
116    /// Parse `demand(interval, set) = value`
117    fn parse_demand_stmt(&mut self) -> Result<DomainStmt, ParseError> {
118        self.expect_ident_matching("demand")?;
119        self.expect_token(TokenKind::LParen)?;
120        let interval = self.expect_ident()?;
121        self.expect_token(TokenKind::Comma)?;
122        let set = self.expect_ident()?;
123        self.expect_token(TokenKind::RParen)?;
124        self.expect_token(TokenKind::Eq)?;
125        let value = self.expect_number()?;
126        Ok(DomainStmt::Demand {
127            interval,
128            set,
129            value,
130        })
131    }
132
133    /// Parse an integer domain: `name in 0..100` or `name in {1, 3, 5}`
134    /// (The identifier has already been consumed.)
135    fn parse_integer_domain(&mut self, name: String) -> Result<DomainStmt, ParseError> {
136        self.expect_ident_matching("in")?;
137        let domain = self.parse_domain_value()?;
138        Ok(DomainStmt::IntegerDomain { name, domain })
139    }
140
141    /// Parse a set domain: `name = {a, b, c}`
142    /// (The identifier has already been consumed.)
143    fn parse_set_domain(&mut self, name: String) -> Result<DomainStmt, ParseError> {
144        self.expect_token(TokenKind::Eq)?;
145        self.expect_token(TokenKind::LBrace)?;
146        let members = self.parse_ident_list()?;
147        self.expect_token(TokenKind::RBrace)?;
148        Ok(DomainStmt::SetDomain { name, members })
149    }
150
151    /// Parse a domain specification after an interval attribute: `= 3`, `= 3.14`, `in 0..10`, or `in {1, 3}`
152    fn parse_domain_spec(&mut self) -> Result<Domain, ParseError> {
153        match self.peek_kind() {
154            Some(TokenKind::Eq) => {
155                self.advance();
156                match self.peek_kind() {
157                    Some(TokenKind::Number(n)) => {
158                        self.advance();
159                        Ok(Domain::Fixed(n))
160                    }
161                    Some(TokenKind::Float(f)) => {
162                        self.advance();
163                        Ok(Domain::RealFixed(f))
164                    }
165                    _ => Err(ParseError::new(
166                        "expected number after '='",
167                        self.current_span(),
168                    )),
169                }
170            }
171            Some(TokenKind::Ident(kw)) if kw.as_str() == "in" => {
172                self.advance();
173                self.parse_domain_value()
174            }
175            _ => Err(ParseError::new(
176                "expected '=' or 'in' in domain specification",
177                self.current_span(),
178            )),
179        }
180    }
181
182    /// Parse a numeric bound: integer, float, or `inf`/`-inf`.
183    fn parse_bound(&mut self) -> Result<f64, ParseError> {
184        // Handle optional leading minus for -inf or negative numbers
185        let negate = if self.peek_kind() == Some(TokenKind::Minus) {
186            self.advance();
187            true
188        } else {
189            false
190        };
191        match self.peek_kind() {
192            Some(TokenKind::Number(n)) => {
193                self.advance();
194                Ok(if negate { -(n as f64) } else { n as f64 })
195            }
196            Some(TokenKind::Float(f)) => {
197                self.advance();
198                Ok(if negate { -f } else { f })
199            }
200            Some(TokenKind::Ident(s)) if s == "inf" => {
201                self.advance();
202                Ok(if negate {
203                    f64::NEG_INFINITY
204                } else {
205                    f64::INFINITY
206                })
207            }
208            _ => Err(ParseError::new(
209                "expected number, float, or 'inf'",
210                self.current_span(),
211            )),
212        }
213    }
214
215    /// Parse a domain value after `in`: `0..100`, `0.0..inf`, `{1, 3, 5}`
216    fn parse_domain_value(&mut self) -> Result<Domain, ParseError> {
217        match self.peek_kind() {
218            Some(TokenKind::LBrace) => {
219                // Enumerated: {1, 3, 5}
220                self.advance();
221                let mut values = vec![self.expect_number()?];
222                while self.peek_kind() == Some(TokenKind::Comma) {
223                    self.advance();
224                    values.push(self.expect_number()?);
225                }
226                self.expect_token(TokenKind::RBrace)?;
227                Ok(Domain::Enumerated(values))
228            }
229            Some(TokenKind::Number(_)) | Some(TokenKind::Float(_)) | Some(TokenKind::Minus) => {
230                // Range: 0..100 or 0.0..inf or -inf..inf
231                let min = self.parse_bound()?;
232                self.expect_token(TokenKind::DotDot)?;
233                let max = self.parse_bound()?;
234                // If both bounds are exact integers, use integer Range
235                if min.fract() == 0.0 && max.fract() == 0.0 && min.is_finite() && max.is_finite() {
236                    Ok(Domain::Range {
237                        min: min as i64,
238                        max: max as i64,
239                    })
240                } else {
241                    Ok(Domain::RealRange { min, max })
242                }
243            }
244            Some(TokenKind::Ident(s)) if s == "inf" || s == "free" => {
245                let kw = s.clone();
246                if kw == "free" {
247                    self.advance();
248                    return Ok(Domain::RealRange {
249                        min: f64::NEG_INFINITY,
250                        max: f64::INFINITY,
251                    });
252                }
253                // inf..something or just inf as range start
254                let min = self.parse_bound()?;
255                self.expect_token(TokenKind::DotDot)?;
256                let max = self.parse_bound()?;
257                Ok(Domain::RealRange { min, max })
258            }
259            _ => Err(ParseError::new(
260                "expected range (min..max), enumeration ({v1, v2, ...}), or 'free'",
261                self.current_span(),
262            )),
263        }
264    }
265}
266
267#[cfg(test)]
268mod tests {
269    use crate::jia_lang::ast::{Domain, DomainStmt};
270    use crate::jia_lang::lexer::tokenize;
271    use crate::jia_lang::parser::Parser;
272
273    fn parse_domains(input: &str) -> Vec<DomainStmt> {
274        let tokens = tokenize(input).unwrap();
275        let mut parser = Parser::new(&tokens);
276        parser.parse_domains_block().unwrap()
277    }
278
279    #[test]
280    fn test_fixed_duration() {
281        let stmts = parse_domains("domains { duration(a) = 3 }");
282        assert_eq!(
283            stmts[0],
284            DomainStmt::IntervalDuration {
285                intervals: vec!["a".to_string()],
286                domain: Domain::Fixed(3),
287            }
288        );
289    }
290
291    #[test]
292    fn test_range_duration() {
293        let stmts = parse_domains("domains { duration(a) in 1..4 }");
294        assert_eq!(
295            stmts[0],
296            DomainStmt::IntervalDuration {
297                intervals: vec!["a".to_string()],
298                domain: Domain::Range { min: 1, max: 4 },
299            }
300        );
301    }
302
303    #[test]
304    fn test_enumerated_duration() {
305        let stmts = parse_domains("domains { duration(a) in {1, 3, 5} }");
306        assert_eq!(
307            stmts[0],
308            DomainStmt::IntervalDuration {
309                intervals: vec!["a".to_string()],
310                domain: Domain::Enumerated(vec![1, 3, 5]),
311            }
312        );
313    }
314
315    #[test]
316    fn test_multi_interval_attr() {
317        let stmts = parse_domains("domains { start(a, b) in 0..20 }");
318        assert_eq!(
319            stmts[0],
320            DomainStmt::IntervalStart {
321                intervals: vec!["a".to_string(), "b".to_string()],
322                domain: Domain::Range { min: 0, max: 20 },
323            }
324        );
325    }
326
327    #[test]
328    fn test_optional() {
329        let stmts = parse_domains("domains { optional(x, y, z) }");
330        assert_eq!(
331            stmts[0],
332            DomainStmt::IntervalOptional {
333                intervals: vec!["x".to_string(), "y".to_string(), "z".to_string()],
334            }
335        );
336    }
337
338    #[test]
339    fn test_integer_domain() {
340        let stmts = parse_domains("domains { makespan in 0..100 }");
341        assert_eq!(
342            stmts[0],
343            DomainStmt::IntegerDomain {
344                name: "makespan".to_string(),
345                domain: Domain::Range { min: 0, max: 100 },
346            }
347        );
348    }
349
350    #[test]
351    fn test_set_domain() {
352        let stmts = parse_domains("domains { machine1 = {a, b, c} }");
353        assert_eq!(
354            stmts[0],
355            DomainStmt::SetDomain {
356                name: "machine1".to_string(),
357                members: vec!["a".to_string(), "b".to_string(), "c".to_string()],
358            }
359        );
360    }
361
362    #[test]
363    fn test_demand() {
364        let stmts = parse_domains("domains { demand(a1, res1) = 2 }");
365        assert_eq!(
366            stmts[0],
367            DomainStmt::Demand {
368                interval: "a1".to_string(),
369                set: "res1".to_string(),
370                value: 2,
371            }
372        );
373    }
374
375    #[test]
376    fn test_domain_error_branches() {
377        for input in [
378            "domains { 1 }",
379            "domains { x }",
380            "domains { duration(a) }",
381            "domains { duration(a) = x }",
382            "domains { x in nope }",
383            "domains { x in 0..nope }",
384            "domains { x in 0.. }",
385        ] {
386            let tokens = tokenize(input).unwrap();
387            let mut parser = Parser::new(&tokens);
388            assert!(parser.parse_domains_block().is_err(), "{input}");
389        }
390    }
391
392    #[test]
393    fn test_domain_spec_inf_range_start() {
394        let stmts = parse_domains("domains { x in inf..10 }");
395        assert!(matches!(
396            stmts[0],
397            DomainStmt::IntegerDomain {
398                domain: Domain::RealRange { min, max },
399                ..
400            } if min.is_infinite() && min.is_sign_positive() && max == 10.0
401        ));
402    }
403}