1use std::cmp::Ordering;
4use std::fmt;
5
6use serde_json::Value;
7
8use crate::node::Node;
9
10#[derive(Debug, Clone, PartialEq)]
155pub struct Query {
156 expr: Expr,
157}
158
159#[derive(Debug, Clone, PartialEq, Eq)]
161pub struct QueryError {
162 pub offset: usize,
164 pub kind: QueryErrorKind,
166}
167
168#[derive(Debug, Clone, PartialEq, Eq)]
170#[non_exhaustive]
171pub enum QueryErrorKind {
172 Empty,
174 UnexpectedEnd {
176 expected: &'static str,
178 },
179 UnexpectedToken {
181 found: String,
183 expected: &'static str,
185 },
186 UnterminatedString,
188 LoneOperator(char),
190 UnknownField(String),
192 ExpectedNumber,
194 InvalidOperator,
196 EmptyPath,
198}
199
200impl fmt::Display for QueryError {
201 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
202 match &self.kind {
203 QueryErrorKind::Empty => write!(f, "empty query"),
204 QueryErrorKind::UnexpectedEnd { expected } => {
205 write!(f, "expected {expected} at end of query")
206 }
207 QueryErrorKind::UnexpectedToken { found, expected } => {
208 write!(f, "expected {expected}, found `{found}`")
209 }
210 QueryErrorKind::UnterminatedString => write!(f, "unterminated string"),
211 QueryErrorKind::LoneOperator(c) => write!(f, "expected `{c}{c}`"),
212 QueryErrorKind::UnknownField(w) => {
213 write!(f, "unknown field `{w}` (try attrs.{w})")
214 }
215 QueryErrorKind::ExpectedNumber => write!(f, "expected a number"),
216 QueryErrorKind::InvalidOperator => write!(f, "operator not valid here"),
217 QueryErrorKind::EmptyPath => write!(f, "empty path segment"),
218 }?;
219 write!(f, " (at byte {})", self.offset)
220 }
221}
222
223impl std::error::Error for QueryError {}
224
225#[derive(Debug, Clone, Copy, PartialEq, Eq)]
228enum Op {
229 Eq,
230 Ne,
231 Lt,
232 Le,
233 Gt,
234 Ge,
235 Tilde,
236}
237
238impl Op {
239 #[inline]
240 fn ord(self, o: Ordering) -> bool {
241 match self {
242 Op::Eq => o == Ordering::Equal,
243 Op::Ne => o != Ordering::Equal,
244 Op::Lt => o == Ordering::Less,
245 Op::Le => o != Ordering::Greater,
246 Op::Gt => o == Ordering::Greater,
247 Op::Ge => o != Ordering::Less,
248 Op::Tilde => false,
249 }
250 }
251}
252
253#[derive(Debug, Clone, PartialEq)]
254enum LitKind {
255 Num(f64),
256 Bool(bool),
257 Null,
258 Str,
259}
260
261#[derive(Debug, Clone, PartialEq)]
262struct Lit {
263 raw: Box<str>,
264 lower: Box<str>,
265 kind: LitKind,
266}
267
268impl Lit {
269 fn new(raw: String, quoted: bool) -> Self {
270 let kind = if quoted {
271 LitKind::Str
272 } else {
273 match raw.as_str() {
274 "true" => LitKind::Bool(true),
275 "false" => LitKind::Bool(false),
276 "null" => LitKind::Null,
277 s => s.parse::<f64>().map(LitKind::Num).unwrap_or(LitKind::Str),
278 }
279 };
280 Lit {
281 lower: raw.to_lowercase().into_boxed_str(),
282 raw: raw.into_boxed_str(),
283 kind,
284 }
285 }
286}
287
288#[derive(Debug, Clone, PartialEq)]
289struct Seg {
290 key: Box<str>,
291 idx: Option<usize>,
292}
293
294type Cmp = Option<(Op, Lit)>;
295
296#[derive(Debug, Clone, PartialEq)]
297enum Pred {
298 Word(Box<str>),
299 Type(Op, Lit),
300 Text(Cmp),
301 AnyAttrs,
302 Attr(Vec<Seg>, Cmp),
303 AnyMarks,
304 Mark(Op, Lit),
305 MarkAttr(Box<str>, Vec<Seg>, Cmp),
306 Children(Option<(Op, f64)>),
307 Depth(Op, f64),
308}
309
310#[derive(Debug, Clone, PartialEq)]
311enum Expr {
312 And(Vec<Expr>),
313 Or(Vec<Expr>),
314 Not(Box<Expr>),
315 Pred(Pred),
316}
317
318impl Query {
321 pub fn parse(src: &str) -> Result<Query, QueryError> {
331 let tokens = lex(src)?;
332 if tokens.is_empty() {
333 return Err(QueryError {
334 offset: 0,
335 kind: QueryErrorKind::Empty,
336 });
337 }
338 let mut p = Parser {
339 tokens: &tokens,
340 pos: 0,
341 end: src.len(),
342 };
343 let expr = p.or()?;
344 if let Some(t) = p.peek() {
345 return Err(p.unexpected(t, "an operator or end of query"));
346 }
347 Ok(Query { expr })
348 }
349
350 #[inline]
359 pub fn matches(&self, node: &Node) -> bool {
360 self.expr.eval(node, 0)
361 }
362
363 #[inline]
372 pub fn matches_at(&self, node: &Node, depth: usize) -> bool {
373 self.expr.eval(node, depth)
374 }
375
376 pub fn find_paths(&self, root: &Node) -> Vec<Vec<usize>> {
388 let mut out = Vec::new();
389 let mut path = Vec::new();
390 self.collect(root, &mut path, &mut out);
391 out
392 }
393
394 fn collect(&self, node: &Node, path: &mut Vec<usize>, out: &mut Vec<Vec<usize>>) {
395 if self.expr.eval(node, path.len()) {
396 out.push(path.clone());
397 }
398 if let Some(children) = &node.content {
399 for (i, c) in children.iter().enumerate() {
400 path.push(i);
401 self.collect(c, path, out);
402 path.pop();
403 }
404 }
405 }
406}
407
408impl Expr {
409 fn eval(&self, n: &Node, depth: usize) -> bool {
410 match self {
411 Expr::And(xs) => xs.iter().all(|x| x.eval(n, depth)),
412 Expr::Or(xs) => xs.iter().any(|x| x.eval(n, depth)),
413 Expr::Not(x) => !x.eval(n, depth),
414 Expr::Pred(p) => p.eval(n, depth),
415 }
416 }
417}
418
419impl Pred {
420 fn eval(&self, n: &Node, depth: usize) -> bool {
421 match self {
422 Pred::Word(w) => {
423 n.node_type.as_deref().is_some_and(|t| contains_ci(t, w))
424 || n.text.as_deref().is_some_and(|t| contains_ci(t, w))
425 }
426 Pred::Type(op, lit) => opt_str(n.node_type.as_deref(), *op, lit),
427 Pred::Text(None) => n.text.is_some(),
428 Pred::Text(Some((op, lit))) => opt_str(n.text.as_deref(), *op, lit),
429 Pred::AnyAttrs => n.attrs.as_ref().is_some_and(|a| !a.is_empty()),
430 Pred::Attr(path, cmp) => {
431 let v = n.attrs.as_ref().and_then(|a| {
432 let (first, rest) = path.split_first()?;
433 resolve(a.get(&*first.key)?, rest)
434 });
435 opt_value(v, cmp)
436 }
437 Pred::AnyMarks => n.marks.as_ref().is_some_and(|m| !m.is_empty()),
438 Pred::Mark(op, lit) => {
439 let marks = n.marks.as_deref().unwrap_or(&[]);
440 if *op == Op::Ne {
441 !marks.iter().any(|m| cmp_str(&m.mark_type, Op::Eq, lit))
442 } else {
443 marks.iter().any(|m| cmp_str(&m.mark_type, *op, lit))
444 }
445 }
446 Pred::MarkAttr(name, path, cmp) => {
447 let mut marks = n
448 .marks
449 .as_deref()
450 .unwrap_or(&[])
451 .iter()
452 .filter(|m| *m.mark_type == **name);
453 if path.is_empty() {
454 return marks.next().is_some();
455 }
456 let hit = marks.any(|m| {
457 let v = m.attrs.as_ref().and_then(|a| {
458 let (first, rest) = path.split_first()?;
459 resolve(a.get(&*first.key)?, rest)
460 });
461 match cmp {
462 Some((Op::Ne, lit)) => v.is_some_and(|v| cmp_value(v, Op::Eq, lit)),
463 _ => opt_value(v, cmp),
464 }
465 });
466 if matches!(cmp, Some((Op::Ne, _))) {
468 !hit
469 } else {
470 hit
471 }
472 }
473 Pred::Children(None) => n.content.as_ref().is_some_and(|c| !c.is_empty()),
474 Pred::Children(Some((op, x))) => cmp_num(n.child_count() as f64, *op, *x),
475 Pred::Depth(op, x) => cmp_num(depth as f64, *op, *x),
476 }
477 }
478}
479
480fn resolve<'a>(mut v: &'a Value, rest: &[Seg]) -> Option<&'a Value> {
481 for seg in rest {
482 v = match v {
483 Value::Object(m) => m.get(&*seg.key)?,
484 Value::Array(a) => a.get(seg.idx?)?,
485 _ => return None,
486 };
487 }
488 Some(v)
489}
490
491#[inline]
492fn opt_value(v: Option<&Value>, cmp: &Cmp) -> bool {
493 match (v, cmp) {
494 (None, None) => false,
495 (Some(_), None) => true,
496 (None, Some((op, _))) => *op == Op::Ne,
497 (Some(v), Some((op, lit))) => cmp_value(v, *op, lit),
498 }
499}
500
501#[inline]
502fn opt_str(s: Option<&str>, op: Op, lit: &Lit) -> bool {
503 match s {
504 Some(s) => cmp_str(s, op, lit),
505 None => op == Op::Ne,
506 }
507}
508
509#[inline]
510fn cmp_num(a: f64, op: Op, b: f64) -> bool {
511 a.partial_cmp(&b).is_some_and(|o| op.ord(o))
512}
513
514#[inline]
515fn cmp_str(s: &str, op: Op, lit: &Lit) -> bool {
516 match op {
517 Op::Tilde => contains_ci(s, &lit.lower),
518 _ => op.ord(s.cmp(&lit.raw)),
519 }
520}
521
522fn cmp_value(v: &Value, op: Op, lit: &Lit) -> bool {
523 if op == Op::Ne {
524 return !cmp_value(v, Op::Eq, lit);
525 }
526 match v {
527 Value::Array(a) => a.iter().any(|x| cmp_value(x, op, lit)),
528 Value::String(s) => cmp_str(s, op, lit),
529 Value::Number(x) => match (&lit.kind, x.as_f64()) {
530 (LitKind::Num(b), Some(a)) if op != Op::Tilde => cmp_num(a, op, *b),
531 _ => false,
532 },
533 Value::Bool(b) => op == Op::Eq && lit.kind == LitKind::Bool(*b),
534 Value::Null => op == Op::Eq && lit.kind == LitKind::Null,
535 Value::Object(_) => false,
536 }
537}
538
539fn contains_ci(hay: &str, needle: &str) -> bool {
541 if needle.is_empty() {
542 return true;
543 }
544 if needle.is_ascii() {
545 let (h, n) = (hay.as_bytes(), needle.as_bytes());
546 if n.len() > h.len() {
547 return false;
548 }
549 let first = n[0];
550 return h
551 .windows(n.len())
552 .any(|w| w[0].to_ascii_lowercase() == first && w.eq_ignore_ascii_case(n));
553 }
554 hay.char_indices()
555 .any(|(i, _)| starts_with_ci(&hay[i..], needle))
556}
557
558fn starts_with_ci(hay: &str, needle: &str) -> bool {
559 let mut n = needle.chars();
560 for c in hay.chars().flat_map(char::to_lowercase) {
561 match n.next() {
562 None => return true,
563 Some(x) if x == c => {}
564 Some(_) => return false,
565 }
566 }
567 n.next().is_none()
568}
569
570#[derive(Debug, Clone, PartialEq)]
573enum Tok {
574 Word(String),
575 Str(String),
576 Op(Op),
577 And,
578 Or,
579 Not,
580 LParen,
581 RParen,
582}
583
584#[derive(Debug)]
585struct Token {
586 tok: Tok,
587 start: usize,
588}
589
590const SPECIAL: &[u8] = b"()&|!=<>~\"'";
591
592fn lex(src: &str) -> Result<Vec<Token>, QueryError> {
593 let b = src.as_bytes();
594 let mut out = Vec::new();
595 let mut i = 0;
596 while i < b.len() {
597 let c = b[i];
598 if c.is_ascii_whitespace() {
599 i += 1;
600 continue;
601 }
602 let start = i;
603 let next = b.get(i + 1).copied();
604 let (tok, len) = match c {
605 b'(' => (Tok::LParen, 1),
606 b')' => (Tok::RParen, 1),
607 b'&' | b'|' if next == Some(c) => (if c == b'&' { Tok::And } else { Tok::Or }, 2),
608 b'&' | b'|' => {
609 return Err(QueryError {
610 offset: start,
611 kind: QueryErrorKind::LoneOperator(c as char),
612 })
613 }
614 b'!' if next == Some(b'=') => (Tok::Op(Op::Ne), 2),
615 b'!' => (Tok::Not, 1),
616 b'=' if next == Some(b'=') => (Tok::Op(Op::Eq), 2),
617 b'=' => (Tok::Op(Op::Eq), 1),
618 b'<' if next == Some(b'=') => (Tok::Op(Op::Le), 2),
619 b'<' => (Tok::Op(Op::Lt), 1),
620 b'>' if next == Some(b'=') => (Tok::Op(Op::Ge), 2),
621 b'>' => (Tok::Op(Op::Gt), 1),
622 b'~' => (Tok::Op(Op::Tilde), 1),
623 b'"' | b'\'' => {
624 let mut s = String::new();
625 let mut j = i + 1;
626 let mut seg = j;
627 loop {
628 match b.get(j) {
629 None => {
630 return Err(QueryError {
631 offset: start,
632 kind: QueryErrorKind::UnterminatedString,
633 })
634 }
635 Some(&q) if q == c => break,
636 Some(b'\\') if j + 1 < b.len() => {
637 s.push_str(&src[seg..j]);
638 let ch = src[j + 1..].chars().next().unwrap_or('\\');
640 s.push(ch);
641 j += 1 + ch.len_utf8();
642 seg = j;
643 }
644 Some(_) => j += 1,
645 }
646 }
647 s.push_str(&src[seg..j]);
648 (Tok::Str(s), j + 1 - i)
649 }
650 _ => {
651 let mut j = i;
652 while j < b.len() && !b[j].is_ascii_whitespace() && !SPECIAL.contains(&b[j]) {
653 j += 1;
654 }
655 let w = &src[i..j];
656 let tok = if w.eq_ignore_ascii_case("and") {
657 Tok::And
658 } else if w.eq_ignore_ascii_case("or") {
659 Tok::Or
660 } else if w.eq_ignore_ascii_case("not") {
661 Tok::Not
662 } else {
663 Tok::Word(w.to_string())
664 };
665 (tok, j - i)
666 }
667 };
668 i += len;
669 out.push(Token { tok, start });
670 }
671 Ok(out)
672}
673
674struct Parser<'a> {
677 tokens: &'a [Token],
678 pos: usize,
679 end: usize,
680}
681
682impl<'a> Parser<'a> {
683 #[inline]
684 fn peek(&self) -> Option<&'a Token> {
685 self.tokens.get(self.pos)
686 }
687
688 fn bump(&mut self) -> Option<&'a Token> {
689 let t = self.tokens.get(self.pos);
690 self.pos += 1;
691 t
692 }
693
694 fn eof(&self, expected: &'static str) -> QueryError {
695 QueryError {
696 offset: self.end,
697 kind: QueryErrorKind::UnexpectedEnd { expected },
698 }
699 }
700
701 fn unexpected(&self, t: &Token, expected: &'static str) -> QueryError {
702 QueryError {
703 offset: t.start,
704 kind: QueryErrorKind::UnexpectedToken {
705 found: self.text(t),
706 expected,
707 },
708 }
709 }
710
711 fn text(&self, t: &Token) -> String {
712 match &t.tok {
713 Tok::Word(w) => w.clone(),
714 Tok::Str(s) => format!("\"{s}\""),
715 Tok::Op(op) => match op {
716 Op::Eq => "==",
717 Op::Ne => "!=",
718 Op::Lt => "<",
719 Op::Le => "<=",
720 Op::Gt => ">",
721 Op::Ge => ">=",
722 Op::Tilde => "~",
723 }
724 .into(),
725 Tok::And => "&&".into(),
726 Tok::Or => "||".into(),
727 Tok::Not => "!".into(),
728 Tok::LParen => "(".into(),
729 Tok::RParen => ")".into(),
730 }
731 }
732
733 fn or(&mut self) -> Result<Expr, QueryError> {
734 let mut xs = vec![self.and()?];
735 while matches!(self.peek(), Some(Token { tok: Tok::Or, .. })) {
736 self.pos += 1;
737 xs.push(self.and()?);
738 }
739 Ok(if xs.len() == 1 {
740 xs.pop().unwrap()
741 } else {
742 Expr::Or(xs)
743 })
744 }
745
746 fn and(&mut self) -> Result<Expr, QueryError> {
747 let mut xs = vec![self.unary()?];
748 loop {
749 match self.peek().map(|t| &t.tok) {
750 Some(Tok::And) => {
751 self.pos += 1;
752 xs.push(self.unary()?);
753 }
754 Some(Tok::Word(_) | Tok::Str(_) | Tok::Not | Tok::LParen) => xs.push(self.unary()?),
756 _ => break,
757 }
758 }
759 Ok(if xs.len() == 1 {
760 xs.pop().unwrap()
761 } else {
762 Expr::And(xs)
763 })
764 }
765
766 fn unary(&mut self) -> Result<Expr, QueryError> {
767 let Some(t) = self.bump() else {
768 return Err(self.eof("a term"));
769 };
770 match &t.tok {
771 Tok::Not => Ok(Expr::Not(Box::new(self.unary()?))),
772 Tok::LParen => {
773 let e = self.or()?;
774 match self.bump() {
775 Some(Token {
776 tok: Tok::RParen, ..
777 }) => Ok(e),
778 Some(t) => Err(self.unexpected(t, "`)`")),
779 None => Err(self.eof("`)`")),
780 }
781 }
782 Tok::Str(s) => Ok(Expr::Pred(Pred::Word(s.to_lowercase().into()))),
783 Tok::Word(w) => self.term(w, t).map(Expr::Pred),
784 _ => Err(self.unexpected(t, "a term")),
785 }
786 }
787
788 fn peek_op(&self) -> Option<Op> {
789 match self.peek() {
790 Some(Token {
791 tok: Tok::Op(op), ..
792 }) => Some(*op),
793 _ => None,
794 }
795 }
796
797 fn cmp(&mut self) -> Result<Cmp, QueryError> {
799 let Some(op) = self.peek_op() else {
800 return Ok(None);
801 };
802 self.pos += 1;
803 Ok(Some((op, self.value()?)))
804 }
805
806 fn cmp_req(&mut self) -> Result<(Op, Lit), QueryError> {
808 match self.cmp()? {
809 Some(c) => Ok(c),
810 None => Err(match self.peek() {
811 Some(t) => self.unexpected(t, "an operator"),
812 None => self.eof("an operator"),
813 }),
814 }
815 }
816
817 fn value(&mut self) -> Result<Lit, QueryError> {
818 match self.bump() {
819 Some(Token {
820 tok: Tok::Word(w), ..
821 }) => Ok(Lit::new(w.clone(), false)),
822 Some(Token {
823 tok: Tok::Str(s), ..
824 }) => Ok(Lit::new(s.clone(), true)),
825 Some(t) => Err(self.unexpected(t, "a value")),
826 None => Err(self.eof("a value")),
827 }
828 }
829
830 fn num_cmp(&mut self, required: bool) -> Result<Option<(Op, f64)>, QueryError> {
831 let op_tok = self.peek();
832 let Some(op) = self.peek_op() else {
833 if required {
834 return Err(match op_tok {
835 Some(t) => self.unexpected(t, "an operator"),
836 None => self.eof("an operator"),
837 });
838 }
839 return Ok(None);
840 };
841 let op_tok = op_tok.unwrap();
842 if op == Op::Tilde {
843 return Err(QueryError {
844 offset: op_tok.start,
845 kind: QueryErrorKind::InvalidOperator,
846 });
847 }
848 self.pos += 1;
849 let at = self.peek().map_or(self.end, |t| t.start);
850 let lit = self.value()?;
851 match lit.kind {
852 LitKind::Num(x) => Ok(Some((op, x))),
853 _ => Err(QueryError {
854 offset: at,
855 kind: QueryErrorKind::ExpectedNumber,
856 }),
857 }
858 }
859
860 fn path(&self, s: &str, offset: usize) -> Result<Vec<Seg>, QueryError> {
861 let mut segs = Vec::new();
862 let mut at = offset;
863 for part in s.split('.') {
864 if part.is_empty() {
865 return Err(QueryError {
866 offset: at,
867 kind: QueryErrorKind::EmptyPath,
868 });
869 }
870 segs.push(Seg {
871 key: part.into(),
872 idx: part.parse().ok(),
873 });
874 at += part.len() + 1;
875 }
876 Ok(segs)
877 }
878
879 fn term(&mut self, w: &str, t: &Token) -> Result<Pred, QueryError> {
880 match w {
881 "type" => {
882 let (op, lit) = self.cmp_req()?;
883 Ok(Pred::Type(op, lit))
884 }
885 "text" => Ok(Pred::Text(self.cmp()?)),
886 "mark" => {
887 let (op, lit) = self.cmp_req()?;
888 Ok(Pred::Mark(op, lit))
889 }
890 "children" => Ok(Pred::Children(self.num_cmp(false)?)),
891 "depth" => {
892 let (op, x) = self.num_cmp(true)?.expect("required");
893 Ok(Pred::Depth(op, x))
894 }
895 "attrs" if self.peek_op().is_none() => Ok(Pred::AnyAttrs),
896 "marks" if self.peek_op().is_none() => Ok(Pred::AnyMarks),
897 "has" => {
898 let what = match self.bump() {
899 Some(Token {
900 tok: Tok::Word(k), ..
901 }) if k == "mark" || k == "attr" => k.as_str(),
902 Some(t) => return Err(self.unexpected(t, "`mark` or `attr`")),
903 None => return Err(self.eof("`mark` or `attr`")),
904 };
905 if what == "mark" {
906 Ok(Pred::Mark(Op::Eq, self.value()?))
907 } else {
908 match self.bump() {
909 Some(
910 t @ Token {
911 tok: Tok::Word(k), ..
912 },
913 ) => Ok(Pred::Attr(self.path(k, t.start)?, None)),
914 Some(t) => Err(self.unexpected(t, "an attr name")),
915 None => Err(self.eof("an attr name")),
916 }
917 }
918 }
919 _ => {
920 if let Some(rest) = w.strip_prefix("attrs.") {
921 let path = self.path(rest, t.start + 6)?;
922 return Ok(Pred::Attr(path, self.cmp()?));
923 }
924 if let Some(rest) = w.strip_prefix("marks.") {
925 let (name, path) = match rest.split_once('.') {
926 Some((n, p)) => (n, self.path(p, t.start + 7 + n.len())?),
927 None => (rest, Vec::new()),
928 };
929 if name.is_empty() {
930 return Err(QueryError {
931 offset: t.start + 6,
932 kind: QueryErrorKind::EmptyPath,
933 });
934 }
935 let cmp = self.cmp()?;
936 if path.is_empty() && cmp.is_some() {
937 return Err(QueryError {
938 offset: self.tokens[self.pos - 2].start,
939 kind: QueryErrorKind::InvalidOperator,
940 });
941 }
942 return Ok(Pred::MarkAttr(name.into(), path, cmp));
943 }
944 if self.peek_op().is_some() {
945 return Err(QueryError {
946 offset: t.start,
947 kind: QueryErrorKind::UnknownField(w.to_string()),
948 });
949 }
950 Ok(Pred::Word(w.to_lowercase().into()))
951 }
952 }
953 }
954}
955
956#[cfg(test)]
957mod tests {
958 use super::*;
959
960 #[test]
961 fn contains_ci_paths() {
962 assert!(contains_ci("Hello World", "world"));
963 assert!(!contains_ci("Hello", "world"));
964 assert!(contains_ci("ÉCOLE", "école"));
965 assert!(contains_ci("x", ""));
966 assert!(!contains_ci("ab", "abc"));
967 }
968
969 #[test]
970 fn lexes_escapes_and_offsets() {
971 let t = lex(r#"a "b\"c" =="#).unwrap();
972 assert_eq!(t[1].tok, Tok::Str("b\"c".into()));
973 assert_eq!((t[1].start, t[2].start), (2, 9));
974 assert_eq!(t[2].tok, Tok::Op(Op::Eq));
975 }
976}