use crate::error::{Error, Result};
use crate::value::Value;
use std::collections::BTreeMap;
pub fn parse(source: &str) -> Result<Value> {
let mut parser = ConfParser::new(source);
parser.parse()
}
struct ConfParser<'a> {
input: &'a str,
position: usize,
line: usize,
column: usize,
}
impl<'a> ConfParser<'a> {
fn new(input: &'a str) -> Self {
Self {
input,
position: 0,
line: 1,
column: 1,
}
}
fn parse(&mut self) -> Result<Value> {
let mut root = BTreeMap::new();
let mut current_section = None;
while !self.is_at_end() {
self.skip_whitespace_and_comments();
if self.is_at_end() {
break;
}
if self.peek() == Some('[') {
current_section = Some(self.parse_section_header()?);
continue;
}
let (key, value) = self.parse_key_value()?;
match ¤t_section {
Some(section) => {
let section_table = root
.entry(section.clone())
.or_insert_with(|| Value::table(BTreeMap::new()));
if let Value::Table(table) = section_table {
table.insert(key, value);
}
}
None => {
root.insert(key, value);
}
}
}
Ok(Value::table(root))
}
fn parse_section_header(&mut self) -> Result<String> {
self.expect('[')?;
let start = self.position;
while let Some(ch) = self.peek() {
if ch == ']' {
break;
}
if ch == '\n' {
return Err(Error::parse(
"Unterminated section header",
self.line,
self.column,
));
}
self.advance();
}
let section_name = self.input[start..self.position].trim().to_string();
self.expect(']')?;
Ok(section_name)
}
fn parse_key_value(&mut self) -> Result<(String, Value)> {
let key = self.parse_key()?;
self.skip_whitespace();
self.expect('=')?;
self.skip_whitespace();
let value = self.parse_value()?;
Ok((key, value))
}
fn parse_key(&mut self) -> Result<String> {
let start = self.position;
while let Some(ch) = self.peek() {
if ch.is_alphanumeric() || ch == '_' || ch == '-' || ch == '.' {
self.advance();
} else {
break;
}
}
if start == self.position {
return Err(Error::parse("Expected key name", self.line, self.column));
}
Ok(self.input[start..self.position].to_string())
}
fn parse_value(&mut self) -> Result<Value> {
self.skip_whitespace();
match self.peek() {
Some('"') => self.parse_quoted_string(),
Some('\'') => self.parse_single_quoted_string(),
Some('[') => self.parse_array(),
_ => {
self.parse_unquoted_value()
}
}
}
fn parse_quoted_string(&mut self) -> Result<Value> {
self.expect('"')?;
let _start = self.position;
let mut result = String::new();
while let Some(ch) = self.peek() {
if ch == '"' {
break;
}
if ch == '\\' {
self.advance();
match self.peek() {
Some('n') => result.push('\n'),
Some('t') => result.push('\t'),
Some('r') => result.push('\r'),
Some('\\') => result.push('\\'),
Some('"') => result.push('"'),
Some(other) => {
result.push('\\');
result.push(other);
}
None => {
return Err(Error::parse(
"Unterminated escape sequence",
self.line,
self.column,
))
}
}
self.advance();
} else {
result.push(ch);
self.advance();
}
}
self.expect('"')?;
Ok(Value::string(result))
}
fn parse_single_quoted_string(&mut self) -> Result<Value> {
self.expect('\'')?;
let start = self.position;
while let Some(ch) = self.peek() {
if ch == '\'' {
break;
}
self.advance();
}
let content = self.input[start..self.position].to_string();
self.expect('\'')?;
Ok(Value::string(content))
}
fn parse_array(&mut self) -> Result<Value> {
self.expect('[')?;
let mut items = Vec::new();
self.skip_whitespace();
if self.peek() == Some(']') {
self.advance();
return Ok(Value::array(items));
}
loop {
items.push(self.parse_value()?);
self.skip_whitespace();
match self.peek() {
Some(',') => {
self.advance();
self.skip_whitespace();
}
Some(']') => {
self.advance();
break;
}
_ => {
return Err(Error::parse(
"Expected ',' or ']' in array",
self.line,
self.column,
))
}
}
}
Ok(Value::array(items))
}
#[allow(dead_code)]
fn parse_number(&mut self) -> Result<Value> {
let start = self.position;
let mut has_dot = false;
if self.peek() == Some('-') || self.peek() == Some('+') {
self.advance();
}
while let Some(ch) = self.peek() {
if ch.is_ascii_digit() {
self.advance();
} else if ch == '.' && !has_dot {
has_dot = true;
self.advance();
} else {
break;
}
}
let number_str = &self.input[start..self.position];
if has_dot {
number_str.parse::<f64>().map(Value::float).map_err(|_| {
Error::parse(
format!("Invalid float: {number_str}"),
self.line,
self.column,
)
})
} else {
number_str.parse::<i64>().map(Value::integer).map_err(|_| {
Error::parse(
format!("Invalid integer: {number_str}"),
self.line,
self.column,
)
})
}
}
fn parse_unquoted_value(&mut self) -> Result<Value> {
let start = self.position;
while let Some(ch) = self.peek() {
if ch == '\n' || ch == '\r' || ch == '#' {
break;
}
self.advance();
}
let raw_value = self.input[start..self.position].trim();
if raw_value.is_empty() {
return Ok(Value::null());
}
match raw_value.to_lowercase().as_str() {
"true" | "yes" | "on" => return Ok(Value::bool(true)),
"false" | "no" | "off" => return Ok(Value::bool(false)),
"null" | "nil" | "" => return Ok(Value::null()),
_ => {}
}
if raw_value.contains(' ') || raw_value.contains(',') {
let items: Vec<Value> = raw_value
.split([' ', ','])
.map(|s| s.trim())
.filter(|s| !s.is_empty())
.map(|s| self.parse_simple_value(s))
.collect::<Result<Vec<_>>>()?;
if items.len() > 1 {
return Ok(Value::array(items));
}
}
self.parse_simple_value(raw_value)
}
fn parse_simple_value(&self, value: &str) -> Result<Value> {
if let Ok(i) = value.parse::<i64>() {
return Ok(Value::integer(i));
}
if let Ok(f) = value.parse::<f64>() {
return Ok(Value::float(f));
}
Ok(Value::string(value.to_string()))
}
fn skip_whitespace(&mut self) {
while let Some(ch) = self.peek() {
if ch == ' ' || ch == '\t' {
self.advance();
} else {
break;
}
}
}
fn skip_whitespace_and_comments(&mut self) {
loop {
self.skip_whitespace();
if self.peek() == Some('#') {
while let Some(ch) = self.peek() {
self.advance();
if ch == '\n' {
break;
}
}
continue;
}
if self.peek() == Some('\n') || self.peek() == Some('\r') {
self.advance();
continue;
}
break;
}
}
fn peek(&self) -> Option<char> {
self.input.chars().nth(self.position)
}
fn advance(&mut self) -> Option<char> {
if let Some(ch) = self.peek() {
self.position += 1;
if ch == '\n' {
self.line += 1;
self.column = 1;
} else {
self.column += 1;
}
Some(ch)
} else {
None
}
}
fn expect(&mut self, expected: char) -> Result<()> {
match self.advance() {
Some(ch) if ch == expected => Ok(()),
Some(ch) => Err(Error::parse(
format!("Expected '{expected}', found '{ch}'"),
self.line,
self.column,
)),
None => Err(Error::parse(
format!("Expected '{expected}', found end of input"),
self.line,
self.column,
)),
}
}
fn is_at_end(&self) -> bool {
self.position >= self.input.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_simple_key_value() {
let config = parse("key = value").unwrap();
assert_eq!(config.get("key").unwrap().as_string().unwrap(), "value");
}
#[test]
fn test_numbers() {
let config = parse("int = 42\nfloat = 1.234").unwrap();
assert_eq!(config.get("int").unwrap().as_integer().unwrap(), 42);
assert_eq!(config.get("float").unwrap().as_float().unwrap(), 1.234);
}
#[test]
fn test_booleans() {
let config = parse("bool1 = true\nbool2 = false").unwrap();
assert!(config.get("bool1").unwrap().as_bool().unwrap());
assert!(!config.get("bool2").unwrap().as_bool().unwrap());
}
#[test]
fn test_quoted_strings() {
let config = parse(r#"quoted = "hello world""#).unwrap();
assert_eq!(
config.get("quoted").unwrap().as_string().unwrap(),
"hello world"
);
}
#[test]
fn test_sections() {
let config = parse("[section]\nkey = value").unwrap();
assert_eq!(
config.get("section.key").unwrap().as_string().unwrap(),
"value"
);
}
#[test]
fn test_arrays() {
let config = parse("arr = item1 item2 item3").unwrap();
let arr = config.get("arr").unwrap().as_array().unwrap();
assert_eq!(arr.len(), 3);
assert_eq!(arr[0].as_string().unwrap(), "item1");
}
#[test]
fn test_comments() {
let config = parse("# This is a comment\nkey = value # inline comment").unwrap();
assert_eq!(config.get("key").unwrap().as_string().unwrap(), "value");
}
}