1use crate::ast::{BinOp, Expr, Step};
8use crate::comment::CommentKind;
9use crate::lexer::Lx;
10use crate::value::Value;
11use logos::Logos;
12
13#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
15#[error("{msg} (at offset {pos})")]
16pub struct ParseError {
17 pub msg: String,
18 pub pos: usize,
19}
20
21struct Tok {
22 kind: Lx,
23 text: String,
24 start: usize,
25}
26
27pub fn parse(src: &str) -> Result<Expr, ParseError> {
29 let toks = lex(src)?;
30 let mut p = Parser { toks, pos: 0 };
31 let e = p.parse_pipe()?;
32 if p.pos != p.toks.len() {
33 let t = &p.toks[p.pos];
34 return Err(ParseError {
35 msg: format!("unexpected trailing token `{}`", t.text),
36 pos: t.start,
37 });
38 }
39 Ok(e)
40}
41
42fn lex(src: &str) -> Result<Vec<Tok>, ParseError> {
43 let mut lx = Lx::lexer(src);
44 let mut out = Vec::new();
45 while let Some(res) = lx.next() {
46 let span = lx.span();
47 match res {
48 Ok(kind) => out.push(Tok {
49 kind,
50 text: lx.slice().to_string(),
51 start: span.start,
52 }),
53 Err(_) => {
54 return Err(ParseError {
55 msg: format!("unexpected character `{}`", lx.slice()),
56 pos: span.start,
57 });
58 }
59 }
60 }
61 Ok(out)
62}
63
64struct Parser {
65 toks: Vec<Tok>,
66 pos: usize,
67}
68
69impl Parser {
70 fn peek(&self) -> Option<Lx> {
71 self.toks.get(self.pos).map(|t| t.kind)
72 }
73 fn text(&self) -> &str {
74 self.toks
75 .get(self.pos)
76 .map(|t| t.text.as_str())
77 .unwrap_or("")
78 }
79 fn at_end(&self) -> usize {
80 self.toks
81 .last()
82 .map(|t| t.start + t.text.len())
83 .unwrap_or(0)
84 }
85 fn err_here(&self, msg: impl Into<String>) -> ParseError {
86 let pos = self
87 .toks
88 .get(self.pos)
89 .map(|t| t.start)
90 .unwrap_or_else(|| self.at_end());
91 ParseError {
92 msg: msg.into(),
93 pos,
94 }
95 }
96 fn expect(&mut self, kind: Lx, what: &str) -> Result<(), ParseError> {
97 if self.peek() == Some(kind) {
98 self.pos += 1;
99 Ok(())
100 } else {
101 Err(self.err_here(format!("expected {what}")))
102 }
103 }
104
105 fn parse_pipe(&mut self) -> Result<Expr, ParseError> {
106 let mut left = self.parse_comma()?;
107 while self.peek() == Some(Lx::Pipe) {
108 self.pos += 1;
109 let right = self.parse_comma()?;
110 left = Expr::Pipe(Box::new(left), Box::new(right));
111 }
112 Ok(left)
113 }
114
115 fn parse_comma(&mut self) -> Result<Expr, ParseError> {
116 let first = self.parse_assign()?;
117 if self.peek() != Some(Lx::Comma) {
118 return Ok(first);
119 }
120 let mut items = vec![first];
121 while self.peek() == Some(Lx::Comma) {
122 self.pos += 1;
123 items.push(self.parse_assign()?);
124 }
125 Ok(Expr::Comma(items))
126 }
127
128 fn parse_assign(&mut self) -> Result<Expr, ParseError> {
129 let lhs = self.parse_alt()?;
130 match self.peek() {
131 Some(Lx::Assign) => {
132 self.reject_hyphen_key_lhs(&lhs)?;
133 self.pos += 1;
134 let rhs = self.parse_assign()?; Ok(Expr::Assign(Box::new(lhs), Box::new(rhs)))
136 }
137 Some(Lx::PipeAssign) => {
138 self.reject_hyphen_key_lhs(&lhs)?;
139 self.pos += 1;
140 let rhs = self.parse_assign()?;
141 Ok(Expr::UpdateAssign(Box::new(lhs), Box::new(rhs)))
142 }
143 Some(Lx::PlusAssign) => {
144 self.reject_hyphen_key_lhs(&lhs)?;
145 self.pos += 1;
146 let rhs = self.parse_assign()?;
147 Ok(Expr::AddAssign(Box::new(lhs), Box::new(rhs)))
148 }
149 _ => Ok(lhs),
150 }
151 }
152
153 fn reject_hyphen_key_lhs(&self, lhs: &Expr) -> Result<(), ParseError> {
160 let Some((path, key)) = hyphen_key(lhs) else {
161 return Ok(());
162 };
163 Err(self.err_here(format!(
164 "key `{key}` contains `-` (parsed as subtraction); quote it: {path}"
165 )))
166 }
167
168 fn parse_alt(&mut self) -> Result<Expr, ParseError> {
171 let left = self.parse_cmp()?;
172 if self.peek() == Some(Lx::Alt) {
173 self.pos += 1;
174 let right = self.parse_alt()?;
175 return Ok(Expr::Alternative(Box::new(left), Box::new(right)));
176 }
177 Ok(left)
178 }
179
180 fn parse_cmp(&mut self) -> Result<Expr, ParseError> {
181 let left = self.parse_add()?;
182 let op = match self.peek() {
183 Some(Lx::EqEq) => BinOp::Eq,
184 Some(Lx::Ne) => BinOp::Ne,
185 Some(Lx::Lt) => BinOp::Lt,
186 Some(Lx::Gt) => BinOp::Gt,
187 Some(Lx::Le) => BinOp::Le,
188 Some(Lx::Ge) => BinOp::Ge,
189 _ => return Ok(left),
190 };
191 self.pos += 1;
192 let right = self.parse_add()?;
193 Ok(Expr::Binary(op, Box::new(left), Box::new(right)))
194 }
195
196 fn parse_add(&mut self) -> Result<Expr, ParseError> {
197 let mut left = self.parse_mul()?;
198 loop {
199 let op = match self.peek() {
200 Some(Lx::Plus) => BinOp::Add,
201 Some(Lx::Minus) => BinOp::Sub,
202 _ => break,
203 };
204 self.pos += 1;
205 let right = self.parse_mul()?;
206 left = Expr::Binary(op, Box::new(left), Box::new(right));
207 }
208 Ok(left)
209 }
210
211 fn parse_mul(&mut self) -> Result<Expr, ParseError> {
212 let mut left = self.parse_unary()?;
213 loop {
214 let op = match self.peek() {
215 Some(Lx::Star) => BinOp::Mul,
216 Some(Lx::Slash) => BinOp::Div,
217 Some(Lx::Percent) => BinOp::Mod,
218 _ => break,
219 };
220 self.pos += 1;
221 let right = self.parse_unary()?;
222 left = Expr::Binary(op, Box::new(left), Box::new(right));
223 }
224 Ok(left)
225 }
226
227 fn parse_unary(&mut self) -> Result<Expr, ParseError> {
228 if self.peek() == Some(Lx::Minus) {
229 self.pos += 1;
230 return Ok(Expr::Neg(Box::new(self.parse_unary()?)));
231 }
232 self.parse_primary()
233 }
234
235 fn parse_primary(&mut self) -> Result<Expr, ParseError> {
236 match self.peek() {
237 Some(Lx::Dot) => self.parse_path(),
238 Some(Lx::LParen) => {
239 self.pos += 1;
240 let e = self.parse_pipe()?;
241 self.expect(Lx::RParen, "`)`")?;
242 Ok(e)
243 }
244 Some(Lx::LBrack) => {
245 self.pos += 1;
246 if self.peek() == Some(Lx::RBrack) {
247 self.pos += 1;
248 return Ok(Expr::Collect(None));
249 }
250 let inner = self.parse_pipe()?;
251 self.expect(Lx::RBrack, "`]`")?;
252 Ok(Expr::Collect(Some(Box::new(inner))))
253 }
254 Some(Lx::LBrace) => self.parse_object_construct(),
255 Some(Lx::Num) => {
256 let v = number_value(self.text());
257 self.pos += 1;
258 Ok(Expr::Literal(v))
259 }
260 Some(Lx::Str) => {
261 let v = Value::Str(unescape(self.text()));
262 self.pos += 1;
263 Ok(Expr::Literal(v))
264 }
265 Some(Lx::Ident) => self.parse_ident(),
266 _ => Err(self.err_here("expected an expression")),
267 }
268 }
269
270 fn parse_object_construct(&mut self) -> Result<Expr, ParseError> {
271 self.pos += 1; let mut pairs = Vec::new();
273 if self.peek() == Some(Lx::RBrace) {
274 self.pos += 1;
275 return Ok(Expr::ObjectConstruct(pairs));
276 }
277 loop {
278 let key = match self.peek() {
279 Some(Lx::Ident) => self.text().to_string(),
280 Some(Lx::Str) => unescape(self.text()),
281 _ => return Err(self.err_here("expected an object key")),
282 };
283 self.pos += 1;
284 match self.peek() {
289 Some(Lx::Colon) | Some(Lx::Assign) => self.pos += 1,
290 _ => return Err(self.err_here("expected `:` or `=`")),
291 }
292 let value = self.parse_cmp()?;
294 pairs.push((key, value));
295 match self.peek() {
296 Some(Lx::Comma) => self.pos += 1,
297 Some(Lx::RBrace) => {
298 self.pos += 1;
299 break;
300 }
301 _ => return Err(self.err_here("expected `,` or `}`")),
302 }
303 }
304 Ok(Expr::ObjectConstruct(pairs))
305 }
306
307 fn parse_path(&mut self) -> Result<Expr, ParseError> {
308 self.pos += 1; let mut steps = Vec::new();
310 loop {
311 match self.peek() {
312 Some(Lx::Ident) => {
313 steps.push(Step::Field(self.text().to_string()));
314 self.pos += 1;
315 }
316 Some(Lx::Str) => {
317 steps.push(Step::Field(unescape(self.text())));
318 self.pos += 1;
319 }
320 Some(Lx::LBrack) => {
321 self.pos += 1;
322 if self.peek() == Some(Lx::RBrack) {
323 self.pos += 1;
324 steps.push(Step::Iterate);
325 } else if self.peek() == Some(Lx::Str) {
326 let key = unescape(self.text());
328 self.pos += 1;
329 self.expect(Lx::RBrack, "`]`")?;
330 steps.push(Step::Field(key));
331 } else {
332 let neg = self.peek() == Some(Lx::Minus);
333 if neg {
334 self.pos += 1;
335 }
336 if self.peek() != Some(Lx::Num) {
337 return Err(self.err_here("expected an array index or a string key"));
338 }
339 let n = parse_i64(self.text())
340 .map_err(|_| self.err_here("array index out of range"))?;
341 self.pos += 1;
342 self.expect(Lx::RBrack, "`]`")?;
343 steps.push(Step::Index(if neg { -n } else { n }));
344 }
345 }
346 Some(Lx::Hash) => {
347 self.pos += 1;
351 let mut kind = CommentKind::Head;
352 if self.peek() == Some(Lx::Dot)
353 && let Some(word) =
354 self.toks.get(self.pos + 1).filter(|t| t.kind == Lx::Ident)
355 && let Some(k) = comment_kind(&word.text)
356 {
357 self.pos += 2; kind = k;
359 }
360 steps.push(Step::Comment(kind));
361 break;
362 }
363 _ => break,
364 }
365 match self.peek() {
366 Some(Lx::Dot) => {
367 self.pos += 1;
368 continue;
369 }
370 Some(Lx::LBrack) => continue,
371 _ => break,
372 }
373 }
374 Ok(Expr::Path(steps))
375 }
376
377 fn parse_ident(&mut self) -> Result<Expr, ParseError> {
378 let name = self.text().to_string();
379 self.pos += 1;
380 match name.as_str() {
381 "true" => return Ok(Expr::Literal(Value::Bool(true))),
382 "false" => return Ok(Expr::Literal(Value::Bool(false))),
383 "null" => return Ok(Expr::Literal(Value::Null)),
384 _ => {}
385 }
386 if self.peek() == Some(Lx::LParen) {
387 self.pos += 1;
388 let mut args = vec![self.parse_pipe()?];
389 while self.peek() == Some(Lx::Semi) {
390 self.pos += 1;
391 args.push(self.parse_pipe()?);
392 }
393 self.expect(Lx::RParen, "`)`")?;
394 Ok(Expr::Call(name, args))
395 } else {
396 Ok(Expr::Call(name, Vec::new()))
397 }
398 }
399}
400
401fn hyphen_key(expr: &Expr) -> Option<(String, String)> {
409 let mut tail: Vec<&str> = Vec::new();
410 let mut cur = expr;
411 while let Expr::Binary(BinOp::Sub, l, r) = cur {
412 match r.as_ref() {
413 Expr::Call(name, args) if args.is_empty() => tail.push(name.as_str()),
414 _ => return None,
415 }
416 cur = l;
417 }
418 if tail.is_empty() {
419 return None;
420 }
421 let Expr::Path(steps) = cur else {
422 return None;
423 };
424 let Some((Step::Field(first), prefix)) = steps.split_last() else {
425 return None;
426 };
427 tail.push(first.as_str());
428 tail.reverse();
429 let key = tail.join("-");
430 let mut path = String::new();
431 for step in prefix {
432 match step {
433 Step::Field(f) if is_bare_key(f) => {
434 path.push('.');
435 path.push_str(f);
436 }
437 Step::Field(f) => {
438 path.push_str(&format!(".\"{f}\""));
439 }
440 Step::Index(i) => path.push_str(&format!("[{i}]")),
441 _ => return None,
442 }
443 }
444 path.push_str(&format!(".\"{key}\""));
445 Some((path, key))
446}
447
448fn is_bare_key(k: &str) -> bool {
450 let mut chars = k.chars();
451 chars
452 .next()
453 .is_some_and(|c| c.is_ascii_alphabetic() || c == '_')
454 && chars.all(|c| c.is_ascii_alphanumeric() || c == '_')
455}
456
457fn comment_kind(word: &str) -> Option<CommentKind> {
458 match word {
459 "head" => Some(CommentKind::Head),
460 "inline" => Some(CommentKind::Inline),
461 "foot" => Some(CommentKind::Foot),
462 _ => None,
463 }
464}
465
466fn number_value(t: &str) -> Value {
467 if t.contains(['.', 'e', 'E']) {
468 Value::Float(t.parse().unwrap_or(0.0))
469 } else {
470 match t.parse::<i64>() {
471 Ok(i) => Value::Int(i),
472 Err(_) => Value::Float(t.parse().unwrap_or(0.0)),
473 }
474 }
475}
476
477fn parse_i64(t: &str) -> Result<i64, std::num::ParseIntError> {
478 t.parse::<i64>()
479}
480
481fn unescape(tok: &str) -> String {
483 let inner = tok
484 .strip_prefix('"')
485 .and_then(|s| s.strip_suffix('"'))
486 .unwrap_or(tok);
487 let mut out = String::with_capacity(inner.len());
488 let mut chars = inner.chars();
489 while let Some(c) = chars.next() {
490 if c != '\\' {
491 out.push(c);
492 continue;
493 }
494 match chars.next() {
495 Some('"') => out.push('"'),
496 Some('\\') => out.push('\\'),
497 Some('/') => out.push('/'),
498 Some('n') => out.push('\n'),
499 Some('r') => out.push('\r'),
500 Some('t') => out.push('\t'),
501 Some('b') => out.push('\u{0008}'),
502 Some('f') => out.push('\u{000c}'),
503 Some('u') => {
504 let hex: String = chars.by_ref().take(4).collect();
505 if let Some(ch) = u32::from_str_radix(&hex, 16).ok().and_then(char::from_u32) {
506 out.push(ch);
507 }
508 }
509 Some(other) => {
510 out.push('\\');
511 out.push(other);
512 }
513 None => out.push('\\'),
514 }
515 }
516 out
517}
518
519#[cfg(test)]
520mod tests {
521 use super::*;
522
523 fn p(s: &str) -> Expr {
524 parse(s).unwrap_or_else(|e| panic!("parse `{s}`: {e}"))
525 }
526
527 #[test]
528 fn identity() {
529 assert_eq!(p("."), Expr::Path(vec![]));
530 }
531
532 #[test]
533 fn dotted_path() {
534 assert_eq!(
535 p(".a.b"),
536 Expr::Path(vec![Step::Field("a".into()), Step::Field("b".into())])
537 );
538 }
539
540 #[test]
541 fn index_and_iterate() {
542 assert_eq!(
543 p(".arr[0][]"),
544 Expr::Path(vec![
545 Step::Field("arr".into()),
546 Step::Index(0),
547 Step::Iterate
548 ])
549 );
550 assert_eq!(
551 p(".x[-1]"),
552 Expr::Path(vec![Step::Field("x".into()), Step::Index(-1)])
553 );
554 }
555
556 #[test]
557 fn quoted_field() {
558 assert_eq!(
559 p(r#"."weird key""#),
560 Expr::Path(vec![Step::Field("weird key".into())])
561 );
562 }
563
564 #[test]
565 fn literals() {
566 assert_eq!(p("true"), Expr::Literal(Value::Bool(true)));
567 assert_eq!(p("null"), Expr::Literal(Value::Null));
568 assert_eq!(p("42"), Expr::Literal(Value::Int(42)));
569 assert_eq!(p("1.5"), Expr::Literal(Value::Float(1.5)));
570 assert_eq!(p(r#""hi""#), Expr::Literal(Value::Str("hi".into())));
571 }
572
573 #[test]
574 fn arithmetic_precedence() {
575 assert_eq!(
577 p("1 + 2 * 3"),
578 Expr::Binary(
579 BinOp::Add,
580 Box::new(Expr::Literal(Value::Int(1))),
581 Box::new(Expr::Binary(
582 BinOp::Mul,
583 Box::new(Expr::Literal(Value::Int(2))),
584 Box::new(Expr::Literal(Value::Int(3))),
585 )),
586 )
587 );
588 }
589
590 #[test]
591 fn pipe_and_select() {
592 let e = p(r#".items[] | select(.name == "x")"#);
593 match e {
594 Expr::Pipe(l, r) => {
595 assert_eq!(
596 *l,
597 Expr::Path(vec![Step::Field("items".into()), Step::Iterate])
598 );
599 match *r {
600 Expr::Call(ref name, ref args) => {
601 assert_eq!(name, "select");
602 assert_eq!(args.len(), 1);
603 }
604 _ => panic!("expected select call"),
605 }
606 }
607 _ => panic!("expected pipe"),
608 }
609 }
610
611 #[test]
612 fn errors() {
613 assert!(parse(".a.").is_ok()); assert!(parse("(").is_err());
615 assert!(parse(".a b").is_err()); assert!(parse("@").is_err()); }
618
619 #[test]
620 fn comment_accessor() {
621 use crate::comment::CommentKind;
622 assert_eq!(
624 p(".foo.#"),
625 Expr::Path(vec![
626 Step::Field("foo".into()),
627 Step::Comment(CommentKind::Head)
628 ])
629 );
630 assert_eq!(
632 p(".foo.#.inline"),
633 Expr::Path(vec![
634 Step::Field("foo".into()),
635 Step::Comment(CommentKind::Inline)
636 ])
637 );
638 assert_eq!(
639 p(".a.#.foot"),
640 Expr::Path(vec![
641 Step::Field("a".into()),
642 Step::Comment(CommentKind::Foot)
643 ])
644 );
645 assert_eq!(p(".#"), Expr::Path(vec![Step::Comment(CommentKind::Head)]));
647 assert_eq!(
649 p(".items[].#"),
650 Expr::Path(vec![
651 Step::Field("items".into()),
652 Step::Iterate,
653 Step::Comment(CommentKind::Head)
654 ])
655 );
656 assert!(parse(".foo.#.bar").is_err());
658 }
659
660 #[test]
661 fn object_construct_accepts_toml_equals() {
662 assert_eq!(
665 p(r#"{version = "1", optional: true}"#),
666 Expr::ObjectConstruct(vec![
667 ("version".into(), Expr::Literal(Value::Str("1".into()))),
668 ("optional".into(), Expr::Literal(Value::Bool(true))),
669 ])
670 );
671 }
672
673 #[test]
674 fn object_construct_names_both_separators() {
675 let e = parse("{a 1}").unwrap_err();
676 assert!(e.to_string().contains("expected `:` or `=`"), "got: {e}");
677 }
678
679 #[test]
680 fn hyphenated_assign_lhs_hints_the_quoted_form() {
681 let e = parse(r#".package.rust-version = "1.85""#).unwrap_err();
682 assert_eq!(
683 e.to_string(),
684 "key `rust-version` contains `-` (parsed as subtraction); \
685 quote it: .package.\"rust-version\" (at offset 22)"
686 );
687 let e = parse(".lib.crate-type-x |= 1").unwrap_err();
689 assert!(e.to_string().contains("`crate-type-x`"), "got: {e}");
690 assert!(e.to_string().contains(".lib.\"crate-type-x\""), "got: {e}");
691 let e = parse(".a.b-c += 1").unwrap_err();
692 assert!(e.to_string().contains("`b-c`"), "got: {e}");
693 let e = parse(".rust-version = 1").unwrap_err();
695 assert!(e.to_string().contains(".\"rust-version\""), "got: {e}");
696 }
697
698 #[test]
699 fn hyphen_hint_leaves_real_subtraction_alone() {
700 assert_eq!(
702 p(".a-b"),
703 Expr::Binary(
704 BinOp::Sub,
705 Box::new(Expr::Path(vec![Step::Field("a".into())])),
706 Box::new(Expr::Call("b".into(), vec![]))
707 )
708 );
709 assert!(parse(".a - 1 = 2").is_ok());
713 p(".x = .a - .b");
715 }
716}