blob: c8df17c320e5f79c90ac45324b391e6d8d838527 [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, skip_ws, using_list, ws,
BindParserError, CompoundIdentifier, Include, NomSpan, Span, Value,
};
use nom::{
branch::alt,
bytes::complete::tag,
combinator::{opt, value},
multi::{many1, separated_nonempty_list},
sequence::{delimited, preceded, terminated, tuple},
IResult,
};
use std::convert::TryFrom;
#[derive(Debug, Clone, PartialEq)]
pub struct Ast<'a> {
pub using: Vec<Include>,
pub statements: Vec<Statement<'a>>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ConditionOp {
Equals,
NotEquals,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Condition<'a> {
pub span: Span<'a>,
pub lhs: CompoundIdentifier,
pub op: ConditionOp,
pub rhs: Value,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Statement<'a> {
ConditionStatement {
span: Span<'a>,
condition: Condition<'a>,
},
Accept {
span: Span<'a>,
identifier: CompoundIdentifier,
values: Vec<Value>,
},
If {
span: Span<'a>,
blocks: Vec<(Condition<'a>, Vec<Statement<'a>>)>,
else_block: Vec<Statement<'a>>,
},
Abort {
span: Span<'a>,
},
}
impl<'a> Statement<'a> {
pub fn get_span(&'a self) -> &'a Span<'a> {
match self {
Statement::ConditionStatement { span, .. } => span,
Statement::Accept { span, .. } => span,
Statement::If { span, .. } => span,
Statement::Abort { span } => span,
}
}
}
// TODO(fxbug.dev/35146): Improve error reporting here.
impl<'a> TryFrom<&'a str> for Ast<'a> {
type Error = BindParserError;
fn try_from(input: &'a str) -> Result<Self, Self::Error> {
match program(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")
}
}
}
}
fn condition_op(input: NomSpan) -> IResult<NomSpan, ConditionOp, BindParserError> {
let equals = value(ConditionOp::Equals, tag("=="));
let not_equals = value(ConditionOp::NotEquals, tag("!="));
map_err(alt((equals, not_equals)), BindParserError::ConditionOp)(input)
}
fn condition(input: NomSpan) -> IResult<NomSpan, Condition, BindParserError> {
let from = skip_ws(input)?;
let (input, lhs) = ws(compound_identifier)(from)?;
let (input, op) = ws(condition_op)(input)?;
let (to, rhs) = ws(condition_value)(input)?;
let span = Span::from_to(&from, &to);
Ok((to, Condition { span, lhs, op, rhs }))
}
fn condition_statement(input: NomSpan) -> IResult<NomSpan, Statement, BindParserError> {
let from = skip_ws(input)?;
let terminator = ws(map_err(tag(";"), BindParserError::Semicolon));
let (to, condition) = terminated(condition, terminator)(from)?;
let span = Span::from_to(&from, &to);
Ok((to, Statement::ConditionStatement { span, condition }))
}
fn keyword_if(input: NomSpan) -> IResult<NomSpan, NomSpan, BindParserError> {
ws(map_err(tag("if"), BindParserError::IfKeyword))(input)
}
fn keyword_else(input: NomSpan) -> IResult<NomSpan, NomSpan, BindParserError> {
ws(map_err(tag("else"), BindParserError::ElseKeyword))(input)
}
fn if_statement(input: NomSpan) -> IResult<NomSpan, Statement, BindParserError> {
let from = skip_ws(input)?;
let if_block = tuple((preceded(keyword_if, condition), statement_block));
let if_blocks = separated_nonempty_list(keyword_else, if_block);
let else_block = preceded(keyword_else, statement_block);
let (input, blocks) = if_blocks(from)?;
let (to, else_block) = else_block(input)?;
let span = Span::from_to(&from, &to);
Ok((to, Statement::If { span, blocks, else_block }))
}
fn statement_block(input: NomSpan) -> IResult<NomSpan, Vec<Statement>, BindParserError> {
let block_start = ws(map_err(tag("{"), BindParserError::IfBlockStart));
let block_end = ws(map_err(tag("}"), BindParserError::IfBlockEnd));
delimited(block_start, many1(ws(statement)), block_end)(input)
}
fn keyword_accept(input: NomSpan) -> IResult<NomSpan, NomSpan, BindParserError> {
ws(map_err(tag("accept"), BindParserError::AcceptKeyword))(input)
}
fn accept(input: NomSpan) -> IResult<NomSpan, Statement, BindParserError> {
let from = skip_ws(input)?;
let list_start = ws(map_err(tag("{"), BindParserError::ListStart));
let list_end = ws(map_err(tag("}"), BindParserError::ListEnd));
let separator = || ws(map_err(tag(","), BindParserError::ListSeparator));
let values = separated_nonempty_list(separator(), ws(condition_value));
// Lists may optionally be terminated by an additional trailing separator.
let values = terminated(values, opt(separator()));
let value_list = delimited(list_start, values, list_end);
let (to, (identifier, values)) =
preceded(keyword_accept, tuple((ws(compound_identifier), value_list)))(from)?;
let span = Span::from_to(&from, &to);
Ok((to, Statement::Accept { span, identifier, values }))
}
fn abort(input: NomSpan) -> IResult<NomSpan, Statement, BindParserError> {
let from = skip_ws(input)?;
let keyword_abort = ws(map_err(tag("abort"), BindParserError::AbortKeyword));
let terminator = ws(map_err(tag(";"), BindParserError::Semicolon));
let (to, _) = terminated(keyword_abort, terminator)(from)?;
let span = Span::from_to(&from, &to);
Ok((to, Statement::Abort { span }))
}
fn statement(input: NomSpan) -> IResult<NomSpan, Statement, BindParserError> {
alt((condition_statement, if_statement, accept, abort))(input)
}
fn program(input: NomSpan) -> IResult<NomSpan, Ast, BindParserError> {
let (input, using) = ws(using_list)(input)?;
let (input, statements) = many_until_eof(ws(statement))(input)?;
if statements.is_empty() {
return Err(nom::Err::Error(BindParserError::NoStatements(input.to_string())));
}
Ok((input, Ast { using, statements }))
}
#[cfg(test)]
mod test {
use super::*;
use crate::make_identifier;
use crate::parser_common::test::check_result;
mod condition_ops {
use super::*;
#[test]
fn equality() {
check_result(condition_op(NomSpan::new("==")), "", ConditionOp::Equals);
}
#[test]
fn inequality() {
check_result(condition_op(NomSpan::new("!=")), "", ConditionOp::NotEquals);
}
#[test]
fn invalid() {
assert_eq!(
condition_op(NomSpan::new(">=")),
Err(nom::Err::Error(BindParserError::ConditionOp(">=".to_string())))
);
}
#[test]
fn empty() {
assert_eq!(
condition_op(NomSpan::new("")),
Err(nom::Err::Error(BindParserError::ConditionOp("".to_string())))
);
}
}
mod conditions {
use super::*;
#[test]
fn equality_condition() {
check_result(
condition(NomSpan::new("abc == true")),
"",
Condition {
span: Span { offset: 0, line: 1, fragment: "abc == true" },
lhs: make_identifier!["abc"],
op: ConditionOp::Equals,
rhs: Value::BoolLiteral(true),
},
);
}
#[test]
fn empty() {
assert_eq!(
condition(NomSpan::new("")),
Err(nom::Err::Error(BindParserError::Identifier("".to_string())))
);
}
#[test]
fn span() {
// Span doesn't contain leading or trailing whitespace, and offset and line number
// are correct.
check_result(
condition(NomSpan::new(" \n\t\r\nabc \n\t\r\n== \n\t\r\ntrue \n\t\r\n")),
" \n\t\r\n",
Condition {
span: Span { offset: 5, line: 3, fragment: "abc \n\t\r\n== \n\t\r\ntrue" },
lhs: make_identifier!["abc"],
op: ConditionOp::Equals,
rhs: Value::BoolLiteral(true),
},
);
}
}
mod if_statements {
use super::*;
#[test]
fn simple() {
check_result(
if_statement(NomSpan::new("if a == b { c == 1; } else { d == 2; }")),
"",
Statement::If {
span: Span {
offset: 0,
line: 1,
fragment: "if a == b { c == 1; } else { d == 2; }",
},
blocks: vec![(
Condition {
span: Span { offset: 3, line: 1, fragment: "a == b" },
lhs: make_identifier!["a"],
op: ConditionOp::Equals,
rhs: Value::Identifier(make_identifier!["b"]),
},
vec![Statement::ConditionStatement {
span: Span { offset: 12, line: 1, fragment: "c == 1;" },
condition: Condition {
span: Span { offset: 12, line: 1, fragment: "c == 1" },
lhs: make_identifier!["c"],
op: ConditionOp::Equals,
rhs: Value::NumericLiteral(1),
},
}],
)],
else_block: vec![Statement::ConditionStatement {
span: Span { offset: 29, line: 1, fragment: "d == 2;" },
condition: Condition {
span: Span { offset: 29, line: 1, fragment: "d == 2" },
lhs: make_identifier!["d"],
op: ConditionOp::Equals,
rhs: Value::NumericLiteral(2),
},
}],
},
);
}
#[test]
fn else_if() {
check_result(
if_statement(NomSpan::new(
"if a == b { c == 1; } else if e == 3 { d == 2; } else { f != 4; }",
)),
"",
Statement::If {
span: Span {
offset: 0,
line: 1,
fragment:
"if a == b { c == 1; } else if e == 3 { d == 2; } else { f != 4; }",
},
blocks: vec![
(
Condition {
span: Span { offset: 3, line: 1, fragment: "a == b" },
lhs: make_identifier!["a"],
op: ConditionOp::Equals,
rhs: Value::Identifier(make_identifier!["b"]),
},
vec![Statement::ConditionStatement {
span: Span { offset: 12, line: 1, fragment: "c == 1;" },
condition: Condition {
span: Span { offset: 12, line: 1, fragment: "c == 1" },
lhs: make_identifier!["c"],
op: ConditionOp::Equals,
rhs: Value::NumericLiteral(1),
},
}],
),
(
Condition {
span: Span { offset: 30, line: 1, fragment: "e == 3" },
lhs: make_identifier!["e"],
op: ConditionOp::Equals,
rhs: Value::NumericLiteral(3),
},
vec![Statement::ConditionStatement {
span: Span { offset: 39, line: 1, fragment: "d == 2;" },
condition: Condition {
span: Span { offset: 39, line: 1, fragment: "d == 2" },
lhs: make_identifier!["d"],
op: ConditionOp::Equals,
rhs: Value::NumericLiteral(2),
},
}],
),
],
else_block: vec![Statement::ConditionStatement {
span: Span { offset: 56, line: 1, fragment: "f != 4;" },
condition: Condition {
span: Span { offset: 56, line: 1, fragment: "f != 4" },
lhs: make_identifier!["f"],
op: ConditionOp::NotEquals,
rhs: Value::NumericLiteral(4),
},
}],
},
);
}
#[test]
fn nested() {
check_result(
if_statement(NomSpan::new(
"if a == 1 { if b == 2 { c != 3; } else { c == 3; } } else { d == 2; }",
)),
"",
Statement::If {
span: Span {
offset: 0,
line: 1,
fragment:
"if a == 1 { if b == 2 { c != 3; } else { c == 3; } } else { d == 2; }",
},
blocks: vec![(
Condition {
span: Span { offset: 3, line: 1, fragment: "a == 1" },
lhs: make_identifier!["a"],
op: ConditionOp::Equals,
rhs: Value::NumericLiteral(1),
},
vec![Statement::If {
span: Span {
offset: 12,
line: 1,
fragment: "if b == 2 { c != 3; } else { c == 3; }",
},
blocks: vec![(
Condition {
span: Span { offset: 15, line: 1, fragment: "b == 2" },
lhs: make_identifier!["b"],
op: ConditionOp::Equals,
rhs: Value::NumericLiteral(2),
},
vec![Statement::ConditionStatement {
span: Span { offset: 24, line: 1, fragment: "c != 3;" },
condition: Condition {
span: Span { offset: 24, line: 1, fragment: "c != 3" },
lhs: make_identifier!["c"],
op: ConditionOp::NotEquals,
rhs: Value::NumericLiteral(3),
},
}],
)],
else_block: vec![Statement::ConditionStatement {
span: Span { offset: 41, line: 1, fragment: "c == 3;" },
condition: Condition {
span: Span { offset: 41, line: 1, fragment: "c == 3" },
lhs: make_identifier!["c"],
op: ConditionOp::Equals,
rhs: Value::NumericLiteral(3),
},
}],
}],
)],
else_block: vec![Statement::ConditionStatement {
span: Span { offset: 60, line: 1, fragment: "d == 2;" },
condition: Condition {
span: Span { offset: 60, line: 1, fragment: "d == 2" },
lhs: make_identifier!["d"],
op: ConditionOp::Equals,
rhs: Value::NumericLiteral(2),
},
}],
},
);
}
#[test]
fn invalid() {
// Must have 'if' keyword.
assert_eq!(
if_statement(NomSpan::new("a == b { c == 1; }")),
Err(nom::Err::Error(BindParserError::IfKeyword("a == b { c == 1; }".to_string())))
);
// Must have condition.
assert_eq!(
if_statement(NomSpan::new("if { c == 1; }")),
Err(nom::Err::Error(BindParserError::Identifier("{ c == 1; }".to_string())))
);
// Must have else block.
assert_eq!(
if_statement(NomSpan::new("if a == b { c == 1; }")),
Err(nom::Err::Error(BindParserError::ElseKeyword("".to_string())))
);
assert_eq!(
if_statement(NomSpan::new("if a == b { c == 1; } else if e == 3 { d == 2; }")),
Err(nom::Err::Error(BindParserError::ElseKeyword("".to_string())))
);
// Must delimit blocks with {}s.
assert_eq!(
if_statement(NomSpan::new("if a == b c == 1; }")),
Err(nom::Err::Error(BindParserError::IfBlockStart("c == 1; }".to_string())))
);
assert_eq!(
if_statement(NomSpan::new("if a == b { c == 1;")),
Err(nom::Err::Error(BindParserError::IfBlockEnd("".to_string())))
);
// Blocks must not be empty.
// TODO(fxbug.dev/35146): Improve this error message, it currently reports a failure to parse
// an accept statement due to the way the combinator works for the statement parser.
assert!(if_statement(NomSpan::new("if a == b { } else { c == 1; }")).is_err());
assert!(if_statement(NomSpan::new("if a == b { c == 1; } else { }")).is_err());
}
#[test]
fn empty() {
assert_eq!(
if_statement(NomSpan::new("")),
Err(nom::Err::Error(BindParserError::IfKeyword("".to_string())))
);
}
}
mod accepts {
use super::*;
#[test]
fn simple() {
check_result(
accept(NomSpan::new("accept a { 1 }")),
"",
Statement::Accept {
span: Span { offset: 0, line: 1, fragment: "accept a { 1 }" },
identifier: make_identifier!["a"],
values: vec![Value::NumericLiteral(1)],
},
);
}
#[test]
fn multiple_values() {
check_result(
accept(NomSpan::new("accept a { 1, 2 }")),
"",
Statement::Accept {
span: Span { offset: 0, line: 1, fragment: "accept a { 1, 2 }" },
identifier: make_identifier!["a"],
values: vec![Value::NumericLiteral(1), Value::NumericLiteral(2)],
},
);
}
#[test]
fn trailing_comma() {
check_result(
accept(NomSpan::new("accept a { 1, 2, }")),
"",
Statement::Accept {
span: Span { offset: 0, line: 1, fragment: "accept a { 1, 2, }" },
identifier: make_identifier!["a"],
values: vec![Value::NumericLiteral(1), Value::NumericLiteral(2)],
},
);
}
#[test]
fn invalid() {
// Must have accept keyword.
assert_eq!(
accept(NomSpan::new("a { 1 }")),
Err(nom::Err::Error(BindParserError::AcceptKeyword("a { 1 }".to_string())))
);
// Must have identifier.
assert_eq!(
accept(NomSpan::new("accept { 1 }")),
Err(nom::Err::Error(BindParserError::Identifier("{ 1 }".to_string())))
);
// Must have at least one value.
assert_eq!(
accept(NomSpan::new("accept a { }")),
Err(nom::Err::Error(BindParserError::ConditionValue("}".to_string())))
);
// Must delimit blocks with {}s.
assert_eq!(
accept(NomSpan::new("accept a 1 }")),
Err(nom::Err::Error(BindParserError::ListStart("1 }".to_string())))
);
assert_eq!(
accept(NomSpan::new("accept a { 1")),
Err(nom::Err::Error(BindParserError::ListEnd("".to_string())))
);
}
#[test]
fn empty() {
assert_eq!(
accept(NomSpan::new("")),
Err(nom::Err::Error(BindParserError::AcceptKeyword("".to_string())))
);
}
#[test]
fn span() {
// Span doesn't contain leading or trailing whitespace, and line number is correct.
check_result(
condition(NomSpan::new(" \n\t\r\nabc \n\t\r\n== \n\t\r\ntrue \n\t\r\n")),
" \n\t\r\n",
Condition {
span: Span { offset: 5, line: 3, fragment: "abc \n\t\r\n== \n\t\r\ntrue" },
lhs: make_identifier!["abc"],
op: ConditionOp::Equals,
rhs: Value::BoolLiteral(true),
},
);
}
}
mod aborts {
use super::*;
#[test]
fn simple() {
check_result(
abort(NomSpan::new("abort;")),
"",
Statement::Abort { span: Span { offset: 0, line: 1, fragment: "abort;" } },
);
}
#[test]
fn invalid() {
// Must have abort keyword.
assert_eq!(
abort(NomSpan::new("a;")),
Err(nom::Err::Error(BindParserError::AbortKeyword("a;".to_string())))
);
assert_eq!(
abort(NomSpan::new(";")),
Err(nom::Err::Error(BindParserError::AbortKeyword(";".to_string())))
);
// Must have semicolon.
assert_eq!(
abort(NomSpan::new("abort")),
Err(nom::Err::Error(BindParserError::Semicolon("".to_string())))
);
}
#[test]
fn empty() {
assert_eq!(
abort(NomSpan::new("")),
Err(nom::Err::Error(BindParserError::AbortKeyword("".to_string())))
);
}
#[test]
fn span() {
// Span doesn't contain leading or trailing whitespace, and line number is correct.
check_result(
accept(NomSpan::new(" \n\t\r\naccept a \n\t\r\n{ 1 } \n\t\r\n")),
" \n\t\r\n",
Statement::Accept {
span: Span { offset: 5, line: 3, fragment: "accept a \n\t\r\n{ 1 }" },
identifier: make_identifier!["a"],
values: vec![Value::NumericLiteral(1)],
},
);
}
}
mod programs {
use super::*;
#[test]
fn simple() {
check_result(
program(NomSpan::new("using a; x == 1;")),
"",
Ast {
using: vec![Include { name: make_identifier!["a"], alias: None }],
statements: vec![Statement::ConditionStatement {
span: Span { offset: 9, line: 1, fragment: "x == 1;" },
condition: Condition {
span: Span { offset: 9, line: 1, fragment: "x == 1" },
lhs: make_identifier!["x"],
op: ConditionOp::Equals,
rhs: Value::NumericLiteral(1),
},
}],
},
);
}
#[test]
fn empty() {
assert_eq!(
program(NomSpan::new("")),
Err(nom::Err::Error(BindParserError::NoStatements("".to_string())))
);
}
#[test]
fn requires_statement() {
// Must have a statement.
assert_eq!(
program(NomSpan::new("using a;")),
Err(nom::Err::Error(BindParserError::NoStatements("".to_string())))
);
}
#[test]
fn using_list_optional() {
check_result(
program(NomSpan::new("x == 1;")),
"",
Ast {
using: vec![],
statements: vec![Statement::ConditionStatement {
span: Span { offset: 0, line: 1, fragment: "x == 1;" },
condition: Condition {
span: Span { offset: 0, line: 1, fragment: "x == 1" },
lhs: make_identifier!["x"],
op: ConditionOp::Equals,
rhs: Value::NumericLiteral(1),
},
}],
},
);
}
#[test]
fn requires_semicolons() {
// TODO(fxbug.dev/35146): Improve the error type that is returned here.
assert_eq!(
program(NomSpan::new("x == 1")),
Err(nom::Err::Error(BindParserError::AbortKeyword("x == 1".to_string())))
);
}
#[test]
fn multiple_statements() {
check_result(
program(NomSpan::new(
"x == 1; accept y { true } abort; if z == 2 { a != 3; } else { a == 3; }",
)),
"",
Ast {
using: vec![],
statements: vec![
Statement::ConditionStatement {
span: Span { offset: 0, line: 1, fragment: "x == 1;" },
condition: Condition {
span: Span { offset: 0, line: 1, fragment: "x == 1" },
lhs: make_identifier!["x"],
op: ConditionOp::Equals,
rhs: Value::NumericLiteral(1),
},
},
Statement::Accept {
span: Span { offset: 8, line: 1, fragment: "accept y { true }" },
identifier: make_identifier!["y"],
values: vec![Value::BoolLiteral(true)],
},
Statement::Abort { span: Span { offset: 26, line: 1, fragment: "abort;" } },
Statement::If {
span: Span {
offset: 33,
line: 1,
fragment: "if z == 2 { a != 3; } else { a == 3; }",
},
blocks: vec![(
Condition {
span: Span { offset: 36, line: 1, fragment: "z == 2" },
lhs: make_identifier!["z"],
op: ConditionOp::Equals,
rhs: Value::NumericLiteral(2),
},
vec![Statement::ConditionStatement {
span: Span { offset: 45, line: 1, fragment: "a != 3;" },
condition: Condition {
span: Span { offset: 45, line: 1, fragment: "a != 3" },
lhs: make_identifier!["a"],
op: ConditionOp::NotEquals,
rhs: Value::NumericLiteral(3),
},
}],
)],
else_block: vec![Statement::ConditionStatement {
span: Span { offset: 62, line: 1, fragment: "a == 3;" },
condition: Condition {
span: Span { offset: 62, line: 1, fragment: "a == 3" },
lhs: make_identifier!["a"],
op: ConditionOp::Equals,
rhs: Value::NumericLiteral(3),
},
}],
},
],
},
);
}
}
}