pub mod ast; pub mod parser; pub use ast::{Number, Value}; pub use parser::{parse, apply_builtin_types, ParseError, ParseResult}; /// Parse and stringify in one go (for convenience) pub fn stringify(input: &str, apply_types: bool) -> ParseResult { let mut values = parse(input)?; if apply_types { values = values.into_iter().map(apply_builtin_types).collect(); } if values.len() == 1 { Ok(values[0].to_uson_string()) } else { let strings: Vec = values.iter().map(|v| v.to_uson_string()).collect(); Ok(strings.join(" ")) } } /// Parse and convert to JSON string pub fn to_json(input: &str, apply_types: bool, pretty: bool) -> ParseResult { let mut values = parse(input)?; if apply_types { values = values.into_iter().map(apply_builtin_types).collect(); } let json_value = serde_json::to_value(&values) .map_err(|e| ParseError::NumberError(e.to_string()))?; if pretty { serde_json::to_string_pretty(&json_value) .map_err(|e| ParseError::NumberError(e.to_string())) } else { serde_json::to_string(&json_value) .map_err(|e| ParseError::NumberError(e.to_string())) } } #[cfg(test)] mod tests { use super::*; use pretty_assertions::assert_eq; #[test] fn test_null() { let result = parse("null").unwrap(); assert_eq!(result, vec![Value::Null]); } #[test] fn test_bool() { assert_eq!(parse("true").unwrap(), vec![Value::Bool(true)]); assert_eq!(parse("false").unwrap(), vec![Value::Bool(false)]); } #[test] fn test_numbers() { assert_eq!( parse("42").unwrap(), vec![Value::Number(Number::Integer(42))] ); assert_eq!( parse("3.14").unwrap(), vec![Value::Number(Number::Float(3.14))] ); assert_eq!( parse("-10").unwrap(), vec![Value::Number(Number::Integer(-10))] ); } #[test] fn test_strings() { assert_eq!( parse("\"hello\"").unwrap(), vec![Value::String("hello".to_string())] ); assert_eq!( parse("'world'").unwrap(), vec![Value::String("world".to_string())] ); assert_eq!( parse("unquoted").unwrap(), vec![Value::String("unquoted".to_string())] ); } #[test] fn test_array() { let result = parse("[1, 2, 3]").unwrap(); assert_eq!( result, vec![Value::Array(vec![ Value::Number(Number::Integer(1)), Value::Number(Number::Integer(2)), Value::Number(Number::Integer(3)) ])] ); } #[test] fn test_array_no_commas() { let result = parse("[1 2 3]").unwrap(); assert_eq!( result, vec![Value::Array(vec![ Value::Number(Number::Integer(1)), Value::Number(Number::Integer(2)), Value::Number(Number::Integer(3)) ])] ); } #[test] fn test_object() { let result = parse(r#"{"key": "value"}"#).unwrap(); let mut expected = std::collections::HashMap::new(); expected.insert("key".to_string(), Value::String("value".to_string())); assert_eq!(result, vec![Value::Object(expected)]); } #[test] fn test_comment() { let result = parse("# comment\n42").unwrap(); assert_eq!(result, vec![Value::Number(Number::Integer(42))]); } #[test] fn test_typed() { let result = parse("date!\"2024-01-01\"").unwrap(); assert_eq!( result, vec![Value::Typed { type_name: "date".to_string(), value: Box::new(Value::String("2024-01-01".to_string())) }] ); } #[test] fn test_assign() { let result = parse(r#"name: "John""#).unwrap(); let mut expected = std::collections::HashMap::new(); expected.insert("name".to_string(), Value::String("John".to_string())); assert_eq!(result, vec![Value::Object(expected)]); } #[test] fn test_multiple_expressions() { let result = parse("1 2 3").unwrap(); assert_eq!( result, vec![ Value::Number(Number::Integer(1)), Value::Number(Number::Integer(2)), Value::Number(Number::Integer(3)) ] ); } #[test] fn test_stringify_simple() { let value = Value::String("hello".to_string()); assert_eq!(value.to_uson_string(), "hello"); } #[test] fn test_stringify_number() { let value = Value::Number(Number::Integer(42)); assert_eq!(value.to_uson_string(), "42"); } #[test] fn test_stringify_array() { let value = Value::Array(vec![ Value::Number(Number::Integer(1)), Value::Number(Number::Integer(2)), Value::Number(Number::Integer(3)), ]); assert_eq!(value.to_uson_string(), "[1 2 3]"); } #[test] fn test_stringify_object() { let mut obj = std::collections::HashMap::new(); obj.insert("name".to_string(), Value::String("John".to_string())); obj.insert("age".to_string(), Value::Number(Number::Integer(30))); let value = Value::Object(obj); let result = value.to_uson_string(); // Note: HashMap doesn't guarantee order, but our stringify sorts keys assert!(result.contains("age:30")); assert!(result.contains("name:John")); } #[test] fn test_stringify_needs_quotes() { let value = Value::String("hello world".to_string()); assert_eq!(value.to_uson_string(), "\"hello world\""); } #[test] fn test_roundtrip() { let input = "name:John age:30 active:true"; let parsed = parse(input).unwrap(); let stringified: Vec = parsed.iter().map(|v| v.to_uson_string()).collect(); let result = stringified.join(" "); // Parse again let reparsed = parse(&result).unwrap(); assert_eq!(parsed, reparsed); } }