Repository: pest-parser/pest (language: rust)

Issue:
Pairs#move_cursor is slow down iter
Hi @tomtau,

I am very sorry about that. 😥

I have found `Pairs#iter` performance issue when we merged #754.

Because when we iter pairs, each iter next will called `Position#line_col` once to move cursor. That will slow down the iter . 

Especially that, in sometimes we just need get a little pair's line,col, not all of pairs. So to do this is not value of money.

By my test:

```rs
fn bench_pairs_iter(c: &mut Criterion) {
    let data = include_str!("data.json");

    fn iter_all_pairs(pairs: pest::iterators::Pairs<autocorrect::Rule>) {
        for pair in pairs {
            iter_all_pairs(pair.into_inner());
        }
    }

    c.bench_function("nested iter", |b| {
        let pairs = autocorrect::JsonParser::parse(autocorrect::Rule::item, &data).unwrap();

        b.iter(move || iter_all_pairs(pairs.clone()));
    });

    c.bench_function("flatten iter", |b| {
        let pairs = autocorrect::JsonParser::parse(autocorrect::Rule::item, &data).unwrap();

        b.iter(move || {
            for _pair in pairs.clone().flatten().into_iter() {
                // do nothing
            }
        });
    });
}
```

```
nested iter                             time:   [258.27 µs 260.05 µs 262.64 µs]
nested iter (fast-line-col)             time:   [14.943 µs 14.963 µs 14.993 µs]
flatten iter                            time:   [2.0367 µs 2.1104 µs 2.2144 µs]
```

> In a large files (or have a lot of pairs, For instance: enabled `WHITESPACE`, each spaces will a pair item), iterate all pairs may take a long time than before.

So we need decide to how to do, remove it or find some solution to fix that.


Discussion / hints:

Fix patch:
diff --git a/grammars/benches/json.rs b/grammars/benches/json.rs
index 7e5fbb99..e580d86b 100644
--- a/grammars/benches/json.rs
+++ b/grammars/benches/json.rs
@@ -12,21 +12,16 @@ extern crate pest;
 extern crate pest_grammars;
 use criterion::{criterion_group, criterion_main, Criterion};
 
-use std::fs::File;
-use std::io::Read;
-
 use pest::Parser;
 
 use pest_grammars::json::*;
 
-fn criterion_benchmark(c: &mut Criterion) {
-    let mut file = File::open("benches/data.json").unwrap();
-    let mut data = String::new();
-
-    file.read_to_string(&mut data).unwrap();
+// json parser             time:   [12.085 µs 12.339 µs 12.680 µs]
+fn bench_json_parse(c: &mut Criterion) {
+    let data = include_str!("data.json");
 
     c.bench_function("json parser", |b| {
-        b.iter(|| JsonParser::parse(Rule::json, &data).unwrap())
+        b.iter(|| JsonParser::parse(Rule::json, data).unwrap())
     });
 }
 
@@ -35,47 +30,45 @@ mod autocorrect {
 
     #[derive(Parser)]
     #[grammar_inline = r#"
-newline = ${ "\n" | "\r" }
-space = ${ " "+ }
+item = _{ SOI ~ line* ~ EOI }
+line = _{ pair | other }
+
+WHITESPACE = { " " | "\t" | NEWLINE }
 
 other = ${ !(pair) ~ ANY }
-comment = ${ single_line_comment | multiline_comment }
-single_line_comment = _{ "//" ~ (!(newline) ~ ANY)* }
-multiline_comment = _{ "/*" ~ (!("*/") ~ ANY)* ~ "*/"}
 
-string_type = _{ 
-  ("\"" ~ (!(newline | "\"") ~ ANY)* ~ "\"") 
-}
-key = ${ string_type ~ (" ")* ~ ":" ~ (" ")* }
-string = ${ string_type  }
-pair = _{ key ~ string }
+key    = ${ inner_string ~ (" ")* ~ ":" ~ (" ")* }
+string = ${ inner_string }
+pair   = { key ~ string }
 
-line = _{ pair | comment | space | other | newline }
-item = _{ SOI ~ line* ~ EOI } 
+inner_string = @{ ("\"" ~ (!(NEWLINE | "\"") ~ ANY)* ~ "\"") }
 "#]
     pub struct JsonParser;
 }
 
 // With 500 times iter
-// pair.line_col                                time:   [2.9937 µs 2.9975 µs 3.0018 µs]
+// pair.line_col (with LineIndex)               time:   [22.426 µs 22.489 µs 22.556 µs]
 // position.line_col                            time:   [212.59 µs 213.38 µs 214.29 µs]
 // position.line_col (with fast-line-col)       time:   [18.241 µs 18.382 µs 18.655 µs]
 //
 // With 1000 times iter
-// pair.line_col                                time:   [10.814 µs 10.846 µs 10.893 µs]
-// position.line_col                            time:   [90.135 µs 93.901 µs 98.655 µs]
-// position.line_col (with fast-line-col)       time:   [1.7199 ms 1.7246 ms 1.7315 ms]
-fn line_col_benchmark(c: &mut Criterion) {
-    let mut file = File::open("benches/main.i18n.json").unwrap();
-    let mut data = String::new();
-
-    file.read_to_string(&mut data).unwrap();
-    let pairs = autocorrect::JsonParser::parse(autocorrect::Rule::item, &data).unwrap();
+// pair.line_col (with LineIndex)               time:   [41.160 µs 41.969 µs 43.478 µs]
+// position.line_col                            time:   [1.7199 ms 1.7246 ms 1.7315 ms]
+// position.line_col (with fast-line-col)       time:   [48.498 µs 49.450 µs 50.877 µs]
+//
+// With 10K times iter
+// pair.line_col                                time:   [471.75 µs 475.03 µs 478.94 µs]
+// position.line_col (with fast-line-col)       time:   [8.1941 ms 8.3278 ms 8.5144 ms]
+fn bench_line_col(c: &mut Criterion) {
+    let data = include_str!("main.i18n.json");
+    let pairs = autocorrect::JsonParser::parse(autocorrect::Rule::item, data)
+        .unwrap()
+        .flatten();
 
     c.bench_function("pair.line_col", |b| {
         b.iter(|| {
             let mut pairs = pairs.clone();
-            for _ in 0..500 {
+            for _ in 0..1000 {
                 pairs.next().unwrap().line_col();
             }
         })
@@ -84,12 +77,41 @@ fn line_col_benchmark(c: &mut Criterion) {
     c.bench_function("position.line_col", |b| {
         b.iter(|| {
             let mut pairs = pairs.clone();
-            for _ in 0..500 {
+            for _ in 0..1000 {
                 pairs.next().unwrap().as_span().start_pos().line_col();
             }
         });
     });
 }
 
-criterion_group!(benches, criterion_benchmark, line_col_benchmark,);
+// pairs nested iter                    time:   [2.0168 ms 2.0381 ms 2.0725 ms]
+// pairs nested iter (with LineIndex)   time:   [14.716 µs 14.822 µs 14.964 µs]
+// pairs flatten iter                   time:   [4.5973 µs 4.6132 µs 4.6307 µs]
+// pairs flatten iter (with LineIndex)  time:   [5.4637 µs 5.6061 µs 5.7886 µs]
+fn bench_pairs_iter(c: &mut Criterion) {
+    let data = include_str!("data.json");
+
+    fn iter_all_pairs(pairs: pest::iterators::Pairs<autocorrect::Rule>) {
+        for pair in pairs {
+            iter_all_pairs(pair.into_inner());
+        }
+    }
+
+    c.bench_function("pairs nested iter", |b| {
+        let pairs = autocorrect::JsonParser::parse(autocorrect::Rule::item, data).unwrap();
+        b.iter(move || iter_all_pairs(pairs.clone()));
+    });
+
+    c.bench_function("pairs flatten iter", |b| {
+        let pairs = autocorrect::JsonParser::parse(autocorrect::Rule::item, data).unwrap();
+
+        b.iter(move || {
+            for _pair in pairs.clone().flatten() {
+                // do nothing
+            }
+        });
+    });
+}
+
+criterion_group!(benches, bench_json_parse, bench_line_col, bench_pairs_iter);
 criterion_main!(benches);
diff --git a/pest/src/iterators/flat_pairs.rs b/pest/src/iterators/flat_pairs.rs
index 411d88b2..52a20740 100644
--- a/pest/src/iterators/flat_pairs.rs
+++ b/pest/src/iterators/flat_pairs.rs
@@ -11,6 +11,7 @@ use alloc::rc::Rc;
 use alloc::vec::Vec;
 use core::fmt;
 
+use super::line_index::LineIndex;
 use super::pair::{self, Pair};
 use super::queueable_token::QueueableToken;
 use super::tokens::{self, Tokens};
@@ -28,6 +29,7 @@ pub struct FlatPairs<'i, R> {
     input: &'i str,
     start: usize,
     end: usize,
+    line_index: Rc<LineIndex>,
 }
 
 /// # Safety
@@ -42,6 +44,7 @@ pub unsafe fn new<R: RuleType>(
     FlatPairs {
         queue,
         input,
+        line_index: Rc::new(LineIndex::new(input)),
         start,
         end,
     }
@@ -107,7 +110,14 @@ impl<'i, R: RuleType> Iterator for FlatPairs<'i, R> {
             return None;
         }
 
-        let pair = unsafe { pair::new(Rc::clone(&self.queue), self.input, self.start) };
+        let pair = unsafe {
+            pair::new(
+                Rc::clone(&self.queue),
+                self.input,
+                Rc::clone(&self.line_index),
+                self.start,
+            )
+        };
         self.next_start();
 
         Some(pair)
@@ -122,7 +132,14 @@ impl<'i, R: RuleType> DoubleEndedIterator for FlatPairs<'i, R> {
 
         self.next_start_from_end();
 
-        let pair = unsafe { pair::new(Rc::clone(&self.queue), self.input, self.end) };
+        let pair = unsafe {
+            pair::new(
+                Rc::clone(&self.queue),
+                self.input,
+                Rc::clone(&self.line_index),
+                self.end,
+            )
+        };
 
         Some(pair)
     }
@@ -141,6 +158,7 @@ impl<'i, R: Clone> Clone for FlatPairs<'i, R> {
         FlatPairs {
             queue: Rc::clone(&self.queue),
             input: self.input,
+            line_index: Rc::clone(&self.line_index),
             start: self.start,
             end: self.end,
         }
diff --git a/pest/src/iterators/line_index.rs b/pest/src/iterators/line_index.rs
new file mode 100644
index 00000000..54871e1b
--- /dev/null
+++ b/pest/src/iterators/line_index.rs
@@ -0,0 +1,91 @@
+//! `LineIndex` to make a line_offsets, each item is an byte offset (start from 0) of the beginning of the line.
+//!
+//! For example, the text: `"hello 你好\nworld"`, the line_offsets will store `[0, 13]`.
+//!
+//! Then `line_col` with a offset just need to find the line index by binary search.
+//!
+//! Inspired by rust-analyzer's `LineIndex`:
+//! <https://github.com/rust-lang/rust/blob/1.67.0/src/tools/rust-analyzer/crates/ide-db/src/line_index.rs>
+use alloc::vec::Vec;
+
+#[derive(Clone)]
+pub struct LineIndex {
+    /// Offset (bytes) the the beginning of each line, zero-based
+    line_offsets: Vec<usize>,
+}
+
+impl LineIndex {
+    pub fn new(text: &str) -> LineIndex {
+        let mut line_offsets: Vec<usize> = alloc::vec![0];
+
+        let mut offset = 0;
+
+        for c in text.chars() {
+            offset += c.len_utf8();
+            if c == '\n' {
+                line_offsets.push(offset);
+            }
+        }
+
+        LineIndex { line_offsets }
+    }
+
+    /// Returns (line, col) of pos.
+    ///
+    /// The pos is a byte offset, start from 0, e.g. "ab" is 2, "你好" is 6
+    pub fn line_col(&self, input: &str, pos: usize) -> (usize, usize) {
+        let line = self.line_offsets.partition_point(|&it| it <= pos) - 1;
+        let first_offset = self.line_offsets[line];
+
+        // Get line str from original input, then we can get column offset
+        let line_str = &input[first_offset..pos];
+        let col = line_str.chars().count();
+
+        (line + 1, col + 1)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[allow(clippy::zero_prefixed_literal)]
+    #[test]
+    fn test_line_index() {
+        let text = "hello 你好 A🎈C\nworld";
+        let table = [
+            (00, 1, 1, 'h'),
+            (01, 1, 2, 'e'),
+            (02, 1, 3, 'l'),
+            (03, 1, 4, 'l'),
+            (04, 1, 5, 'o'),
+            (05, 1, 6, ' '),
+            (06, 1, 7, '你'),
+            (09, 1, 8, '好'),
+            (12, 1, 9, ' '),
+            (13, 1, 10, 'A'),
+            (14, 1, 11, '🎈'),
+            (18, 1, 12, 'C'),
+            (19, 1, 13, '\n'),
+            (20, 2, 1, 'w'),
+            (21, 2, 2, 'o'),
+            (22, 2, 3, 'r'),
+            (23, 2, 4, 'l'),
+            (24, 2, 5, 'd'),
+        ];
+
+        let index = LineIndex::new(text);
+        for &(offset, line, col, c) in table.iter() {
+            let res = index.line_col(text, offset);
+            assert_eq!(
+                (res.0, res.1),
+                (line, col),
+                "Expected: ({}, {}, {}, {:?})",
+                offset,
+                line,
+                col,
+                c
+            );
+        }
+    }
+}
diff --git a/pest/src/iterators/mod.rs b/pest/src/iterators/mod.rs
index 1a789637..7f81019a 100644
--- a/pest/src/iterators/mod.rs
+++ b/pest/src/iterators/mod.rs
@@ -10,6 +10,7 @@
 //! Types and iterators for parser output.
 
 mod flat_pairs;
+mod line_index;
 mod pair;
 pub(crate) mod pairs;
 mod queueable_token;
diff --git a/pest/src/iterators/pair.rs b/pest/src/iterators/pair.rs
index 2c813478..891b9059 100644
--- a/pest/src/iterators/pair.rs
+++ b/pest/src/iterators/pair.rs
@@ -20,6 +20,7 @@ use core::str;
 #[cfg(feature = "pretty-print")]
 use serde::ser::SerializeStruct;
 
+use super::line_index::LineIndex;
 use super::pairs::{self, Pairs};
 use super::queueable_token::QueueableToken;
 use super::tokens::{self, Tokens};
@@ -43,7 +44,7 @@ pub struct Pair<'i, R> {
     input: &'i str,
     /// Token index into `queue`.
     start: usize,
-    pub(crate) line_col: Option<(usize, usize)>,
+    line_index: Rc<LineIndex>,
 }
 
 /// # Safety
@@ -52,13 +53,14 @@ pub struct Pair<'i, R> {
 pub unsafe fn new<R: RuleType>(
     queue: Rc<Vec<QueueableToken<R>>>,
     input: &str,
+    line_index: Rc<LineIndex>,
     start: usize,
 ) -> Pair<'_, R> {
     Pair {
         queue,
         input,
         start,
-        line_col: None,
+        line_index,
     }
 }
 
@@ -204,7 +206,13 @@ impl<'i, R: RuleType> Pair<'i, R> {
     pub fn into_inner(self) -> Pairs<'i, R> {
         let pair = self.pair();
 
-        pairs::new(self.queue, self.input, self.start + 1, pair)
+        pairs::new(
+            self.queue,
+            self.input,
+            Some(self.line_index),
+            self.start + 1,
+            pair,
+        )
     }
 
     /// Returns the `Tokens` for the `Pair`.
@@ -245,10 +253,8 @@ impl<'i, R: RuleType> Pair<'i, R> {
 
     /// Returns the `line`, `col` of this pair start.
     pub fn line_col(&self) -> (usize, usize) {
-        match &self.line_col {
-            Some(line_col) => (line_col.0, line_col.1),
-            None => self.as_span().start_pos().line_col(),
-        }
+        let pos = self.pos(self.start);
+        self.line_index.line_col(self.input, pos)
     }
 
     fn pair(&self) -> usize {
@@ -273,7 +279,13 @@ impl<'i, R: RuleType> Pairs<'i, R> {
     /// Create a new `Pairs` iterator containing just the single `Pair`.
     pub fn single(pair: Pair<'i, R>) -> Self {
         let end = pair.pair();
-        pairs::new(pair.queue, pair.input, pair.start, end)
+        pairs::new(
+            pair.queue,
+            pair.input,
+            Some(pair.line_index),
+            pair.start,
+            end,
+        )
     }
 }
 
diff --git a/pest/src/iterators/pairs.rs b/pest/src/iterators/pairs.rs
index d4596b0f..c21a7fae 100644
--- a/pest/src/iterators/pairs.rs
+++ b/pest/src/iterators/pairs.rs
@@ -20,27 +20,12 @@ use core::str;
 use serde::ser::SerializeStruct;
 
 use super::flat_pairs::{self, FlatPairs};
+use super::line_index::LineIndex;
 use super::pair::{self, Pair};
 use super::queueable_token::QueueableToken;
 use super::tokens::{self, Tokens};
-use crate::{position, RuleType};
+use crate::RuleType;
 
-#[derive(Clone)]
-pub struct Cursor {
-    pub line: usize,
-    pub col: usize,
-    pub end: usize,
-}
-
-impl Default for Cursor {
-    fn default() -> Cursor {
-        Cursor {
-            line: 1,
-            col: 1,
-            end: 0,
-        }
-    }
-}
 /// An iterator over [`Pair`]s. It is created by [`pest::state`] and [`Pair::into_inner`].
 ///
 /// [`Pair`]: struct.Pair.html
@@ -52,21 +37,27 @@ pub struct Pairs<'i, R> {
     input: &'i str,
     start: usize,
     end: usize,
-    cursor: Cursor,
+    line_index: Rc<LineIndex>,
 }
 
 pub fn new<R: RuleType>(
     queue: Rc<Vec<QueueableToken<R>>>,
     input: &str,
+    line_index: Option<Rc<LineIndex>>,
     start: usize,
     end: usize,
 ) -> Pairs<'_, R> {
+    let line_index = match line_index {
+        Some(line_index) => line_index,
+        None => Rc::new(LineIndex::new(input)),
+    };
+
     Pairs {
         queue,
         input,
         start,
         end,
-        cursor: Cursor::default(),
+        line_index,
     }
 }
 
@@ -199,7 +190,14 @@ impl<'i, R: RuleType> Pairs<'i, R> {
     #[inline]
     pub fn peek(&self) -> Option<Pair<'i, R>> {
         if self.start < self.end {
-            Some(unsafe { pair::new(Rc::clone(&self.queue), self.input, self.start) })
+            Some(unsafe {
+                pair::new(
+                    Rc::clone(&self.queue),
+                    self.input,
+                    Rc::clone(&self.line_index),
+                    self.start,
+                )
+            })
         } else {
             None
         }
@@ -237,42 +235,13 @@ impl<'i, R: RuleType> Pairs<'i, R> {
             }
         }
     }
-
-    /// Move the cursor (line, col) by a part of the input.
-    fn move_cursor(&mut self, input: &str, start: usize, end: usize) -> (usize, usize) {
-        // Move cursor for some skiped characters (by skip(n))
-        let prev_end = self.cursor.end;
-        if prev_end != start {
-            self.move_cursor(input, prev_end, start);
-        }
-
-        let (prev_line, prev_col) = (self.cursor.line, self.cursor.col);
-
-        let part = &input[self.cursor.end..end];
-        let (l, c) = position::line_col(part, part.len(), (0, 0));
-
-        self.cursor.line += l;
-        // Has new line
-        if l > 0 {
-            self.cursor.col = c;
-        } else {
-            self.cursor.col += c;
-        }
-        self.cursor.end = end;
-
-        (prev_line, prev_col)
-    }
 }
 
 impl<'i, R: RuleType> Iterator for Pairs<'i, R> {
     type Item = Pair<'i, R>;
 
     fn next(&mut self) -> Option<Self::Item> {
-        let mut pair = self.peek()?;
-        let span = pair.as_span();
-
-        let (l, c) = self.move_cursor(self.input, span.start(), span.end());
-        pair.line_col = Some((l, c));
+        let pair = self.peek()?;
 
         self.start = self.pair() + 1;
         Some(pair)
@@ -287,7 +256,14 @@ impl<'i, R: RuleType> DoubleEndedIterator for Pairs<'i, R> {
 
         self.end = self.pair_from_end();
 
-        let pair = unsafe { pair::new(Rc::clone(&self.queue), self.input, self.end) };
+        let pair = unsafe {
+            pair::new(
+                Rc::clone(&self.queue),
+                self.input,
+                Rc::clone(&self.line_index),
+                self.end,
+            )
+        };
 
         Some(pair)
     }
@@ -478,26 +454,14 @@ mod tests {
         let pair = pairs.next().unwrap();
         assert_eq!(pair.as_str(), "abc");
         assert_eq!(pair.line_col(), (1, 1));
-        assert_eq!(
-            (pairs.cursor.line, pairs.cursor.col, pairs.cursor.end),
-            (1, 4, 3)
-        );
 
         let pair = pairs.next().unwrap();
         assert_eq!(pair.as_str(), "e");
         assert_eq!(pair.line_col(), (2, 1));
-        assert_eq!(
-            (pairs.cursor.line, pairs.cursor.col, pairs.cursor.end),
-            (2, 2, 5)
-        );
 
         let pair = pairs.next().unwrap();
         assert_eq!(pair.as_str(), "fgh");
         assert_eq!(pair.line_col(), (2, 2));
-        assert_eq!(
-            (pairs.cursor.line, pairs.cursor.col, pairs.cursor.end),
-            (2, 5, 8)
-        );
     }
 
     #[test]
diff --git a/pest/src/parser_state.rs b/pest/src/parser_state.rs
index 609de555..f58de00c 100644
--- a/pest/src/parser_state.rs
+++ b/pest/src/parser_state.rs
@@ -157,7 +157,7 @@ where
     match f(state) {
         Ok(state) => {
             let len = state.queue.len();
-            Ok(pairs::new(Rc::new(state.queue), input, 0, len))
+            Ok(pairs::new(Rc::new(state.queue), input, None, 0, len))
         }
         Err(mut state) => {
             let variant = if state.reached_call_limit() {


Test patch:
diff --git a/grammars/tests/examples.json b/grammars/tests/examples.json
index 2149c4d5..4f889eeb 100644
--- a/grammars/tests/examples.json
+++ b/grammars/tests/examples.json
@@ -26,7 +26,7 @@
     "0123456789": "digit",
     "special": "`1~!@#$%^&*()_+-={':[,]}|;.</>?",
     "hex": "\u0123\u4567\u89AB\uCDEF\uabcd\uef4A",
-    "true": true,
+    "han": "你好", "true": true,
     "false": false,
     "null": null,
     "array":[  ],
diff --git a/grammars/tests/examples.line-col.txt b/grammars/tests/examples.line-col.txt
index 272edca2..0c10ec5d 100644
--- a/grammars/tests/examples.line-col.txt
+++ b/grammars/tests/examples.line-col.txt
@@ -1,3 +1,4 @@
+
 (2:3) "JSON Test Pattern pass1"
 (3:4) "object with 1 member"
 (3:28) "array with 1 element"
@@ -43,8 +44,10 @@
 (27:16) "`1~!@#$%^&*()_+-={':[,]}|;.</>?"
 (28:5) "hex"
 (28:12) "\u0123\u4567\u89AB\uCDEF\uabcd\uef4A"
-(29:5) "true"
-(29:13) true
+(29:5) "han"
+(29:12) "你好"
+(29:18) "true"
+(29:26) true
 (30:5) "false"
 (30:14) false
 (31:5) "null"
diff --git a/grammars/tests/json.rs b/grammars/tests/json.rs
index 9d609805..9a59dc4d 100644
--- a/grammars/tests/json.rs
+++ b/grammars/tests/json.rs
@@ -160,10 +160,16 @@ fn object() {
 }
 
 #[test]
-fn examples() {
+fn test_json_parse() {
     let raw = include_str!("examples.json");
-    let pairs = JsonParser::parse(Rule::json, raw).unwrap();
+    let ast = JsonParser::parse(Rule::json, raw);
+    assert!(ast.is_ok());
+}
 
+#[test]
+fn test_line_col() {
+    let raw = include_str!("examples.json");
+    let pairs = JsonParser::parse(Rule::json, raw).unwrap();
     let expected = include_str!("examples.line-col.txt");
 
     // Test for flatten iter, and use span.start_pos().line_col()
@@ -179,10 +185,38 @@ fn examples() {
 
     // Test for nested iter, use pair.line_col()
     let mut out = String::new();
-    for pair in pairs {
+    for pair in pairs.clone() {
         out.push_str(&build_result_for_pair(pair.clone()));
     }
+    assert_eq!(expected.trim(), out.trim());
 
+    // Test for nested iter, use pair.line_col()
+    let mut out = String::new();
+    for pair in pairs.clone().rev() {
+        out.insert_str(0, &build_result_for_pair(pair.clone()));
+    }
+    assert_eq!(expected.trim(), out.trim());
+
+    // Test for flatten iter, and use pair.line_col()
+    let mut out = String::new();
+    for pair in pairs.clone().flatten() {
+        let sub_pairs = pair.clone().into_inner();
+        if sub_pairs.count() == 0 {
+            let span = pair.as_span();
+            out.push_str(&build_line_col(pair.line_col(), span.as_str()));
+        }
+    }
+    assert_eq!(expected.trim(), out.trim());
+
+    // Test for flatten rev iter, and use pair.line_col()
+    let mut out = String::new();
+    for pair in pairs.clone().flatten().rev() {
+        let sub_pairs = pair.clone().into_inner();
+        if sub_pairs.count() == 0 {
+            let span = pair.as_span();
+            out.insert_str(0, &build_line_col(pair.line_col(), span.as_str()));
+        }
+    }
     assert_eq!(expected.trim(), out.trim());
 }
 
@@ -199,6 +233,7 @@ fn build_result_for_pair(pair: pest::iterators::Pair<Rule>) -> String {
     let mut out = String::new();
 
     let sub_pairs = pair.clone().into_inner();
+
     if sub_pairs.clone().count() == 0 {
         out.push_str(&build_line_col(pair.line_col(), pair.as_str()));
     } else {