blob: 314cf813294f0e28705bae96e1d692c4940768c4 [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::{
bool_literal, compound_identifier, identifier, many_until_eof, map_err, numeric_literal,
string_literal, using_list, ws, BindParserError, CompoundIdentifier, Include, NomSpan,
};
use nom::{
branch::alt,
bytes::complete::{tag, take_until},
combinator::{map, opt, value},
multi::separated_nonempty_list,
sequence::{delimited, separated_pair, terminated, tuple},
IResult,
};
use std::convert::TryFrom;
#[derive(Debug, PartialEq)]
pub struct Ast {
pub name: CompoundIdentifier,
pub using: Vec<Include>,
pub declarations: Vec<Declaration>,
}
#[derive(Debug, PartialEq)]
pub struct Declaration {
pub identifier: CompoundIdentifier,
pub value_type: ValueType,
pub extends: bool,
pub values: Vec<Value>,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum ValueType {
Number,
Str,
Bool,
Enum,
}
#[derive(Debug, PartialEq)]
pub enum Value {
Number(String, u64),
Str(String, String),
Bool(String, bool),
Enum(String),
}
impl TryFrom<&str> for Ast {
type Error = BindParserError;
fn try_from(input: &str) -> Result<Self, Self::Error> {
match library(NomSpan::new(input)) {
Ok((_, ast)) => Ok(ast),
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 Value {
pub fn identifier(&self) -> &str {
match self {
Value::Number(identifier, _)
| Value::Str(identifier, _)
| Value::Bool(identifier, _)
| Value::Enum(identifier) => &identifier,
}
}
}
fn keyword_extend(input: NomSpan) -> IResult<NomSpan, NomSpan, BindParserError> {
ws(tag("extend"))(input)
}
fn keyword_uint(input: NomSpan) -> IResult<NomSpan, ValueType, BindParserError> {
value(ValueType::Number, ws(map_err(tag("uint"), BindParserError::Type)))(input)
}
fn keyword_string(input: NomSpan) -> IResult<NomSpan, ValueType, BindParserError> {
value(ValueType::Str, ws(map_err(tag("string"), BindParserError::Type)))(input)
}
fn keyword_bool(input: NomSpan) -> IResult<NomSpan, ValueType, BindParserError> {
value(ValueType::Bool, ws(map_err(tag("bool"), BindParserError::Type)))(input)
}
fn keyword_enum(input: NomSpan) -> IResult<NomSpan, ValueType, BindParserError> {
value(ValueType::Enum, ws(map_err(tag("enum"), BindParserError::Type)))(input)
}
fn value_list<'a, O, F>(
f: F,
) -> impl Fn(NomSpan<'a>) -> IResult<NomSpan<'a>, Vec<O>, BindParserError>
where
F: Fn(NomSpan<'a>) -> IResult<NomSpan<'a>, O, BindParserError>,
{
move |input: NomSpan<'a>| {
let separator = || ws(map_err(tag(","), BindParserError::ListSeparator));
let values = separated_nonempty_list(separator(), |s| f(s));
// Lists may optionally be terminated by an additional trailing separator.
let values = terminated(values, opt(separator()));
// First consume all input until ';'. This simplifies the error handling since a semicolon
// is mandatory, but a list of values is optional.
let (input, vals_input) =
map_err(terminated(take_until(";"), tag(";")), BindParserError::Semicolon)(input)?;
if vals_input.fragment().is_empty() {
return Ok((input, Vec::new()));
}
let list_start = map_err(tag("{"), BindParserError::ListStart);
let list_end = map_err(tag("}"), BindParserError::ListEnd);
let (_, result) = delimited(ws(list_start), ws(values), ws(list_end))(vals_input)?;
Ok((input, result))
}
}
fn number_value_list(input: NomSpan) -> IResult<NomSpan, Vec<Value>, BindParserError> {
let token = map_err(tag("="), BindParserError::Assignment);
let value = separated_pair(ws(identifier), ws(token), ws(numeric_literal));
value_list(map(value, |(ident, val)| Value::Number(ident, val)))(input)
}
fn string_value_list(input: NomSpan) -> IResult<NomSpan, Vec<Value>, BindParserError> {
let token = map_err(tag("="), BindParserError::Assignment);
let value = separated_pair(ws(identifier), ws(token), ws(string_literal));
value_list(map(value, |(ident, val)| Value::Str(ident, val)))(input)
}
fn bool_value_list(input: NomSpan) -> IResult<NomSpan, Vec<Value>, BindParserError> {
let token = map_err(tag("="), BindParserError::Assignment);
let value = separated_pair(ws(identifier), ws(token), ws(bool_literal));
value_list(map(value, |(ident, val)| Value::Bool(ident, val)))(input)
}
fn enum_value_list(input: NomSpan) -> IResult<NomSpan, Vec<Value>, BindParserError> {
value_list(map(ws(identifier), Value::Enum))(input)
}
fn declaration(input: NomSpan) -> IResult<NomSpan, Declaration, BindParserError> {
let (input, extends) = opt(keyword_extend)(input)?;
let (input, value_type) =
alt((keyword_uint, keyword_string, keyword_bool, keyword_enum))(input)?;
let (input, identifier) = ws(compound_identifier)(input)?;
let value_parser = match value_type {
ValueType::Number => number_value_list,
ValueType::Str => string_value_list,
ValueType::Bool => bool_value_list,
ValueType::Enum => enum_value_list,
};
let (input, vals) = value_parser(input)?;
Ok((input, Declaration { identifier, value_type, extends: extends.is_some(), values: vals }))
}
fn library_name(input: NomSpan) -> IResult<NomSpan, CompoundIdentifier, BindParserError> {
let keyword = ws(map_err(tag("library"), BindParserError::LibraryKeyword));
let terminator = ws(map_err(tag(";"), BindParserError::Semicolon));
delimited(keyword, ws(compound_identifier), terminator)(input)
}
fn library(input: NomSpan) -> IResult<NomSpan, Ast, BindParserError> {
map(
tuple((ws(library_name), ws(using_list), many_until_eof(ws(declaration)))),
|(name, using, declarations)| Ast { name, using, declarations },
)(input)
}
#[cfg(test)]
mod test {
use super::*;
use crate::make_identifier;
use crate::parser_common::test::check_result;
mod number_value_lists {
use super::*;
#[test]
fn single_value() {
check_result(
number_value_list(NomSpan::new("{abc = 123};")),
"",
vec![Value::Number("abc".to_string(), 123)],
);
}
#[test]
fn multiple_values() {
// Matches multiple string values.
check_result(
number_value_list(NomSpan::new("{abc = 123, DEF = 456};")),
"",
vec![Value::Number("abc".to_string(), 123), Value::Number("DEF".to_string(), 456)],
);
check_result(
number_value_list(NomSpan::new("{abc = 123, DEF = 456, ghi = 0xabc};")),
"",
vec![
Value::Number("abc".to_string(), 123),
Value::Number("DEF".to_string(), 456),
Value::Number("ghi".to_string(), 0xabc),
],
);
}
#[test]
fn whitespace() {
// Handles whitespace.
check_result(
number_value_list(NomSpan::new("{ abc=123,\n\tDEF\t = 0xdef\n};")),
"",
vec![
Value::Number("abc".to_string(), 123),
Value::Number("DEF".to_string(), 0xdef),
],
);
}
#[test]
fn invalid_values() {
// Does not match non-number values.
assert_eq!(
number_value_list(NomSpan::new("{abc = \"string\"};")),
Err(nom::Err::Error(BindParserError::NumericLiteral("\"string\"}".to_string())))
);
assert_eq!(
number_value_list(NomSpan::new("{abc = true};")),
Err(nom::Err::Error(BindParserError::NumericLiteral("true}".to_string())))
);
}
#[test]
fn trailing_comma() {
// Matches trailing ','.
check_result(
number_value_list(NomSpan::new("{abc = 123,};")),
"",
vec![Value::Number("abc".to_string(), 123)],
);
}
#[test]
fn empty_list() {
// Does not match empty list.
assert_eq!(
number_value_list(NomSpan::new("{};")),
Err(nom::Err::Error(BindParserError::Identifier("}".to_string())))
);
}
#[test]
fn invalid_list() {
// Must have list start and end braces.
assert_eq!(
number_value_list(NomSpan::new("abc = 123};")),
Err(nom::Err::Error(BindParserError::ListStart("abc = 123}".to_string())))
);
assert_eq!(
number_value_list(NomSpan::new("{abc = 123;")),
Err(nom::Err::Error(BindParserError::ListEnd("".to_string())))
);
// Must have assignment operator.
assert_eq!(
number_value_list(NomSpan::new("{abc 123};")),
Err(nom::Err::Error(BindParserError::Assignment("123}".to_string())))
);
}
#[test]
fn no_list() {
// Matches no list.
check_result(number_value_list(NomSpan::new(";")), "", vec![]);
}
}
mod string_value_lists {
use super::*;
#[test]
fn single_value() {
check_result(
string_value_list(NomSpan::new(r#"{abc = "xyz"};"#)),
"",
vec![Value::Str("abc".to_string(), "xyz".to_string())],
);
}
#[test]
fn multiple_values() {
// Matches multiple string values.
check_result(
string_value_list(NomSpan::new(r#"{abc = "xyz", DEF = "UVW"};"#)),
"",
vec![
Value::Str("abc".to_string(), "xyz".to_string()),
Value::Str("DEF".to_string(), "UVW".to_string()),
],
);
check_result(
string_value_list(NomSpan::new(r#"{abc = "xyz", DEF = "UVW", ghi = "rst"};"#)),
"",
vec![
Value::Str("abc".to_string(), "xyz".to_string()),
Value::Str("DEF".to_string(), "UVW".to_string()),
Value::Str("ghi".to_string(), "rst".to_string()),
],
);
}
#[test]
fn whitespace() {
// Handles whitespace.
check_result(
string_value_list(NomSpan::new("{ abc=\"xyz\",\n\tDEF\t = \"UVW\"\n};")),
"",
vec![
Value::Str("abc".to_string(), "xyz".to_string()),
Value::Str("DEF".to_string(), "UVW".to_string()),
],
);
}
#[test]
fn invalid_values() {
// Does not match non-string values.
assert_eq!(
string_value_list(NomSpan::new("{abc = 123};")),
Err(nom::Err::Error(BindParserError::StringLiteral("123}".to_string())))
);
assert_eq!(
string_value_list(NomSpan::new("{abc = true};")),
Err(nom::Err::Error(BindParserError::StringLiteral("true}".to_string())))
);
}
#[test]
fn trailing_comma() {
// Matches trailing ','.
check_result(
string_value_list(NomSpan::new(r#"{abc = "xyz",};"#)),
"",
vec![Value::Str("abc".to_string(), "xyz".to_string())],
);
}
#[test]
fn empty_list() {
// Does not match empty list.
assert_eq!(
string_value_list(NomSpan::new("{};")),
Err(nom::Err::Error(BindParserError::Identifier("}".to_string())))
);
}
#[test]
fn invalid_list() {
// Must have list start and end braces.
assert_eq!(
string_value_list(NomSpan::new(r#"abc = "xyz"};"#)),
Err(nom::Err::Error(BindParserError::ListStart(r#"abc = "xyz"}"#.to_string())))
);
assert_eq!(
string_value_list(NomSpan::new(r#"{abc = "xyz";"#)),
Err(nom::Err::Error(BindParserError::ListEnd("".to_string())))
);
// Must have assignment operator.
assert_eq!(
number_value_list(NomSpan::new(r#"{abc "xyz"};"#)),
Err(nom::Err::Error(BindParserError::Assignment(r#""xyz"}"#.to_string())))
);
}
#[test]
fn no_list() {
// Matches no list.
check_result(string_value_list(NomSpan::new(";")), "", vec![]);
}
}
mod bool_value_lists {
use super::*;
#[test]
fn single_value() {
// Matches one string value.
check_result(
bool_value_list(NomSpan::new("{abc = true};")),
"",
vec![Value::Bool("abc".to_string(), true)],
);
}
#[test]
fn multiple_values() {
// Matches multiple string values.
check_result(
bool_value_list(NomSpan::new("{abc = true, DEF = false};")),
"",
vec![Value::Bool("abc".to_string(), true), Value::Bool("DEF".to_string(), false)],
);
check_result(
bool_value_list(NomSpan::new("{abc = true, DEF = false, ghi = false};")),
"",
vec![
Value::Bool("abc".to_string(), true),
Value::Bool("DEF".to_string(), false),
Value::Bool("ghi".to_string(), false),
],
);
}
#[test]
fn whitespace() {
// Handles whitespace.
check_result(
bool_value_list(NomSpan::new("{ abc=true,\n\tDEF\t = false\n};")),
"",
vec![Value::Bool("abc".to_string(), true), Value::Bool("DEF".to_string(), false)],
);
}
#[test]
fn invalid_values() {
// Does not match non-bool values.
assert_eq!(
bool_value_list(NomSpan::new("{abc = 123};")),
Err(nom::Err::Error(BindParserError::BoolLiteral("123}".to_string())))
);
assert_eq!(
bool_value_list(NomSpan::new("{abc = \"string\"};")),
Err(nom::Err::Error(BindParserError::BoolLiteral("\"string\"}".to_string())))
);
}
#[test]
fn trailing_comma() {
// Matches trailing ','.
check_result(
bool_value_list(NomSpan::new(r#"{abc = true,};"#)),
"",
vec![Value::Bool("abc".to_string(), true)],
);
}
#[test]
fn empty_list() {
// Does not match empty list.
assert_eq!(
bool_value_list(NomSpan::new("{};")),
Err(nom::Err::Error(BindParserError::Identifier("}".to_string())))
);
}
#[test]
fn invalid_list() {
// Must have list start and end braces.
assert_eq!(
bool_value_list(NomSpan::new("abc = true};")),
Err(nom::Err::Error(BindParserError::ListStart("abc = true}".to_string())))
);
assert_eq!(
bool_value_list(NomSpan::new("{abc = true;")),
Err(nom::Err::Error(BindParserError::ListEnd("".to_string())))
);
// Must have assignment operator.
assert_eq!(
number_value_list(NomSpan::new("{abc false};")),
Err(nom::Err::Error(BindParserError::Assignment("false}".to_string())))
);
}
#[test]
fn no_list() {
// Matches no list
check_result(bool_value_list(NomSpan::new(";")), "", vec![]);
}
}
mod enum_value_lists {
use super::*;
#[test]
fn single_value() {
// Matches one identifier.
check_result(
enum_value_list(NomSpan::new("{abc};")),
"",
vec![Value::Enum("abc".to_string())],
);
}
#[test]
fn multiple_values() {
// Matches multiple identifiers.
check_result(
enum_value_list(NomSpan::new("{abc,def};")),
"",
vec![Value::Enum("abc".to_string()), Value::Enum("def".to_string())],
);
check_result(
enum_value_list(NomSpan::new("{abc,def,ghi};")),
"",
vec![
Value::Enum("abc".to_string()),
Value::Enum("def".to_string()),
Value::Enum("ghi".to_string()),
],
);
}
#[test]
fn whitespace() {
// Matches multiple identifiers with whitespace.
check_result(
enum_value_list(NomSpan::new("{abc, def, \tghi,\n jkl};")),
"",
vec![
Value::Enum("abc".to_string()),
Value::Enum("def".to_string()),
Value::Enum("ghi".to_string()),
Value::Enum("jkl".to_string()),
],
);
}
#[test]
fn trailing_comma() {
// Matches trailing ','.
check_result(
enum_value_list(NomSpan::new("{abc,};")),
"",
vec![Value::Enum("abc".to_string())],
);
}
#[test]
fn no_list() {
// Matches no list.
check_result(enum_value_list(NomSpan::new(";")), "", vec![]);
}
#[test]
fn invalid_list() {
// Must have semicolon.
assert_eq!(
enum_value_list(NomSpan::new("{abc,}")),
Err(nom::Err::Error(BindParserError::Semicolon("{abc,}".to_string())))
);
// Must have list start and end braces.
assert_eq!(
enum_value_list(NomSpan::new("abc};")),
Err(nom::Err::Error(BindParserError::ListStart("abc}".to_string())))
);
assert_eq!(
enum_value_list(NomSpan::new("{abc;")),
Err(nom::Err::Error(BindParserError::ListEnd("".to_string())))
);
}
#[test]
fn empty_list() {
// Does not match empty list.
assert_eq!(
enum_value_list(NomSpan::new("{};")),
Err(nom::Err::Error(BindParserError::Identifier("}".to_string())))
);
}
}
mod declarations {
use super::*;
#[test]
fn no_value() {
// Matches key declaration without values.
check_result(
declaration(NomSpan::new("uint test;")),
"",
Declaration {
identifier: make_identifier!["test"],
value_type: ValueType::Number,
extends: false,
values: vec![],
},
);
}
#[test]
fn numbers() {
// Matches numbers.
check_result(
declaration(NomSpan::new("uint test { x = 1 };")),
"",
Declaration {
identifier: make_identifier!["test"],
value_type: ValueType::Number,
extends: false,
values: vec![Value::Number("x".to_string(), 1)],
},
);
}
#[test]
fn strings() {
// Matches strings.
check_result(
declaration(NomSpan::new(r#"string test { x = "a" };"#)),
"",
Declaration {
identifier: make_identifier!["test"],
value_type: ValueType::Str,
extends: false,
values: vec![Value::Str("x".to_string(), "a".to_string())],
},
);
}
#[test]
fn bools() {
// Matches bools.
check_result(
declaration(NomSpan::new("bool test { x = false };")),
"",
Declaration {
identifier: make_identifier!["test"],
value_type: ValueType::Bool,
extends: false,
values: vec![Value::Bool("x".to_string(), false)],
},
);
}
#[test]
fn enums() {
// Matches enums.
check_result(
declaration(NomSpan::new("enum test { x };")),
"",
Declaration {
identifier: make_identifier!["test"],
value_type: ValueType::Enum,
extends: false,
values: vec![Value::Enum("x".to_string())],
},
);
}
#[test]
fn extend() {
// Handles "extend" keyword.
check_result(
declaration(NomSpan::new("extend uint test { x = 1 };")),
"",
Declaration {
identifier: make_identifier!["test"],
value_type: ValueType::Number,
extends: true,
values: vec![Value::Number("x".to_string(), 1)],
},
);
}
#[test]
fn type_mismatch() {
// Handles type mismatches.
assert_eq!(
declaration(NomSpan::new("uint test { x = false };")),
Err(nom::Err::Error(BindParserError::NumericLiteral("false }".to_string())))
);
}
#[test]
fn invalid() {
// Must have a type, and an identifier.
assert_eq!(
declaration(NomSpan::new("bool { x = false };")),
Err(nom::Err::Error(BindParserError::Identifier("{ x = false };".to_string())))
);
assert_eq!(
declaration(NomSpan::new("test { x = false };")),
Err(nom::Err::Error(BindParserError::Type("test { x = false };".to_string())))
);
// Must be terminated by ';'.
assert_eq!(
declaration(NomSpan::new("bool test { x = false }")),
Err(nom::Err::Error(BindParserError::Semicolon(" { x = false }".to_string())))
);
}
#[test]
fn compound_identifier() {
// Identifier can be compound.
check_result(
declaration(NomSpan::new("uint this.is.a.test { x = 1 };")),
"",
Declaration {
identifier: make_identifier!["this", "is", "a", "test"],
value_type: ValueType::Number,
extends: false,
values: vec![Value::Number("x".to_string(), 1)],
},
);
}
#[test]
fn empty() {
// Does not match empty string.
assert_eq!(
declaration(NomSpan::new("")),
Err(nom::Err::Error(BindParserError::Type("".to_string())))
);
}
}
mod library_names {
use super::*;
#[test]
fn single_name() {
check_result(library_name(NomSpan::new("library a;")), "", make_identifier!["a"]);
}
#[test]
fn compound_name() {
check_result(
library_name(NomSpan::new("library a.b;")),
"",
make_identifier!["a", "b"],
);
}
#[test]
fn whitespace() {
check_result(
library_name(NomSpan::new("library \n\t a\n\t ;")),
"",
make_identifier!["a"],
);
}
#[test]
fn invalid() {
// Must have a name.
assert_eq!(
library_name(NomSpan::new("library ;")),
Err(nom::Err::Error(BindParserError::Identifier(";".to_string())))
);
// Must be terminated by ';'.
assert_eq!(
library_name(NomSpan::new("library a")),
Err(nom::Err::Error(BindParserError::Semicolon("".to_string())))
);
}
#[test]
fn empty() {
// Does not match empty string.
assert_eq!(
library_name(NomSpan::new("")),
Err(nom::Err::Error(BindParserError::LibraryKeyword("".to_string())))
);
}
}
mod libraries {
use super::*;
#[test]
fn empty() {
// Does not match empty string.
assert_eq!(
library(NomSpan::new("")),
Err(nom::Err::Error(BindParserError::LibraryKeyword("".to_string())))
);
}
#[test]
fn empty_library() {
check_result(
library(NomSpan::new("library a;")),
"",
Ast { name: make_identifier!["a"], using: vec![], declarations: vec![] },
);
}
#[test]
fn using_list() {
check_result(
library(NomSpan::new("library a; using c as d;")),
"",
Ast {
name: make_identifier!["a"],
using: vec![Include {
name: make_identifier!["c"],
alias: Some("d".to_string()),
}],
declarations: vec![],
},
);
}
#[test]
fn declarations() {
check_result(
library(NomSpan::new("library a; uint t { x = 1 };")),
"",
Ast {
name: make_identifier!["a"],
using: vec![],
declarations: vec![Declaration {
identifier: make_identifier!["t"],
value_type: ValueType::Number,
extends: false,
values: vec![(Value::Number("x".to_string(), 1))],
}],
},
);
}
#[test]
fn multiple_elements() {
// Matches library with using list and declarations.
check_result(
library(NomSpan::new("library a; using b.c as d; extend enum d.t { x };")),
"",
Ast {
name: make_identifier!["a"],
using: vec![Include {
name: make_identifier!["b", "c"],
alias: Some("d".to_string()),
}],
declarations: vec![Declaration {
identifier: make_identifier!["d", "t"],
value_type: ValueType::Enum,
extends: true,
values: vec![Value::Enum("x".to_string())],
}],
},
);
// Matches library with using list and two declarations.
check_result(
library(NomSpan::new("library a; using b.c as d; extend enum d.t { x }; bool e;")),
"",
Ast {
name: make_identifier!["a"],
using: vec![Include {
name: make_identifier!["b", "c"],
alias: Some("d".to_string()),
}],
declarations: vec![
Declaration {
identifier: make_identifier!["d", "t"],
value_type: ValueType::Enum,
extends: true,
values: vec![Value::Enum("x".to_string())],
},
Declaration {
identifier: make_identifier!["e"],
value_type: ValueType::Bool,
extends: false,
values: vec![],
},
],
},
);
}
#[test]
fn consumes_entire_input() {
// Must parse entire input.
assert_eq!(
library(NomSpan::new("library a; using b.c as d; invalid input")),
Err(nom::Err::Error(BindParserError::Type("invalid input".to_string())))
);
}
#[test]
fn whitespace() {
// Handles whitespace.
assert_eq!(
library(NomSpan::new(
"\n\t library a;\t using b.c as d;\n extend enum d.t { x }; \t bool e;\n "
))
.unwrap()
.1,
library(NomSpan::new("library a; using b.c as d; extend enum d.t { x }; bool e;"))
.unwrap()
.1,
);
}
}
}