1use crate::{ValveKeyValue, ValveKeyValueType};
18use crate::error::{Error, Result};
19
20#[derive(Clone, Debug)]
21pub struct TrackedChars<'a> {
22 inner: std::iter::Peekable<std::str::Chars<'a>>,
23 line: usize,
24 col: usize,
25}
26
27impl<'a> TrackedChars<'a> {
28 pub fn new(input: &'a str) -> Self {
29 Self { inner: input.chars().peekable(), line: 0, col: 0 }
30 }
31
32 pub fn get_pos(&self) -> (usize, usize) {
33 (self.line, self.col)
34 }
35
36 #[allow(clippy::should_implement_trait)]
37 pub fn next(&mut self) -> Option<char> {
38 let ch = self.inner.next();
39 if let Some(c) = ch {
40 match c {
41 '\n' => {
42 self.line += 1;
43 self.col = 0;
44 },
45 '\r' => self.col = 0,
46 _ => self.col += 1
47 }
48 }
49 ch
50 }
51
52 pub fn peek(&mut self) -> Option<&char> {
53 self.inner.peek()
54 }
55
56 pub fn peek_2(&mut self) -> Option<char> {
57 let mut next = self.inner.clone();
58 next.next()?;
59 next.next()
60 }
61}
62
63pub fn parse_string( input: &mut TrackedChars,
68 use_escape_sequences: bool
69) -> Result<String> {
70 let mut output_string = String::new();
71
72 let is_quote = Some(&'"') == input.peek();
73 if is_quote {
74 input.next();
75 }
76
77 let is_end = |inp: char| -> bool {
78 if is_quote {
79 inp == '"'
80 } else {
81 inp.is_whitespace() || inp == '{' || inp == '}' || inp == '"'
82 }
83 };
84
85 while let Some(ch) = input.peek() {
86 if use_escape_sequences {
87 match ch {
88 '\\' => match input.peek_2() {
89 Some(ch) => {
90 match ch {
91 '\\' => {
92 output_string.push('\\');
93 input.next();
94 input.next();
95 },
96 '"' => {
97 output_string.push('"');
98 input.next();
99 input.next();
100 },
101 'n' => {
102 output_string.push('\n');
103 input.next();
104 input.next();
105 },
106 't' => {
107 output_string.push('\t');
108 input.next();
109 input.next();
110 },
111 _ => {input.next();}
112 }
113 },
114 None => return Err(Error::UnexpectedEndOfFile)
115 },
116 _ => if is_end(*ch) {
117 if *ch == '"' && is_quote {input.next();}
118 return Ok(output_string);
119 } else {
120 output_string.push(*ch);
121 input.next();
122 }
123 }
124 } else {
125 if is_end(*ch) {
126 if *ch == '"' && is_quote {input.next();}
127 return Ok(output_string);
128 } else {
129 output_string.push(*ch);
130 input.next();
131 }
132 }
133 }
134
135 if is_quote {
136 Err(Error::UnexpectedEndOfFile)
137 } else {
138 Ok(output_string)
139 }
140}
141
142pub fn skip_comments(input: &mut TrackedChars) {
143 while let Some(ch) = input.peek() {
144 if ch == &'\n' {
145 return;
146 } else {
147 input.next();
148 }
149 }
150}
151
152pub fn parse_object(
153 input: &mut TrackedChars,
154 use_escape_sequences: bool,
155 depth: usize
156) -> Result<Vec<ValveKeyValue>> {
157 let mut keypairs: Vec<ValveKeyValue> = Vec::new();
158 let mut current_key: Option<String> = None;
159
160 while let Some(ch) = input.peek() {
161 if ch == &'{' {
162 let pos = input.get_pos();
163 input.next();
164 let parsed_object = parse_object(input, use_escape_sequences, depth + 1)?;
165 if let Some(curr_key) = ¤t_key {
166 keypairs.push(ValveKeyValue::new(curr_key.clone(), ValveKeyValueType::Object(parsed_object)));
167 current_key = None;
168 } else {
169 return Err(Error::UnexpectedObjectAsKey { line: pos.0, col: pos.1 });
170 }
171 } else if ch == &'}' {
172 let pos = input.get_pos();
173 if depth == 0 {
174 return Err(Error::UnexpectedEndOfObject { line: pos.0, col: pos.1 });
175 }
176 if current_key.is_some() {
177 return Err(Error::MissingValue { line: pos.0, col: pos.1 });
178 }
179 input.next();
180 return Ok(keypairs);
181 } else if ch.is_whitespace() {
182 input.next();
183 } else if ch == &'/' && input.peek_2() == Some('/') {
184 input.next();
185 skip_comments(input);
186 } else {
187 let parsed_string = parse_string(input, use_escape_sequences)?;
188 if let Some(curr_key) = ¤t_key {
189 keypairs.push(ValveKeyValue::new(curr_key.clone(), ValveKeyValueType::String(parsed_string)));
190 current_key = None;
191 } else {
192 current_key = Some(parsed_string);
193 }
194 }
195 }
196
197 if current_key.is_some() {
198 let pos = input.get_pos();
199 Err(Error::MissingValue { line: pos.0, col: pos.1 })
200 } else if depth == 0 {
201 Ok(keypairs)
202 } else {
203 Err(Error::UnexpectedEndOfFile)
204 }
205}
206
207
208pub fn parse(input: String, use_escape_sequences: bool) -> Result<Vec<ValveKeyValue>> {
210 parse_object(&mut TrackedChars::new(&input), use_escape_sequences, 0)
211}