use crate::error::{Error, Result};
use crate::lexer::{tokenize, Token};
use indexmap::IndexMap;
#[derive(Debug, Clone, PartialEq)]
pub enum Value {
Scalar(String),
Array(Vec<Value>),
Object(IndexMap<String, Value>),
}
pub fn parse(input: &str) -> Result<Value> {
let tokens = tokenize(input);
let mut pos = 0usize;
let map = parse_object(&tokens, &mut pos, true)?;
Ok(Value::Object(map))
}
fn peek(tokens: &[Token], pos: usize) -> &Token {
tokens.get(pos).unwrap_or(&Token::Eof)
}
fn advance(pos: &mut usize) {
*pos += 1;
}
fn expect_term(tokens: &[Token], pos: &mut usize) -> Result<String> {
match tokens.get(*pos) {
Some(Token::Term(s)) => {
let s = s.clone();
advance(pos);
Ok(s)
}
other => Err(Error::Parse(format!("expected term, got {other:?}"))),
}
}
fn parse_object(
tokens: &[Token],
pos: &mut usize,
top_level: bool,
) -> Result<IndexMap<String, Value>> {
let mut map = IndexMap::new();
loop {
match peek(tokens, *pos) {
Token::Eof => {
if top_level {
break;
}
return Err(Error::Parse("unexpected EOF inside object".to_owned()));
}
Token::Close => {
if top_level {
return Err(Error::Parse("unexpected '}'".to_owned()));
}
advance(pos); break;
}
Token::Term(_) => {
let key = expect_term(tokens, pos)?;
match peek(tokens, *pos) {
Token::Eq => {
advance(pos); let val = parse_value(tokens, pos)?;
if map.contains_key(&key) {
return Err(Error::Parse(format!("duplicate key '{key}'")));
}
map.insert(key, val);
}
Token::Open => {
advance(pos); let sub = parse_object(tokens, pos, false)?;
if map.contains_key(&key) {
return Err(Error::Parse(format!("duplicate key '{key}'")));
}
map.insert(key, Value::Object(sub));
}
other => {
return Err(Error::Parse(format!(
"expected '=' or '{{' after key '{key}', got {other:?}"
)));
}
}
}
other => {
return Err(Error::Parse(format!("unexpected token {other:?}")));
}
}
}
Ok(map)
}
fn parse_value(tokens: &[Token], pos: &mut usize) -> Result<Value> {
match peek(tokens, *pos) {
Token::Open => {
advance(pos); parse_array_or_object_list(tokens, pos)
}
Token::Term(_) => {
let s = expect_term(tokens, pos)?;
Ok(Value::Scalar(s))
}
other => Err(Error::Parse(format!("expected value, got {other:?}"))),
}
}
fn parse_array_or_object_list(tokens: &[Token], pos: &mut usize) -> Result<Value> {
let mut items: Vec<Value> = Vec::new();
loop {
match peek(tokens, *pos) {
Token::Close => {
advance(pos); break;
}
Token::Eof => {
return Err(Error::Parse("unexpected EOF inside array".to_owned()));
}
Token::Open => {
advance(pos); let sub = parse_object(tokens, pos, false)?;
items.push(Value::Object(sub));
}
Token::Term(_) => {
let s = expect_term(tokens, pos)?;
items.push(Value::Scalar(s));
}
other @ Token::Eq => {
return Err(Error::Parse(format!(
"unexpected token in array: {other:?}"
)));
}
}
}
Ok(Value::Array(items))
}
impl Value {
#[must_use]
pub fn as_bool(&self) -> Option<bool> {
if let Value::Scalar(s) = self {
match s.as_str() {
"true" => Some(true),
"false" => Some(false),
_ => None,
}
} else {
None
}
}
#[must_use]
pub fn as_i64(&self) -> Option<i64> {
if let Value::Scalar(s) = self {
s.parse().ok()
} else {
None
}
}
#[must_use]
pub fn as_f64(&self) -> Option<f64> {
if let Value::Scalar(s) = self {
s.parse().ok()
} else {
None
}
}
#[must_use]
pub fn is_null(&self) -> bool {
matches!(self, Value::Scalar(s) if s == "null")
}
#[must_use]
pub fn type_name(&self) -> &'static str {
match self {
Value::Scalar(_) => "scalar",
Value::Array(_) => "array",
Value::Object(_) => "object",
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn simple_kv() {
let v = parse("key = value\n").unwrap();
if let Value::Object(map) = v {
assert_eq!(map["key"], Value::Scalar("value".into()));
} else {
panic!("expected object");
}
}
#[test]
fn nested_object() {
let input = "db {\n host = localhost\n port = 5432\n}\n";
let v = parse(input).unwrap();
if let Value::Object(map) = v {
if let Value::Object(db) = &map["db"] {
assert_eq!(db["host"], Value::Scalar("localhost".into()));
assert_eq!(db["port"], Value::Scalar("5432".into()));
} else {
panic!("expected nested object");
}
} else {
panic!("expected object");
}
}
#[test]
fn array_of_scalars() {
let input = "tables = { Table1 Table2 }\n";
let v = parse(input).unwrap();
if let Value::Object(map) = v {
assert_eq!(
map["tables"],
Value::Array(vec![
Value::Scalar("Table1".into()),
Value::Scalar("Table2".into()),
])
);
} else {
panic!("expected object");
}
}
#[test]
fn number_scalar() {
let v = parse("port = 8080\n").unwrap();
if let Value::Object(map) = v {
assert_eq!(map["port"].as_i64(), Some(8080));
}
}
#[test]
fn bool_scalar() {
let v = parse("enabled = true\n").unwrap();
if let Value::Object(map) = v {
assert_eq!(map["enabled"].as_bool(), Some(true));
}
}
}