blob: d158345a19707bdc6c11bc5a25527f98d7c6e286 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use crate::parser_common::{
compound_identifier, condition_value, many_until_eof, map_err, ws, BindParserError,
CompoundIdentifier, NomSpan, Value,
};
use nom::{bytes::complete::tag, sequence::separated_pair, IResult};
use std::str::FromStr;
#[derive(Debug, Clone, PartialEq)]
pub struct DeviceSpecification {
pub properties: Vec<Property>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Property {
pub key: CompoundIdentifier,
pub value: Value,
}
impl FromStr for DeviceSpecification {
type Err = BindParserError;
fn from_str(input: &str) -> Result<Self, Self::Err> {
match device_specification(NomSpan::new(input)) {
Ok((_, spec)) => Ok(spec),
Err(nom::Err::Error(e)) => Err(e),
Err(nom::Err::Failure(e)) => Err(e),
Err(nom::Err::Incomplete(_)) => {
unreachable!("Parser should never generate Incomplete errors")
}
}
}
}
impl DeviceSpecification {
pub fn new() -> Self {
DeviceSpecification { properties: Vec::new() }
}
pub fn add_property(&mut self, key: &str, value: &str) -> Result<(), BindParserError> {
match property_from_pair(NomSpan::new(key), NomSpan::new(value)) {
Ok((_, property)) => {
self.properties.push(property);
Ok(())
}
Err(nom::Err::Error(e)) => Err(e),
Err(nom::Err::Failure(e)) => Err(e),
Err(nom::Err::Incomplete(_)) => {
unreachable!("Parser should never generate Incomplete errors")
}
}
}
}
fn property_from_pair<'a, 'b>(
key: NomSpan<'a>,
value: NomSpan<'b>,
) -> IResult<(NomSpan<'a>, NomSpan<'b>), Property, BindParserError> {
let (key_remaining, key) = compound_identifier(key)?;
if !key_remaining.fragment().is_empty() {
return Err(nom::Err::Error(BindParserError::Eof(key_remaining.fragment().to_string())));
}
let (value_remaining, value) = condition_value(value)?;
if !value_remaining.fragment().is_empty() {
return Err(nom::Err::Error(BindParserError::Eof(value_remaining.fragment().to_string())));
}
Ok(((key_remaining, value_remaining), Property { key, value }))
}
fn property(input: NomSpan) -> IResult<NomSpan, Property, BindParserError> {
let key = ws(compound_identifier);
let separator = ws(map_err(tag("="), BindParserError::Assignment));
let (input, (key, value)) = separated_pair(key, separator, condition_value)(input)?;
Ok((input, Property { key, value }))
}
fn device_specification(input: NomSpan) -> IResult<NomSpan, DeviceSpecification, BindParserError> {
let (input, properties) = many_until_eof(ws(property))(input)?;
Ok((input, DeviceSpecification { properties }))
}
#[cfg(test)]
mod test {
use super::*;
use crate::make_identifier;
use crate::parser_common::test::check_result;
mod properties {
use super::*;
#[test]
fn simple() {
check_result(
property(NomSpan::new("abc = 5")),
"",
Property { key: make_identifier!["abc"], value: Value::NumericLiteral(5) },
);
}
#[test]
fn simple_from_pair() {
assert_eq!(
property_from_pair(NomSpan::new("abc"), NomSpan::new("5")).unwrap().1,
Property { key: make_identifier!["abc"], value: Value::NumericLiteral(5) },
);
}
#[test]
fn invalid() {
assert_eq!(
property(NomSpan::new("abc 5")),
Err(nom::Err::Error(BindParserError::Assignment("5".to_string())))
);
assert_eq!(
property(NomSpan::new("abc =")),
Err(nom::Err::Error(BindParserError::ConditionValue("".to_string())))
);
assert_eq!(
property(NomSpan::new("= 5")),
Err(nom::Err::Error(BindParserError::Identifier("= 5".to_string())))
);
}
#[test]
fn invalid_from_pair() {
assert_eq!(
property_from_pair(NomSpan::new("abc def"), NomSpan::new("5")),
Err(nom::Err::Error(BindParserError::Eof(" def".to_string())))
);
assert_eq!(
property_from_pair(NomSpan::new("_abc"), NomSpan::new("5")),
Err(nom::Err::Error(BindParserError::Identifier("_abc".to_string())))
);
assert_eq!(
property_from_pair(NomSpan::new("abc"), NomSpan::new("5 42")),
Err(nom::Err::Error(BindParserError::Eof(" 42".to_string())))
);
assert_eq!(
property_from_pair(NomSpan::new("abc"), NomSpan::new("@")),
Err(nom::Err::Error(BindParserError::ConditionValue("@".to_string())))
);
}
#[test]
fn empty() {
assert_eq!(
property(NomSpan::new("")),
Err(nom::Err::Error(BindParserError::Identifier("".to_string())))
);
}
#[test]
fn empty_from_pair() {
assert_eq!(
property_from_pair(NomSpan::new(""), NomSpan::new("5")),
Err(nom::Err::Error(BindParserError::Identifier("".to_string())))
);
assert_eq!(
property_from_pair(NomSpan::new("abc"), NomSpan::new("")),
Err(nom::Err::Error(BindParserError::ConditionValue("".to_string())))
);
}
}
mod device_specifications {
use super::*;
#[test]
fn simple() {
check_result(
device_specification(NomSpan::new("abc = 5\nxyz = true")),
"",
DeviceSpecification {
properties: vec![
Property { key: make_identifier!["abc"], value: Value::NumericLiteral(5) },
Property { key: make_identifier!["xyz"], value: Value::BoolLiteral(true) },
],
},
);
}
#[test]
fn empty() {
check_result(
device_specification(NomSpan::new("")),
"",
DeviceSpecification { properties: Vec::new() },
);
}
}
}