use crate::parser::json_value::JsonValueParser;
use crate::ParseError;
use serde_json::Value;
#[derive(Debug, Clone, PartialEq)]
pub enum MetadataStatement {
Current {
key: String,
value: Value,
},
Targeted {
target: String,
key: String,
value: Value,
},
}
pub struct MetadataParser<'a> {
input: &'a str,
position: usize,
}
impl<'a> MetadataParser<'a> {
pub fn new(input: &'a str) -> Self {
Self { input, position: 0 }
}
pub fn parse(&mut self) -> Result<MetadataStatement, ParseError> {
self.skip_whitespace();
if !self.consume_char('#') {
return Err(ParseError::Expected {
expected: "'#'",
found: self.current_char().unwrap_or('\0').to_string(),
position: self.position,
});
}
let _checkpoint = self.position;
let target = self.parse_optional_target()?;
if let Some(target) = target {
self.skip_whitespace();
if !self.consume_char(':') {
return Err(ParseError::Expected {
expected: "':'",
found: self.current_char().unwrap_or('\0').to_string(),
position: self.position,
});
}
self.skip_whitespace();
let key = self.parse_key()?;
self.skip_whitespace();
if !self.consume_char('=') {
return Err(ParseError::Expected {
expected: "'='",
found: self.current_char().unwrap_or('\0').to_string(),
position: self.position,
});
}
self.skip_whitespace();
let remaining_input = &self.input[self.position..];
let mut json_parser = JsonValueParser::new(remaining_input);
let value = json_parser.parse()?;
Ok(MetadataStatement::Targeted { target, key, value })
} else {
let key = self.parse_key()?;
self.skip_whitespace();
if !self.consume_char('=') {
return Err(ParseError::Expected {
expected: "'='",
found: self.current_char().unwrap_or('\0').to_string(),
position: self.position,
});
}
self.skip_whitespace();
let remaining_input = &self.input[self.position..];
let mut json_parser = JsonValueParser::new(remaining_input);
let value = json_parser.parse()?;
Ok(MetadataStatement::Current { key, value })
}
}
fn parse_optional_target(&mut self) -> Result<Option<String>, ParseError> {
let start_pos = self.position;
while let Some(ch) = self.current_char() {
if ch == ':' {
let target = self.input[start_pos..self.position].trim().to_string();
if target.is_empty() {
return Err(ParseError::Expected {
expected: "target name",
found: "empty string".to_string(),
position: start_pos,
});
}
return Ok(Some(target));
} else if ch == '=' {
self.position = start_pos; return Ok(None);
}
self.advance();
}
self.position = start_pos; Ok(None)
}
fn parse_key(&mut self) -> Result<String, ParseError> {
let start_pos = self.position;
if !self
.current_char()
.is_some_and(|ch| ch.is_alphanumeric() || ch == '_')
{
return Err(ParseError::Expected {
expected: "metadata key",
found: self.current_char().unwrap_or('\0').to_string(),
position: self.position,
});
}
while self
.current_char()
.is_some_and(|ch| ch.is_alphanumeric() || ch == '_' || ch == '.')
{
self.advance();
}
let key = self.input[start_pos..self.position].to_string();
if key.is_empty() {
return Err(ParseError::Expected {
expected: "metadata key",
found: "empty string".to_string(),
position: start_pos,
});
}
Ok(key)
}
fn skip_whitespace(&mut self) {
while self.current_char().is_some_and(|ch| ch.is_whitespace()) {
self.advance();
}
}
fn current_char(&self) -> Option<char> {
self.input.chars().nth(self.position)
}
fn advance(&mut self) {
if self.position < self.input.len() {
self.position += 1;
}
}
fn consume_char(&mut self, expected: char) -> bool {
if self.current_char() == Some(expected) {
self.advance();
true
} else {
false
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_parse_current_metadata_string() {
let mut parser = MetadataParser::new(r#"#doc.label="Patch A""#);
let result = parser.parse().unwrap();
match result {
MetadataStatement::Current { key, value } => {
assert_eq!(key, "doc.label");
assert_eq!(value, json!("Patch A"));
}
_ => panic!("Expected Current metadata"),
}
}
#[test]
fn test_parse_current_metadata_number() {
let mut parser = MetadataParser::new("#logic.clock_hz=4");
let result = parser.parse().unwrap();
match result {
MetadataStatement::Current { key, value } => {
assert_eq!(key, "logic.clock_hz");
assert_eq!(value, json!(4));
}
_ => panic!("Expected Current metadata"),
}
}
#[test]
fn test_parse_targeted_metadata() {
let mut parser = MetadataParser::new(r#"#cpu.core:logic.clock_hz=4"#);
let result = parser.parse().unwrap();
match result {
MetadataStatement::Targeted { target, key, value } => {
assert_eq!(target, "cpu.core");
assert_eq!(key, "logic.clock_hz");
assert_eq!(value, json!(4));
}
_ => panic!("Expected Targeted metadata"),
}
}
#[test]
fn test_parse_wildcard_metadata() {
let mut parser = MetadataParser::new(r#"#cpu.*:power.budget="low""#);
let result = parser.parse().unwrap();
match result {
MetadataStatement::Targeted { target, key, value } => {
assert_eq!(target, "cpu.*");
assert_eq!(key, "power.budget");
assert_eq!(value, json!("low"));
}
_ => panic!("Expected Targeted metadata"),
}
}
#[test]
fn test_parse_global_metadata() {
let mut parser = MetadataParser::new("#$global:io.bus_width=8");
let result = parser.parse().unwrap();
match result {
MetadataStatement::Targeted { target, key, value } => {
assert_eq!(target, "$global");
assert_eq!(key, "io.bus_width");
assert_eq!(value, json!(8));
}
_ => panic!("Expected Targeted metadata"),
}
}
#[test]
fn test_parse_with_whitespace() {
let mut parser = MetadataParser::new(r#"# cpu.core : logic.clock_hz = 4 "#);
let result = parser.parse().unwrap();
match result {
MetadataStatement::Targeted { target, key, value } => {
assert_eq!(target, "cpu.core");
assert_eq!(key, "logic.clock_hz");
assert_eq!(value, json!(4));
}
_ => panic!("Expected Targeted metadata"),
}
}
#[test]
fn test_parse_complex_json() {
let mut parser = MetadataParser::new(r#"#config={"enabled": true, "count": 42}"#);
let result = parser.parse().unwrap();
match result {
MetadataStatement::Current { key, value } => {
assert_eq!(key, "config");
assert_eq!(value, json!({"enabled": true, "count": 42}));
}
_ => panic!("Expected Current metadata"),
}
}
#[test]
fn test_parse_error_missing_hash() {
let mut parser = MetadataParser::new("key=value");
let result = parser.parse();
assert!(result.is_err());
}
#[test]
fn test_parse_error_missing_equals() {
let mut parser = MetadataParser::new("#key");
let result = parser.parse();
assert!(result.is_err());
}
#[test]
fn test_parse_error_invalid_json() {
let mut parser = MetadataParser::new("#key=invalid_json");
let result = parser.parse();
assert!(result.is_err());
}
}