jia_parse/jia_lang/parser/
constraint.rs1use super::Parser;
15use crate::error::ParseError;
16use crate::jia_lang::ast::{CmpOp, Constraint};
17use crate::jia_lang::lexer::TokenKind;
18
19impl<'a> Parser<'a> {
20 pub fn parse_constraints_block(&mut self) -> Result<Vec<Constraint>, ParseError> {
22 self.expect_ident_matching("constraints")?;
23 self.expect_token(TokenKind::LBrace)?;
24
25 let mut constraints = Vec::new();
26 while self.peek_kind() != Some(TokenKind::RBrace) {
27 constraints.push(self.parse_constraint_stmt()?);
28 }
29 self.expect_token(TokenKind::RBrace)?;
30 Ok(constraints)
31 }
32
33 fn parse_constraint_stmt(&mut self) -> Result<Constraint, ParseError> {
35 if let Some(TokenKind::Ident(name)) = self.peek_kind() {
37 match name.as_str() {
38 "no_overlap" => return self.parse_no_overlap(),
39 "cumulative" => return self.parse_cumulative(),
40 "span" => return self.parse_span(),
41 "alternative" => return self.parse_alternative(),
42 _ => {}
43 }
44 }
45
46 self.parse_comparison()
48 }
49
50 fn parse_no_overlap(&mut self) -> Result<Constraint, ParseError> {
52 self.expect_ident_matching("no_overlap")?;
53 self.expect_token(TokenKind::LParen)?;
54 let intervals = self.parse_ident_list()?;
55 self.expect_token(TokenKind::RParen)?;
56 Ok(Constraint::NoOverlap { intervals })
57 }
58
59 fn parse_cumulative(&mut self) -> Result<Constraint, ParseError> {
61 self.expect_ident_matching("cumulative")?;
62 self.expect_token(TokenKind::LParen)?;
63 let set = self.expect_ident()?;
64 self.expect_token(TokenKind::Comma)?;
65 let capacity = self.parse_expr()?;
66 self.expect_token(TokenKind::RParen)?;
67 Ok(Constraint::Cumulative { set, capacity })
68 }
69
70 fn parse_span(&mut self) -> Result<Constraint, ParseError> {
72 self.expect_ident_matching("span")?;
73 self.expect_token(TokenKind::LParen)?;
74 let parent = self.expect_ident()?;
75 self.expect_token(TokenKind::Comma)?;
76 let set = self.expect_ident()?;
77 self.expect_token(TokenKind::RParen)?;
78 Ok(Constraint::Span { parent, set })
79 }
80
81 fn parse_alternative(&mut self) -> Result<Constraint, ParseError> {
83 self.expect_ident_matching("alternative")?;
84 self.expect_token(TokenKind::LParen)?;
85 let parent = self.expect_ident()?;
86 self.expect_token(TokenKind::Comma)?;
87 let set = self.expect_ident()?;
88 self.expect_token(TokenKind::RParen)?;
89 Ok(Constraint::Alternative { parent, set })
90 }
91
92 fn parse_comparison(&mut self) -> Result<Constraint, ParseError> {
94 let left = self.parse_expr()?;
95 let op = self.parse_cmp_op()?;
96 let right = self.parse_expr()?;
97 Ok(Constraint::Comparison { left, op, right })
98 }
99
100 fn parse_cmp_op(&mut self) -> Result<CmpOp, ParseError> {
102 let span = self.current_span();
103 match self.peek_kind() {
104 Some(TokenKind::Le) => {
105 self.advance();
106 Ok(CmpOp::Le)
107 }
108 Some(TokenKind::Ge) => {
109 self.advance();
110 Ok(CmpOp::Ge)
111 }
112 Some(TokenKind::Lt) => {
113 self.advance();
114 Ok(CmpOp::Lt)
115 }
116 Some(TokenKind::Gt) => {
117 self.advance();
118 Ok(CmpOp::Gt)
119 }
120 Some(TokenKind::EqEq) | Some(TokenKind::Eq) => {
121 self.advance();
122 Ok(CmpOp::Eq)
123 }
124 Some(TokenKind::Ne) => {
125 self.advance();
126 Ok(CmpOp::Ne)
127 }
128 _ => Err(ParseError::new(
129 "expected comparison operator (<=, >=, <, >, ==, !=, =)",
130 span,
131 )),
132 }
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use crate::jia_lang::ast::{CmpOp, Constraint, Expr};
139 use crate::jia_lang::lexer::tokenize;
140 use crate::jia_lang::parser::Parser;
141
142 fn parse_constraints(input: &str) -> Vec<Constraint> {
143 let tokens = tokenize(input).unwrap();
144 let mut parser = Parser::new(&tokens);
145 parser.parse_constraints_block().unwrap()
146 }
147
148 #[test]
149 fn test_no_overlap() {
150 let cs = parse_constraints("constraints { no_overlap(machine1) }");
151 assert_eq!(
152 cs[0],
153 Constraint::NoOverlap {
154 intervals: vec!["machine1".to_string()]
155 }
156 );
157 }
158
159 #[test]
160 fn test_no_overlap_multiple() {
161 let cs = parse_constraints("constraints { no_overlap(a, b, c) }");
162 assert_eq!(
163 cs[0],
164 Constraint::NoOverlap {
165 intervals: vec!["a".to_string(), "b".to_string(), "c".to_string()]
166 }
167 );
168 }
169
170 #[test]
171 fn test_cumulative() {
172 let cs = parse_constraints("constraints { cumulative(res1, 4) }");
173 assert_eq!(
174 cs[0],
175 Constraint::Cumulative {
176 set: "res1".to_string(),
177 capacity: Expr::Number(4),
178 }
179 );
180 }
181
182 #[test]
183 fn test_span() {
184 let cs = parse_constraints("constraints { span(project, phases) }");
185 assert_eq!(
186 cs[0],
187 Constraint::Span {
188 parent: "project".to_string(),
189 set: "phases".to_string(),
190 }
191 );
192 }
193
194 #[test]
195 fn test_alternative() {
196 let cs = parse_constraints("constraints { alternative(j1_o1, j1_o1_alts) }");
197 assert_eq!(
198 cs[0],
199 Constraint::Alternative {
200 parent: "j1_o1".to_string(),
201 set: "j1_o1_alts".to_string(),
202 }
203 );
204 }
205
206 #[test]
207 fn test_comparison() {
208 let cs = parse_constraints("constraints { end_of(a) <= start_of(b) }");
209 assert_eq!(
210 cs[0],
211 Constraint::Comparison {
212 left: Expr::EndOf("a".to_string()),
213 op: CmpOp::Le,
214 right: Expr::StartOf("b".to_string()),
215 }
216 );
217 }
218
219 #[test]
220 fn test_comparison_with_arithmetic() {
221 let cs = parse_constraints("constraints { start_of(b) >= end_of(a) + gap }");
222 assert!(matches!(
223 &cs[0],
224 Constraint::Comparison { op, .. } if *op == CmpOp::Ge
225 ));
226 }
227
228 #[test]
229 fn test_lt_gt_ne_and_missing_comparator_errors() {
230 for (input, expected) in [
231 ("constraints { x < 1 }", CmpOp::Lt),
232 ("constraints { x > 1 }", CmpOp::Gt),
233 ("constraints { x != 1 }", CmpOp::Ne),
234 ] {
235 let cs = parse_constraints(input);
236 assert!(matches!(
237 &cs[0],
238 Constraint::Comparison { op, .. } if *op == expected
239 ));
240 }
241
242 let tokens = tokenize("constraints { x + 1 }").unwrap();
243 let mut parser = Parser::new(&tokens);
244 assert!(parser.parse_constraints_block().is_err());
245 }
246}