diff options
author | pommicket <pommicket@gmail.com> | 2025-09-07 22:51:58 -0400 |
---|---|---|
committer | pommicket <pommicket@gmail.com> | 2025-09-07 22:51:58 -0400 |
commit | 8d5b16efda7c21fabd3cec655d143ed3e78ea5e6 (patch) | |
tree | abef05b4f14321b34ba1b7c603d1eb7512aa5972 | |
parent | 575bb3913b01ec83490d61f0540ccdcb4d861845 (diff) |
More parser
-rw-r--r-- | examples/conf.pom | 1 | ||||
-rw-r--r-- | examples/simple.rs | 16 | ||||
-rw-r--r-- | src/lib.rs | 126 |
3 files changed, 141 insertions, 2 deletions
diff --git a/examples/conf.pom b/examples/conf.pom new file mode 100644 index 0000000..5fe5fbb --- /dev/null +++ b/examples/conf.pom @@ -0,0 +1 @@ +fav-colour = green diff --git a/examples/simple.rs b/examples/simple.rs new file mode 100644 index 0000000..161baac --- /dev/null +++ b/examples/simple.rs @@ -0,0 +1,16 @@ +use pom_parser::Configuration; +use std::process::ExitCode; + +fn try_main() -> Result<(), Box<dyn std::error::Error>> { + let conf = Configuration::load_path("examples/conf.pom")?; + println!("{conf}"); + Ok(()) +} + +fn main() -> ExitCode { + if let Err(e) = try_main() { + eprintln!("Error: {e}"); + return ExitCode::FAILURE; + } + ExitCode::SUCCESS +} @@ -1,6 +1,7 @@ #![cfg_attr(not(feature = "std"), no_std)] extern crate alloc; +use alloc::borrow::Cow; #[cfg(not(feature = "std"))] use alloc::collections::BTreeMap as Map; use alloc::sync::Arc; @@ -36,6 +37,54 @@ pub struct Configuration { values: Arc<Map<Box<str>, Value>>, } +impl fmt::Display for Configuration { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut lines = vec![]; + fn format_value(val: &str) -> Cow<str> { + if val.chars().all(|c| !c.is_ascii_control()) + && !val.starts_with(['\'', '"', '`']) + && !val.starts_with(char::is_whitespace) + { + return Cow::Borrowed(val); + } + let mut quoted = String::from("\""); + for c in val.chars() { + if c == '"' { + quoted.push_str("\\\""); + } else if c == '\n' { + quoted.push_str("\\n"); + } else if c == '\r' { + quoted.push_str("\\r"); + } else if c == '\t' { + quoted.push_str("\\t"); + } else if c == '\\' { + quoted.push('\\'); + } else if c.is_ascii_control() { + quoted.push_str(&format!("\\x{:02x}", c as u32)); + } else { + quoted.push(c); + } + } + quoted.push('"'); + Cow::Owned(quoted) + } + fn add_lines(lines: &mut Vec<String>, prefix: &str, conf: &Configuration) { + for (key, val) in conf.values.iter() { + lines.push(format!("{prefix}{key}: {}", format_value(&val.value))); + } + for (key, child) in conf.children.iter() { + add_lines(lines, &format!("{prefix}{key}."), child); + } + } + add_lines(&mut lines, "", self); + lines.sort(); + for line in lines { + writeln!(f, "{line}")?; + } + Ok(()) + } +} + #[non_exhaustive] #[derive(Debug)] pub enum Error { @@ -62,8 +111,71 @@ pub enum Error { } impl fmt::Display for Error { - fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { - todo!() + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::IO(message, io_err) => write!(f, "{message}: {io_err}"), + Self::IllegalCharacter(location, c) => { + write!(f, "{location}: illegal character {}", c.escape_debug()) + } + Self::InvalidUtf8(location) => write!(f, "{location}: invalid UTF-8"), + Self::BadInt(location, value) => { + write!(f, "{location}: invalid integer: {}", value.escape_debug()) + } + Self::BadUInt(location, value) => write!( + f, + "{location}: invalid (unsigned) integer: {}", + value.escape_debug() + ), + Self::BadFloat(location, value) => { + write!(f, "{location}: invalid number: {}", value.escape_debug()) + } + Self::BadBool(location, value) => write!( + f, + "{location}: value {} should be off/false/no or on/true/yes", + value.escape_debug() + ), + Self::UnmatchedLeftBrace(location) => write!(f, "{location}: [ has no matching ]"), + Self::InvalidKey(location, key) => { + write!(f, "{location}: invalid key {}", key.escape_debug()) + } + Self::InvalidValue(location) => write!(f, "{location}: value contains null characters"), + Self::InvalidLine(location) => write!( + f, + "{location}: line should either start with [ or contain =" + ), + Self::StrayCharsAfterQuotedString(location) => { + write!(f, "{location}: stray characters after string value") + } + Self::UnterminatedString(location, delimiter) => { + write!(f, "{location}: missing {delimiter} to close string") + } + Self::InvalidEscapeSequence(location, sequence) => write!( + f, + "{location}: invalid escape sequence {} (try using \\\\ instead of \\ maybe?)", + sequence.escape_debug() + ), + Self::Multiple(errs) => { + let mut first = true; + for err in errs { + if !first { + writeln!(f)?; + } + first = false; + write!(f, "{err}")?; + } + Ok(()) + } + } + } +} + +impl core::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + if let Error::IO(_, e) = self { + Some(e) + } else { + None + } } } @@ -484,6 +596,16 @@ impl Configuration { } node.clone() } + pub fn keys(&self) -> impl '_ + Iterator<Item = &str> { + self.values + .keys() + .chain( + self.children + .keys() + .filter(|&k| !self.values.contains_key(k)), + ) + .map(|x| x.as_ref()) + } fn get_val(&self, key: &str) -> Option<&Value> { let Some(last_dot) = key.rfind('.') else { return self.values.get(key); |