rustla/parser/
table_parsers.rs

1/*!
2**TODO:** This submodule contains the table parsing functions,
3that the state machine uses as helpers in constructing tables.
4
5
6Copyright © 2020 Santtu Söderholm
7*/
8use crate::parser::line_cursor::LineCursor;
9use crate::parser::Parser;
10use crate::parser::types_and_aliases::TextBlockResult;
11
12#[derive(Debug)]
13/// A data structure that will be returned once the table parsing has been completed, or if it fails.
14/// Provides a variant for successfully constructed tables and an error for malformed tables.
15///
16/// Both grid and simple rST tables result in the data structure of column widhts,
17/// header rows and body rows. Each row is a vector of cells.
18///
19pub enum TableResult {
20    CompleteTable {
21        col_widths: Vec<u32>,
22        head_rows: Vec<Row>,
23        body_rows: Vec<Row>,
24    },
25    MalformedTableError(String),
26}
27
28impl TableResult {}
29
30#[derive(Debug)]
31pub struct Cell {
32    vspan: u32,
33    hspan: u32,
34    content_offset: u32,
35    text_lines: Vec<String>,
36}
37
38impl Cell {
39    /// The `Cell` constructor.
40    pub fn new(vspan: u32, hspan: u32, content_offset: u32, text_lines: Vec<String>) -> Self {
41        Self {
42            vspan: vspan,
43            hspan: hspan,
44            content_offset: content_offset,
45            text_lines: text_lines,
46        }
47    }
48}
49
50/// A type alias for a vector of table cells.
51type Row = Vec<Cell>;
52
53/// Implementation of the table parsing functions for the `Parser` type.
54impl Parser {
55
56    /// Parses a grid table, returning a `TableResult`.
57    pub fn parse_grid_table(src_lines: &Vec<String>, line_cursor: &LineCursor) -> TableResult {
58        // Initial preparations...
59        let table_lines = match Self::isolate_grid_table(src_lines, line_cursor) {
60            TableIsolationResult::Table(lines) => lines,
61            TableIsolationResult::EmptyTable => {
62                return TableResult::MalformedTableError(format!(
63                    "Table starting on line {} was empty.",
64                    line_cursor.sum_total()
65                ))
66            }
67            TableIsolationResult::EndOfInput => {
68                return TableResult::MalformedTableError(format!(
69                    "Ran off the end of input when scanning a table starting on line {}.",
70                    line_cursor.sum_total()
71                ))
72            }
73        };
74
75        let table_height = if let Some(line_len) = table_lines.len().checked_sub(1) {
76            line_len
77        } else {
78            return TableResult::MalformedTableError(format!(
79                "Table on line {} only had a top border?",
80                line_cursor.sum_total()
81            ));
82        };
83        let table_width = {
84            match table_lines.get(0) {
85                Some(line) => match line.chars().count().checked_sub(1) {
86                    Some(num) => num,
87                    None => return TableResult::MalformedTableError(format!(
88                        "The first row of grid table on line {} was only a single character long?",
89                        line_cursor.sum_total()
90                    )),
91                },
92                None => {
93                    return TableResult::MalformedTableError(format!(
94                        "Table on line {} didn't even have a top border?",
95                        line_cursor.sum_total()
96                    ))
97                }
98            }
99        };
100
101        let mut cell_corner_coordinates = Vec::<(usize, usize)>::from([(0, 0)]);
102
103        let done_cells = [usize::MAX].repeat(table_width);
104
105        // Start parsing loop...
106        while let Some((top_pos, left_pos)) = cell_corner_coordinates.pop() {
107            if top_pos == table_height || left_pos == table_width || top_pos <= done_cells[left_pos]
108            {
109                continue;
110            }
111
112            // Scan cell next...
113            let (right_pos, bottom_pos) = if let Some((right, bottom)) =
114                Self::outline_cell(&table_lines, top_pos, left_pos)
115            {
116                (right, bottom)
117            } else {
118                continue;
119            };
120        }
121        todo!()
122    }
123
124    /// Retrieves the lines containing a grid table from the source line vector.
125    fn isolate_grid_table(
126        src_lines: &Vec<String>,
127        line_cursor: &LineCursor,
128    ) -> TableIsolationResult {
129        let start_line = line_cursor.relative_offset();
130        let indent_allowed = true;
131        let remove_indent = true;
132        let alignment = if let Some(line) = src_lines.get(line_cursor.relative_offset()) {
133            line.chars().take_while(|c| c.is_whitespace()).count()
134        } else {
135            return TableIsolationResult::EndOfInput;
136        };
137
138        let (mut lines, offset) = if let TextBlockResult::Ok { lines, offset } = Parser::read_text_block(
139            src_lines,
140            start_line,
141            indent_allowed,
142            remove_indent,
143            Some(alignment),
144            true
145        ) {
146            (lines, offset)
147        } else {
148            return TableIsolationResult::EndOfInput;
149        };
150
151        // Check if the last line of lines matches the table bottom pattern and if not,
152        // pop lines until it is found.
153        while let Some(line) = lines.last_mut() {
154            if let Some(capts) =
155                crate::parser::automata::GRID_TABLE_TOP_AND_BOT_AUTOMATON.captures(line)
156            {
157                break;
158            } else {
159                if let None = lines.pop() {
160                    return TableIsolationResult::EmptyTable;
161                }
162            }
163        }
164
165        // Kept popping and met the table top line...
166        if lines.len() == 1 {
167            return TableIsolationResult::EmptyTable;
168        }
169
170        TableIsolationResult::Table(lines)
171    }
172
173    /// Finds the positions of the table cell corners, starting from the given top left corner coordinates,
174    /// moving towards the right edge.
175    fn outline_cell(
176        table_lines: &Vec<String>,
177        top_pos: usize,
178        left_pos: usize,
179    ) -> Option<(usize, usize)> {
180        let colseps = Vec::<usize>::new();
181        if let Some((right, bottom)) = Self::find_right_colsep(table_lines, top_pos, left_pos) {
182            Some((right, bottom))
183        } else {
184            None
185        }
186    }
187
188    fn find_right_colsep(
189        table_lines: &Vec<String>,
190        top_pos: usize,
191        left_pos: usize,
192    ) -> Option<(usize, usize)> {
193        if let Some(line) = table_lines.get(top_pos) {
194            let topline_chars = line.chars().enumerate().skip(left_pos + 1);
195
196            for (i, c) in topline_chars {
197                if c == '+' {
198                    if let Some(bottom_pos) =
199                        Self::find_below_rowsep(table_lines, top_pos, left_pos, i)
200                    {
201                        return Some((i, bottom_pos));
202                    } else {
203                        return None;
204                    };
205                } else {
206                    continue;
207                }
208            }
209        } else {
210            return None;
211        }
212        None
213    }
214
215    /// Finds the bottom row separator of the cell being scanned, assuming it can trace its way to the bottom and top left corners as well.
216    fn find_below_rowsep(
217        table_lines: &Vec<String>,
218        top_pos: usize,
219        left_pos: usize,
220        right_pos: usize,
221    ) -> Option<usize> {
222        let lines = table_lines.iter().skip(top_pos + 1);
223
224        for line in lines {
225            let c = if let Some(c) = line.chars().skip(left_pos).next() {
226                c
227            } else {
228                return None;
229            };
230        }
231        todo!()
232    }
233
234    /// Tries to locate the bottom left corner of the cell in question, starting from the bottom right corner.
235    fn find_left_colsep(table_lines: &Vec<String>) {
236        todo!()
237    }
238
239    /// Tries to locate the top left corner of the cell in question, starting from the bottom left corner.
240    fn find_above_rowsep(table_lines: &Vec<String>) {
241        todo!()
242    }
243
244    // Simple table parser
245
246    /// Parses a simple table into a `TableResult`.
247    pub fn parse_simple_table(table_string: Vec<String>) -> TableResult {
248        todo!()
249    }
250
251    /// Retrieves the lines containing a simple table from the source line vector.
252    pub fn isolate_simple_table(src_lines: &Vec<String>) {
253        todo!()
254    }
255}
256
257pub enum TableIsolationResult {
258    Table(Vec<String>),
259    EndOfInput,
260    EmptyTable,
261}