use crate::lex::{Spanned, Token, tokenize};
use crate::schema::ConfigError;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum Value {
Str(String),
Int(i64),
Bool(bool),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Item {
pub(crate) section: Option<String>,
pub(crate) key: String,
pub(crate) value: Value,
pub(crate) line: usize,
}
pub(crate) fn parse(src: &str) -> Result<Vec<Item>, ConfigError> {
let tokens = tokenize(src)?;
let mut p = Parser {
tokens,
pos: 0,
current_section: None,
items: Vec::new(),
};
p.parse_all()?;
Ok(p.items)
}
struct Parser {
tokens: Vec<Spanned>,
pos: usize,
current_section: Option<String>,
items: Vec<Item>,
}
impl Parser {
fn parse_all(&mut self) -> Result<(), ConfigError> {
while self.pos < self.tokens.len() {
match &self.tokens[self.pos].tok {
Token::Newline => {
self.pos += 1;
}
Token::LBracket => self.parse_section_header()?,
Token::Ident(_) => self.parse_assignment()?,
other => {
let s = &self.tokens[self.pos];
return Err(unexpected(s, format!("{other:?}")));
}
}
}
Ok(())
}
fn parse_section_header(&mut self) -> Result<(), ConfigError> {
self.pos += 1;
let name = match self.tokens.get(self.pos) {
Some(Spanned {
tok: Token::Ident(n),
..
}) => n.clone(),
Some(s) => return Err(unexpected(s, "expected section name".into())),
None => return Err(eof("expected section name")),
};
self.pos += 1;
match self.tokens.get(self.pos) {
Some(Spanned {
tok: Token::RBracket,
..
}) => self.pos += 1,
Some(s) => return Err(unexpected(s, "expected ']'".into())),
None => return Err(eof("expected ']'")),
}
self.expect_eol("after section header")?;
self.current_section = Some(name);
Ok(())
}
fn parse_assignment(&mut self) -> Result<(), ConfigError> {
let key_span = self.tokens[self.pos].clone();
let key = match key_span.tok {
Token::Ident(k) => k,
_ => unreachable!("parse_assignment called without Ident at head"),
};
let line = key_span.line;
self.pos += 1;
match self.tokens.get(self.pos) {
Some(Spanned {
tok: Token::Equals,
..
}) => self.pos += 1,
Some(s) => return Err(unexpected(s, "expected '='".into())),
None => return Err(eof("expected '='")),
}
let value_span = self
.tokens
.get(self.pos)
.cloned()
.ok_or_else(|| eof("expected value"))?;
let value = match value_span.tok {
Token::Str(s) => Value::Str(s),
Token::Int(n) => Value::Int(n),
Token::Bool(b) => Value::Bool(b),
ref other => {
return Err(unexpected(
&value_span,
format!("expected value, got {other:?}"),
));
}
};
self.pos += 1;
self.expect_eol("after value")?;
self.items.push(Item {
section: self.current_section.clone(),
key,
value,
line,
});
Ok(())
}
fn expect_eol(&mut self, ctx: &str) -> Result<(), ConfigError> {
match self.tokens.get(self.pos) {
None => Ok(()),
Some(Spanned {
tok: Token::Newline,
..
}) => {
self.pos += 1;
Ok(())
}
Some(s) => Err(unexpected(s, format!("expected newline {ctx}"))),
}
}
}
fn unexpected(s: &Spanned, msg: String) -> ConfigError {
ConfigError::Parse {
line: s.line,
col: s.col,
msg,
}
}
fn eof(msg: &str) -> ConfigError {
ConfigError::Parse {
line: 0,
col: 0,
msg: format!("unexpected end of input: {msg}"),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn parse_ok(src: &str) -> Vec<Item> {
parse(src).unwrap_or_else(|e| panic!("parse failed: {e}"))
}
#[test]
fn empty_input_parses_to_nothing() {
assert!(parse_ok("").is_empty());
assert!(parse_ok("\n\n\n").is_empty());
assert!(parse_ok("# only comments\n# more\n").is_empty());
}
#[test]
fn section_then_keys() {
let items = parse_ok(
"[server]\n\
bind = \"127.0.0.1\"\n\
port = 6004\n",
);
assert_eq!(items.len(), 2);
assert_eq!(items[0].section.as_deref(), Some("server"));
assert_eq!(items[0].key, "bind");
assert_eq!(items[0].value, Value::Str("127.0.0.1".into()));
assert_eq!(items[0].line, 2);
assert_eq!(items[1].key, "port");
assert_eq!(items[1].value, Value::Int(6004));
}
#[test]
fn key_before_any_section() {
let items = parse_ok("foo = true\n[s]\nbar = 1\n");
assert_eq!(items[0].section, None);
assert_eq!(items[1].section.as_deref(), Some("s"));
}
#[test]
fn last_line_without_newline() {
let items = parse_ok("[a]\nx = 1");
assert_eq!(items[0].key, "x");
}
#[test]
fn blank_lines_between_keys_ok() {
let items = parse_ok("[a]\n\nx = 1\n\n\ny = 2\n");
assert_eq!(items.len(), 2);
}
#[test]
fn duplicate_section_overwrites_current_section_pointer() {
let items = parse_ok("[a]\nx = 1\n[a]\ny = 2\n");
assert_eq!(items[0].section.as_deref(), Some("a"));
assert_eq!(items[1].section.as_deref(), Some("a"));
}
#[test]
fn missing_equals_errors() {
let err = parse("[s]\nkey 1\n").unwrap_err();
match err {
ConfigError::Parse { line, .. } => assert_eq!(line, 2),
_ => panic!("expected Parse"),
}
}
#[test]
fn missing_rbracket_errors() {
assert!(matches!(parse("[s\n").unwrap_err(), ConfigError::Parse { .. }));
}
#[test]
fn second_statement_on_same_line_errors() {
let err = parse("a = 1 b = 2\n").unwrap_err();
assert!(matches!(err, ConfigError::Parse { .. }));
}
#[test]
fn bare_section_header_is_ok() {
let items = parse_ok("[empty]\n");
assert!(items.is_empty());
}
}