indent_stack/
lib.rs

1/*
2 * indent-stack
3 *
4 * Copyright (C) 2019 chankyin
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 *     http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18
19use std::error::Error;
20use std::fmt::{Display, Formatter};
21
22/// Stores the indentation state of an off-side parser.
23/// The default state is where
24#[derive(Debug, Default)]
25pub struct IndentStack {
26    stack: Vec<String>,
27    std_indent: Option<String>,
28    pub allow_inconsistent_indents: bool,
29}
30
31impl IndentStack {
32    pub fn default_inconsistent_indents() -> Self {
33        Self { allow_inconsistent_indents: true, ..Default::default() }
34    }
35
36    /// Mutates the state of the stack by applying an indent level.
37    /// There is no restriction on the input characters, as long as they are identical.
38    ///
39    /// The initial state is an empty indent. Passing a non-empty string the first time would
40    /// immediately result in an indent.
41    ///
42    /// # Returns
43    /// - If an indentation error occurred, `None` is returned.
44    /// - `Ok(1)` implies that an indent is detected.
45    /// - `Ok(0)` implies that no indentation change is detected.
46    /// - `Ok(x)` where `x < 0` implies that `-x` levels of dedents are detected.
47    pub fn accept(&mut self, input: &str) -> Result<isize, IndentError> {
48        let mut offset = 0;
49        for i in 0..self.stack.len() {
50            if offset == input.len() {
51                let ret = self.stack.len() - i;
52                self.stack.drain(i..);
53                return Ok(-(ret as isize));
54            }
55
56            let indent = self.stack[i].as_str();
57            if input.len() - offset < indent.len() || &input[offset..(offset + indent.len())] != indent {
58                return Err(IndentError::MixedIndent);
59            }
60
61            offset += indent.len();
62        }
63
64        if offset == input.len() {
65            return Ok(0);
66        }
67
68        let indent = &input[offset..];
69        if !self.allow_inconsistent_indents {
70            match &self.std_indent {
71                Some(std) => {
72                    if indent != std {
73                        return Err(IndentError::InconsistentIndent);
74                    }
75                }
76                None => {
77                    self.std_indent = Some(indent.into());
78                }
79            }
80        }
81
82        self.stack.push(input[offset..].into());
83        Ok(1)
84    }
85}
86
87#[derive(Debug, PartialEq)]
88pub enum IndentError {
89    /// Not all indentations use the same character sequence.
90    ///
91    /// Only returned if allow_inconsistent_indent is false.
92    InconsistentIndent,
93
94    /// The current indentation is not a continuation nor substring of the previous indentation.
95    MixedIndent,
96}
97
98impl Display for IndentError {
99    fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> {
100        match *self {
101            IndentError::InconsistentIndent => write!(f, "Not all indentations use the same character sequence."),
102            IndentError::MixedIndent => write!(f, "The current indentation is not a continuation nor substring of the previous indentation."),
103        }
104    }
105}
106
107impl Error for IndentError {}
108
109#[cfg(test)]
110mod tests;