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;