jia_parse/jia_lang/parser/
mod.rs1pub mod constraint;
12pub mod decl;
13pub mod domain;
14pub mod expr;
15
16use crate::error::{ParseError, Span};
17use crate::jia_lang::ast::{Expr, JiaModel, ModelType, Objective, OptDirection};
18use crate::jia_lang::lexer::{Token, TokenKind};
19
20pub struct Parser<'a> {
22 tokens: &'a [Token],
23 pos: usize,
24}
25
26impl<'a> Parser<'a> {
27 pub fn new(tokens: &'a [Token]) -> Self {
29 Self { tokens, pos: 0 }
30 }
31
32 pub fn peek_kind(&self) -> Option<TokenKind> {
34 self.tokens.get(self.pos).map(|t| t.kind.clone())
35 }
36
37 pub fn advance(&mut self) {
39 self.pos += 1;
40 }
41
42 pub fn current_span(&self) -> Span {
44 self.tokens
45 .get(self.pos)
46 .map(|t| t.span)
47 .unwrap_or(Span::new(0, 1, 1))
48 }
49
50 pub fn is_eof(&self) -> bool {
52 self.pos >= self.tokens.len()
53 }
54
55 pub fn expect_token(&mut self, expected: TokenKind) -> Result<(), ParseError> {
57 let span = self.current_span();
58 match self.peek_kind() {
59 Some(ref kind) if kind == &expected => {
60 self.advance();
61 Ok(())
62 }
63 Some(ref kind) => Err(ParseError::new(
64 format!("expected {expected:?}, got {kind:?}"),
65 span,
66 )),
67 None => Err(ParseError::new(
68 format!("expected {expected:?}, got end of input"),
69 span,
70 )),
71 }
72 }
73
74 pub fn expect_ident(&mut self) -> Result<String, ParseError> {
76 let span = self.current_span();
77 match self.peek_kind() {
78 Some(TokenKind::Ident(name)) => {
79 self.advance();
80 Ok(name)
81 }
82 Some(kind) => Err(ParseError::new(
83 format!("expected identifier, got {kind:?}"),
84 span,
85 )),
86 None => Err(ParseError::new(
87 "expected identifier, got end of input",
88 span,
89 )),
90 }
91 }
92
93 pub fn expect_ident_matching(&mut self, expected: &str) -> Result<(), ParseError> {
95 let span = self.current_span();
96 let name = self.expect_ident()?;
97 if name != expected {
98 return Err(ParseError::new(
99 format!("expected '{expected}', got '{name}'"),
100 span,
101 ));
102 }
103 Ok(())
104 }
105
106 pub fn expect_number(&mut self) -> Result<i64, ParseError> {
108 let span = self.current_span();
109 match self.peek_kind() {
110 Some(TokenKind::Number(n)) => {
111 self.advance();
112 Ok(n)
113 }
114 Some(kind) => Err(ParseError::new(
115 format!("expected number, got {kind:?}"),
116 span,
117 )),
118 None => Err(ParseError::new("expected number, got end of input", span)),
119 }
120 }
121}
122
123pub fn parse_model(tokens: &[Token]) -> Result<JiaModel, ParseError> {
125 let mut parser = Parser::new(tokens);
126
127 let model_type = if matches!(parser.peek_kind(), Some(TokenKind::At)) {
129 parser.advance(); parser.expect_ident_matching("model")?;
131 let span = parser.current_span();
132 let type_name = parser.expect_ident()?;
133 match type_name.as_str() {
134 "lp" => Some(ModelType::Lp),
135 "cp" => Some(ModelType::Cp),
136 other => {
137 return Err(ParseError::new(
138 format!("unknown model type '{other}', expected 'lp' or 'cp'"),
139 span,
140 ));
141 }
142 }
143 } else {
144 None
145 };
146
147 parser.expect_ident_matching("model")?;
149 let name = parser.expect_ident()?;
150
151 let mut variables = Vec::new();
152 let mut domains = Vec::new();
153 let mut constraints = Vec::new();
154 let mut objective = None;
155
156 while !parser.is_eof() {
158 match parser.peek_kind() {
159 Some(TokenKind::Ident(kw)) => match kw.as_str() {
160 "variables" => {
161 variables = parser.parse_variables_block()?;
162 }
163 "domains" => {
164 domains = parser.parse_domains_block()?;
165 }
166 "constraints" => {
167 constraints = parser.parse_constraints_block()?;
168 }
169 "minimize" => {
170 parser.advance();
171 let expr = parser.parse_expr()?;
172 objective = Some(Objective {
173 direction: OptDirection::Minimize,
174 expr,
175 });
176 }
177 "maximize" => {
178 parser.advance();
179 let expr = parser.parse_expr()?;
180 objective = Some(Objective {
181 direction: OptDirection::Maximize,
182 expr,
183 });
184 }
185 other => {
186 return Err(ParseError::new(
187 format!("unexpected keyword '{other}', expected section or objective"),
188 parser.current_span(),
189 ));
190 }
191 },
192 Some(kind) => {
193 return Err(ParseError::new(
194 format!("unexpected token {kind:?}"),
195 parser.current_span(),
196 ));
197 }
198 None => break,
199 }
200 }
201
202 Ok(JiaModel {
203 model_type,
204 name,
205 variables,
206 domains,
207 constraints,
208 objective,
209 })
210}
211
212pub fn parse_model_str(input: &str) -> Result<JiaModel, ParseError> {
217 let tokens = crate::jia_lang::lexer::tokenize(input)?;
218 parse_model(&tokens)
219}
220
221pub fn parse_expr_str(input: &str) -> Result<Expr, ParseError> {
223 let tokens = crate::jia_lang::lexer::tokenize(input)?;
224 let mut parser = Parser::new(&tokens);
225 parser.parse_expr()
226}
227
228#[cfg(test)]
229mod tests {
230 use super::*;
231 use crate::jia_lang::ast::*;
232
233 #[test]
234 fn test_minimal_model() {
235 let model = parse_model_str("model test").unwrap();
236 assert_eq!(model.name, "test");
237 assert!(model.variables.is_empty());
238 assert!(model.domains.is_empty());
239 assert!(model.constraints.is_empty());
240 assert!(model.objective.is_none());
241 }
242
243 #[test]
244 fn test_model_with_objective() {
245 let model = parse_model_str(
246 "model test\nvariables { Integer: x }\ndomains { x in 0..10 }\nminimize x",
247 )
248 .unwrap();
249 assert_eq!(model.name, "test");
250 assert!(model.objective.is_some());
251 let obj = model.objective.unwrap();
252 assert_eq!(obj.direction, OptDirection::Minimize);
253 assert_eq!(obj.expr, Expr::Var("x".to_string()));
254 }
255
256 #[test]
257 fn test_job_shop_model() {
258 let input = r#"
259model job_shop
260
261variables {
262 Interval: j1_op1, j1_op2
263 Integer: makespan
264 Set[Interval]: machine1
265}
266
267domains {
268 duration(j1_op1) = 3
269 duration(j1_op2) = 2
270 makespan in 0..100
271 machine1 = {j1_op1, j1_op2}
272}
273
274constraints {
275 end_of(j1_op1) <= start_of(j1_op2)
276 no_overlap(machine1)
277 end_of(j1_op2) <= makespan
278}
279
280minimize makespan
281"#;
282 let model = parse_model_str(input).unwrap();
283 assert_eq!(model.name, "job_shop");
284 assert_eq!(model.variables.len(), 3);
285 assert_eq!(model.constraints.len(), 3);
286 assert!(model.objective.is_some());
287 }
288
289 #[test]
290 fn test_unknown_section() {
291 let err = parse_model_str("model test\nfoobar { }").unwrap_err();
292 assert!(err.message.contains("unexpected keyword"));
293 }
294
295 #[test]
296 fn test_model_tag_lp() {
297 let model = parse_model_str("@model lp\nmodel test").unwrap();
298 assert_eq!(model.model_type, Some(ModelType::Lp));
299 assert_eq!(model.name, "test");
300 }
301
302 #[test]
303 fn test_model_tag_cp() {
304 let model = parse_model_str("@model cp\nmodel test").unwrap();
305 assert_eq!(model.model_type, Some(ModelType::Cp));
306 }
307
308 #[test]
309 fn test_model_no_tag() {
310 let model = parse_model_str("model test").unwrap();
311 assert_eq!(model.model_type, None);
312 }
313
314 #[test]
315 fn test_empty_token_stream_errors_cleanly() {
316 let tokens = Vec::new();
317 assert!(parse_model(&tokens).is_err());
318 }
319
320 #[test]
321 fn test_model_tag_unknown() {
322 let err = parse_model_str("@model foo\nmodel test").unwrap_err();
323 assert!(err.message.contains("unknown model type"));
324 }
325
326 #[test]
327 fn test_real_variables() {
328 let model = parse_model_str("@model lp\nmodel test\nvariables { Real: x, y }").unwrap();
329 assert_eq!(model.variables.len(), 1);
330 assert_eq!(model.variables[0].var_type, VarType::Real);
331 assert_eq!(model.variables[0].names, vec!["x", "y"]);
332 }
333
334 #[test]
335 fn test_float_in_expression() {
336 let expr = parse_expr_str("3.25 + x").unwrap();
337 assert_eq!(
338 expr,
339 Expr::BinaryOp {
340 op: ArithOp::Add,
341 left: Box::new(Expr::Float(3.25)),
342 right: Box::new(Expr::Var("x".to_string())),
343 }
344 );
345 }
346
347 #[test]
348 fn test_division_in_expression() {
349 let expr = parse_expr_str("x / 2").unwrap();
350 assert!(matches!(
351 expr,
352 Expr::BinaryOp {
353 op: ArithOp::Div,
354 ..
355 }
356 ));
357 }
358
359 #[test]
360 fn test_real_domain_with_inf() {
361 let model = parse_model_str(
362 "@model lp\nmodel test\nvariables { Real: x }\ndomains { x in 0..inf }",
363 )
364 .unwrap();
365 assert_eq!(model.domains.len(), 1);
366 }
367
368 #[test]
369 fn test_full_lp_model() {
370 let input = r#"
371@model lp
372model simple
373
374variables {
375 Real: x, y
376}
377
378domains {
379 x in 0..inf
380 y in 0..inf
381}
382
383constraints {
384 x + y >= 10
385 x <= 8
386}
387
388minimize 2 * x + 3 * y
389"#;
390 let model = parse_model_str(input).unwrap();
391 assert_eq!(model.model_type, Some(ModelType::Lp));
392 assert_eq!(model.name, "simple");
393 assert_eq!(model.variables.len(), 1);
394 assert_eq!(model.variables[0].var_type, VarType::Real);
395 assert_eq!(model.constraints.len(), 2);
396 assert!(model.objective.is_some());
397 }
398
399 #[test]
400 fn test_equality_with_single_eq() {
401 let model = parse_model_str(
403 "model test\nvariables { Integer: x }\ndomains { x in 0..10 }\nconstraints { x = 5 }",
404 )
405 .unwrap();
406 assert_eq!(model.constraints.len(), 1);
407 assert!(matches!(
408 &model.constraints[0],
409 Constraint::Comparison { op, .. } if *op == CmpOp::Eq
410 ));
411 }
412
413 #[test]
414 fn parser_expect_helpers_report_errors() {
415 let tokens = crate::jia_lang::lexer::tokenize("name").unwrap();
416 let mut parser = Parser::new(&tokens);
417 assert!(parser.expect_token(TokenKind::LBrace).is_err());
418
419 let tokens = Vec::new();
420 let mut parser = Parser::new(&tokens);
421 assert!(parser.expect_token(TokenKind::LBrace).is_err());
422 assert!(parser.expect_ident().is_err());
423 assert!(parser.expect_number().is_err());
424
425 let tokens = crate::jia_lang::lexer::tokenize("123").unwrap();
426 let mut parser = Parser::new(&tokens);
427 assert!(parser.expect_ident().is_err());
428
429 let tokens = crate::jia_lang::lexer::tokenize("actual").unwrap();
430 let mut parser = Parser::new(&tokens);
431 assert!(parser.expect_ident_matching("expected").is_err());
432
433 let tokens = crate::jia_lang::lexer::tokenize("name").unwrap();
434 let mut parser = Parser::new(&tokens);
435 assert!(parser.expect_number().is_err());
436 }
437
438 #[test]
439 fn parse_model_reports_unexpected_non_identifier_token() {
440 assert!(parse_model_str("model test\n{").is_err());
441 }
442}