#[derive(Clone, Debug, PartialEq)]
pub struct Pair {
pub key: String,
pub value: Value,
}
#[derive(Clone, Debug, PartialEq)]
pub enum Value {
Dict(Vec<Value>),
Pair(Box<Pair>),
Str(String),
ZStr(String),
Int(i64),
Float(f64),
Bool(bool),
}
impl Value {
pub fn as_dict(&self) -> Option<&[Value]> {
if let Self::Dict(dict) = self {
Some(dict.as_slice())
} else {
None
}
}
pub fn as_pair(&self) -> Option<&Pair> {
if let Self::Pair(p) = self {
Some(p.as_ref())
} else {
None
}
}
pub fn as_str(&self) -> Option<&str> {
match self {
Self::Str(s) | Self::ZStr(s) => Some(s.as_str()),
_ => None,
}
}
pub fn as_bool(&self) -> Option<bool> {
if let Self::Bool(b) = self {
Some(*b)
} else {
None
}
}
pub fn as_float<T>(&self) -> Option<T>
where
T: std::convert::From<f64>,
{
match *self {
Self::Int(i) => Some((i as f64).into()),
Self::Float(f) => Some(f.into()),
_ => None,
}
}
pub fn as_int<T>(&self) -> Option<T>
where
T: std::convert::From<i64>,
{
match *self {
Self::Int(i) => Some(i.into()),
Self::Float(f) => Some((f as i64).into()),
_ => None,
}
}
pub fn from_string(s: &str) -> Result<Value> {
lron::root(s)
}
}
type Result<T> = std::result::Result<T, peg::error::ParseError<peg::str::LineCol>>;
peg::parser! {grammar lron() for str {
use std::str::FromStr;
pub rule root() -> Value
= key:identifier() _() "=" _() value:array() _()
{ Value::Pair(Box::new(Pair{key, value: Value::Dict(value)})) }
rule array() -> Vec<Value>
= "{" _() v:(value() ** (_() "," _())) _()(",")? _() "}" { v }
rule bracket_key() -> String
= key:string_literal()
{ key.to_owned() } /
key:int()
{ key.to_string() }
rule pair() -> Pair
= key:identifier() _() "=" _() value:value()
{ Pair { key, value } } /
"[" key:bracket_key() "]" _() "=" _() value:value()
{ Pair { key, value } }
rule value() -> Value
= a:array() { Value::Dict(a) } /
p:pair() { Value::Pair(Box::new(p)) } /
i:int() { Value::Int(i) } /
b:bool() { Value::Bool(b) } /
f:float() { Value::Float(f) } /
s:string_literal() { Value::Str(s) } /
a:array() { Value::Dict(a) } /
z:zstr() { Value::ZStr(z) }
rule int() -> i64
= n:$("-"? ['0'..='9']+) !"." {? i64::from_str(n).or(Err("out of range for i32")) } / expected!("integer")
rule bool() -> bool
= "true" { true } / "false" { false }
rule float() -> f64
= f:$("-"? ['0'..='9']+ "." ['0'..='9']+) {? f64::from_str(f).or(Err("invalid float")) } / expected!("floating point")
rule identifier() -> String
= s:$(['a'..='z' | 'A'..='Z' | '0'..='9' | '_']+) { s.to_owned() } / expected!("identifier")
rule escape() -> &'static str
= "\\\"" { "\"" } / "\\\n" { "\n" }
rule string_literal() -> String
= "\"" s:((escape() / $(!['"'][_]))*) "\"" { s.join("") }
rule zstr() -> String
= "ZSTR" _() s:string_literal() { s }
rule _() = quiet!{[' ' | '\r' | '\n' | '\t']*}
}}
#[cfg(test)]
mod tests {
use super::Value;
#[test]
fn test_long_int() {
const DATA: &str = include_str!("../data/volumeinfo.lron");
let _ = Value::from_string(DATA).expect("Couldn't parse data");
}
#[test]
fn test_int_key() {
const DATA: &str = include_str!("../data/metadataThatTriggersRepublish.lron");
let v = Value::from_string(DATA).expect("Couldn't parse data");
if let Some(p) = v.as_pair()
&& let Some(dict) = p.value.as_dict()
{
if let Some(p) = dict[0].as_pair() {
assert_eq!(&p.key, "800348");
} else {
unreachable!();
}
} else {
unreachable!();
}
}
#[test]
fn test_parser() {
const DATA: &str = include_str!("../data/test_lron");
let r = Value::from_string(DATA);
assert!(r.is_ok());
let o = r.unwrap();
assert!(matches!(o, Value::Pair(_)));
if let Some(p) = o.as_pair() {
assert_eq!(p.key, "s");
assert!(matches!(p.value, Value::Dict(_)));
if let Some(d) = p.value.as_dict() {
assert_eq!(d.len(), 2);
assert!(matches!(d[0], Value::Dict(_)));
if let Some(d) = d[0].as_dict() {
assert_eq!(d.len(), 5);
assert!(matches!(d[0], Value::Pair(_)));
assert!(matches!(d[1], Value::Pair(_)));
assert!(matches!(d[2], Value::Pair(_)));
assert!(matches!(d[3], Value::Pair(_)));
assert!(matches!(d[4], Value::Pair(_)));
if let Some(p) = d[4].as_pair() {
assert_eq!(p.key, "someOther");
if let Some(value) = p.value.as_str() {
let r2 = Value::from_string(value);
assert!(r2.is_ok());
}
assert_eq!(
p.value.as_str(),
Some(
"anObject = {\n\
key = \"lr\",\n\
}\n"
)
);
}
}
assert!(matches!(d[1], Value::Pair(_)));
if let Some(p) = d[1].as_pair() {
assert_eq!(p.key, "combine");
assert_eq!(p.value.as_str(), Some("intersect"));
}
}
} else {
unreachable!();
}
}
}