veridian_kernel/pkg/
toml_parser.rs1#[cfg(feature = "alloc")]
8use alloc::{collections::BTreeMap, string::String, vec::Vec};
9
10#[cfg(feature = "alloc")]
11use crate::error::KernelError;
12
13#[cfg(feature = "alloc")]
15#[derive(Debug, Clone, PartialEq)]
16pub enum TomlValue {
17 String(String),
19 Integer(i64),
21 Boolean(bool),
23 Array(Vec<TomlValue>),
25 Table(BTreeMap<String, TomlValue>),
27}
28
29#[cfg(feature = "alloc")]
30impl TomlValue {
31 pub fn as_str(&self) -> Option<&str> {
33 match self {
34 TomlValue::String(s) => Some(s.as_str()),
35 _ => None,
36 }
37 }
38
39 pub fn as_integer(&self) -> Option<i64> {
41 match self {
42 TomlValue::Integer(n) => Some(*n),
43 _ => None,
44 }
45 }
46
47 pub fn as_bool(&self) -> Option<bool> {
49 match self {
50 TomlValue::Boolean(b) => Some(*b),
51 _ => None,
52 }
53 }
54
55 pub fn as_array(&self) -> Option<&[TomlValue]> {
57 match self {
58 TomlValue::Array(a) => Some(a.as_slice()),
59 _ => None,
60 }
61 }
62
63 pub fn as_table(&self) -> Option<&BTreeMap<String, TomlValue>> {
65 match self {
66 TomlValue::Table(t) => Some(t),
67 _ => None,
68 }
69 }
70}
71
72#[cfg(feature = "alloc")]
80pub fn parse_toml(input: &str) -> Result<BTreeMap<String, TomlValue>, KernelError> {
81 let mut root = BTreeMap::new();
82 let mut current_section: Option<String> = None;
83
84 for raw_line in input.lines() {
85 let line = strip_comment(raw_line).trim();
86 if line.is_empty() {
87 continue;
88 }
89
90 if line.starts_with('[') && line.ends_with(']') && !line.starts_with("[[") {
92 let section_name = line[1..line.len() - 1].trim();
93 if section_name.is_empty() {
94 return Err(KernelError::InvalidArgument {
95 name: "toml_section",
96 value: "empty_section_name",
97 });
98 }
99 current_section = Some(String::from(section_name));
100 root.entry(String::from(section_name))
102 .or_insert_with(|| TomlValue::Table(BTreeMap::new()));
103 continue;
104 }
105
106 if let Some((key, value)) = split_key_value(line) {
108 let key = key.trim();
109 let value = value.trim();
110
111 let parsed_value = parse_value(value)?;
112
113 if let Some(ref section) = current_section {
114 if let Some(TomlValue::Table(table)) = root.get_mut(section) {
116 table.insert(String::from(key), parsed_value);
117 }
118 } else {
119 root.insert(String::from(key), parsed_value);
121 }
122 }
123 }
124
125 Ok(root)
126}
127
128#[cfg(feature = "alloc")]
130fn strip_comment(line: &str) -> &str {
131 let mut in_string = false;
132 for (i, c) in line.char_indices() {
133 match c {
134 '"' => in_string = !in_string,
135 '#' if !in_string => return &line[..i],
136 _ => {}
137 }
138 }
139 line
140}
141
142#[cfg(feature = "alloc")]
144fn split_key_value(line: &str) -> Option<(&str, &str)> {
145 let mut in_string = false;
146 for (i, c) in line.char_indices() {
147 match c {
148 '"' => in_string = !in_string,
149 '=' if !in_string => {
150 return Some((&line[..i], &line[i + 1..]));
151 }
152 _ => {}
153 }
154 }
155 None
156}
157
158#[cfg(feature = "alloc")]
160fn parse_value(s: &str) -> Result<TomlValue, KernelError> {
161 let s = s.trim();
162
163 if s.starts_with('"') {
165 return parse_string(s);
166 }
167
168 if s == "true" {
170 return Ok(TomlValue::Boolean(true));
171 }
172 if s == "false" {
173 return Ok(TomlValue::Boolean(false));
174 }
175
176 if s.starts_with('[') {
178 return parse_array(s);
179 }
180
181 if s.starts_with('{') {
183 return parse_inline_table(s);
184 }
185
186 if let Some(n) = try_parse_integer(s) {
188 return Ok(TomlValue::Integer(n));
189 }
190
191 Err(KernelError::InvalidArgument {
192 name: "toml_value",
193 value: "unrecognised_value",
194 })
195}
196
197#[cfg(feature = "alloc")]
199fn parse_string(s: &str) -> Result<TomlValue, KernelError> {
200 if !s.starts_with('"') {
201 return Err(KernelError::InvalidArgument {
202 name: "toml_string",
203 value: "missing_opening_quote",
204 });
205 }
206
207 let mut result = String::new();
208 let mut chars = s[1..].chars();
209 let mut closed = false;
210
211 while let Some(c) = chars.next() {
212 match c {
213 '"' => {
214 closed = true;
215 break;
216 }
217 '\\' => {
218 let escaped = chars.next().ok_or(KernelError::InvalidArgument {
219 name: "toml_string",
220 value: "unterminated_escape",
221 })?;
222 match escaped {
223 'n' => result.push('\n'),
224 't' => result.push('\t'),
225 'r' => result.push('\r'),
226 '\\' => result.push('\\'),
227 '"' => result.push('"'),
228 _ => {
229 result.push('\\');
230 result.push(escaped);
231 }
232 }
233 }
234 _ => result.push(c),
235 }
236 }
237
238 if !closed {
239 return Err(KernelError::InvalidArgument {
240 name: "toml_string",
241 value: "unterminated_string",
242 });
243 }
244
245 Ok(TomlValue::String(result))
246}
247
248#[cfg(feature = "alloc")]
250fn parse_array(s: &str) -> Result<TomlValue, KernelError> {
251 if !s.starts_with('[') || !s.ends_with(']') {
252 return Err(KernelError::InvalidArgument {
253 name: "toml_array",
254 value: "malformed_array",
255 });
256 }
257
258 let inner = s[1..s.len() - 1].trim();
259 if inner.is_empty() {
260 return Ok(TomlValue::Array(Vec::new()));
261 }
262
263 let elements = split_top_level(inner, ',');
264 let mut values = Vec::new();
265 for elem in elements {
266 let elem = elem.trim();
267 if !elem.is_empty() {
268 values.push(parse_value(elem)?);
269 }
270 }
271
272 Ok(TomlValue::Array(values))
273}
274
275#[cfg(feature = "alloc")]
277fn parse_inline_table(s: &str) -> Result<TomlValue, KernelError> {
278 if !s.starts_with('{') || !s.ends_with('}') {
279 return Err(KernelError::InvalidArgument {
280 name: "toml_table",
281 value: "malformed_inline_table",
282 });
283 }
284
285 let inner = s[1..s.len() - 1].trim();
286 if inner.is_empty() {
287 return Ok(TomlValue::Table(BTreeMap::new()));
288 }
289
290 let pairs = split_top_level(inner, ',');
291 let mut table = BTreeMap::new();
292 for pair in pairs {
293 let pair = pair.trim();
294 if pair.is_empty() {
295 continue;
296 }
297 if let Some((key, value)) = split_key_value(pair) {
298 table.insert(String::from(key.trim()), parse_value(value)?);
299 } else {
300 return Err(KernelError::InvalidArgument {
301 name: "toml_table",
302 value: "missing_equals_in_inline_table",
303 });
304 }
305 }
306
307 Ok(TomlValue::Table(table))
308}
309
310#[cfg(feature = "alloc")]
313fn split_top_level(s: &str, delimiter: char) -> Vec<&str> {
314 let mut parts = Vec::new();
315 let mut depth = 0i32;
316 let mut in_string = false;
317 let mut start = 0;
318
319 for (i, c) in s.char_indices() {
320 match c {
321 '"' => in_string = !in_string,
322 '[' | '{' if !in_string => depth += 1,
323 ']' | '}' if !in_string => depth -= 1,
324 c if c == delimiter && !in_string && depth == 0 => {
325 parts.push(&s[start..i]);
326 start = i + 1;
327 }
328 _ => {}
329 }
330 }
331 if start <= s.len() {
333 parts.push(&s[start..]);
334 }
335
336 parts
337}
338
339#[cfg(feature = "alloc")]
341fn try_parse_integer(s: &str) -> Option<i64> {
342 let s = s.trim();
343 if s.is_empty() {
344 return None;
345 }
346
347 if s.starts_with("0x") || s.starts_with("0X") {
349 return i64::from_str_radix(&s[2..].replace('_', ""), 16).ok();
350 }
351 if s.starts_with("0o") || s.starts_with("0O") {
352 return i64::from_str_radix(&s[2..].replace('_', ""), 8).ok();
353 }
354 if s.starts_with("0b") || s.starts_with("0B") {
355 return i64::from_str_radix(&s[2..].replace('_', ""), 2).ok();
356 }
357
358 let cleaned: String = s.chars().filter(|&c| c != '_').collect();
360 cleaned.parse::<i64>().ok()
361}