From 646e0faa7afa4279974996cd62a9a69f66d528e7 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Tue, 20 Feb 2024 16:23:17 +0100 Subject: [PATCH 01/24] start work on the manual parser --- Cargo.toml | 1 + examples/rib.roto | 5 ++ src/lib.rs | 6 +- src/main.rs | 22 ++++++ src/parser.rs | 195 ++++++++++++++++++++++++++++++++++++++++++++++ src/token.rs | 148 +++++++++++++++++++++++++++++++++++ 6 files changed, 375 insertions(+), 2 deletions(-) create mode 100644 examples/rib.roto create mode 100644 src/main.rs create mode 100644 src/parser.rs create mode 100644 src/token.rs diff --git a/Cargo.toml b/Cargo.toml index 7821f747..3815c236 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ log = "0.4" arc-swap = "^1.6" bytes = { version = "1", features = [ "serde" ] } +logos = "0.14.0" nom = "7.1" paste = "1.0.14" smallvec = { version = "1.11", features = [ "const_generics", "serde" ] } diff --git a/examples/rib.roto b/examples/rib.roto new file mode 100644 index 00000000..38bc07b8 --- /dev/null +++ b/examples/rib.roto @@ -0,0 +1,5 @@ +// one comment +rib unrib contains Blaffer { + // another comment + blaffer: Blaf +} diff --git a/src/lib.rs b/src/lib.rs index 6c513981..f42d11c4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,10 +1,12 @@ pub mod ast; mod attr_change_set; +pub mod blocks; +pub mod compiler; pub mod eval; mod parse_string; +pub mod parser; mod symbols; +pub mod token; pub mod traits; pub mod types; pub mod vm; -pub mod blocks; -pub mod compiler; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 00000000..238667b4 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,22 @@ +fn main() { + let mut args = std::env::args(); + args.next(); + + let cmd = args.next().expect("missing command"); + + match cmd.as_ref() { + "parse" => { + let file = args.next().expect("need a file to parse"); + parse(&file); + } + _ => panic!("unrecognized command: {cmd}"), + }; +} + +fn parse(file: &str) { + let contents = std::fs::read_to_string(file).unwrap(); + match roto::parser::Parser::parse(&contents) { + Ok(contents) => println!("{contents:?}"), + Err(err) => println!("{err}"), + }; +} diff --git a/src/parser.rs b/src/parser.rs new file mode 100644 index 00000000..04142d40 --- /dev/null +++ b/src/parser.rs @@ -0,0 +1,195 @@ +use crate::ast::{ + Identifier, RecordTypeIdentifier, Rib, RibBody, RibField, RootExpr, + SyntaxTree, TypeIdentField, TypeIdentifier, ListTypeIdentifier +}; +use crate::token::Token; +use logos::{Lexer, Span, SpannedIter}; +use std::iter::Peekable; + +type ParseResult = Result; + +#[derive(Debug)] +pub enum ParseError { + EndOfInput, + InvalidToken(Span), + /// Dummy variant where more precise messages should be made + Todo, + Expected(Span), +} + +impl std::fmt::Display for ParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::EndOfInput => write!(f, "reached end of input"), + Self::InvalidToken(_) => write!(f, "invalid token"), + Self::Todo => write!(f, "add a nice message here"), + Self::Expected(s) => write!(f, "expected at {s:?}"), + } + } +} + +pub struct Parser<'source> { + lexer: Peekable>>, +} + +/// # Helper methods +impl<'source> Parser<'source> { + /// Move the lexer forward and return the token + fn next(&mut self) -> ParseResult<(Token<'source>, Span)> { + match self.lexer.next() { + None => Err(ParseError::EndOfInput), + Some((Err(()), span)) => Err(ParseError::InvalidToken(span)), + Some((Ok(token), span)) => Ok((token, span)), + } + } + + /// Peek the next token + fn peek(&mut self) -> Option<&Token<'source>> { + match self.lexer.peek() { + Some((Ok(token), _span)) => Some(token), + _ => None, + } + } + + /// Peek the next token and return whether it matches the given token + fn peek_is(&mut self, token: Token) -> bool { + let Some(lexed_token) = self.peek() else { + return false; + }; + + &token == lexed_token + } + + /// Check for an optional token + fn accept_optional(&mut self, token: Token) -> ParseResult> { + if self.peek_is(token) { + Ok(Some(self.next()?.1)) + } else { + Ok(None) + } + } + + /// Move the lexer forward and assert that it matches the token + fn accept_required(&mut self, token: Token) -> ParseResult<()> { + let (next, span) = self.next()?; + if next == token { + Ok(()) + } else { + Err(ParseError::Expected(span)) + } + } +} + +/// # Parsing items +impl<'source> Parser<'source> { + pub fn parse(input: &'source str) -> ParseResult { + Self { + lexer: Lexer::new(input).spanned().peekable(), + } + .tree() + } + + fn tree(&mut self) -> ParseResult { + let mut expressions = Vec::new(); + + while self.peek().is_some() { + expressions.push(self.root()?); + } + + Ok(SyntaxTree { expressions }) + } + + fn root(&mut self) -> ParseResult { + let expr = match self.peek().ok_or(ParseError::EndOfInput)? { + Token::Rib => RootExpr::Rib(self.rib()?), + // Token::Table => RootExpr::Table(self.table()), + // Token::OutputStream => RootExpr::OutputStream(self.output_stream()), + // Token::FilterMap => RootExpr::FilterMap(Box::new(self.filter_map()?)), + // Token::Type => RootExpr::Ty(self.record_type_assignment()), + t => panic!("{:?}", t), + }; + Ok(expr) + } + + fn rib(&mut self) -> ParseResult { + self.accept_required(Token::Rib)?; + let ident = self.identifier()?; + self.accept_required(Token::Contains)?; + let contain_ty = self.type_identifier()?; + let body = self.rib_body()?; + + Ok(Rib { + ident, + contain_ty, + body, + }) + } + + fn rib_body(&mut self) -> ParseResult { + self.accept_required(Token::CurlyLeft)?; + + let mut key_values = Vec::new(); + + let mut i = 0; + while !self.peek_is(Token::CurlyRight) { + if i > 0 { + self.accept_required(Token::Colon)?; + } + key_values.push(self.rib_field()?); + i += 1; + } + + self.accept_required(Token::CurlyRight)?; + + Ok(RibBody { key_values }) + } + + fn rib_field(&mut self) -> ParseResult { + let key = self.identifier()?; + self.accept_required(Token::Colon)?; + + let field = if self.peek_is(Token::CurlyLeft) { + // TODO: This recursion seems to be the right thing to do, maybe + // the syntax tree should reflect that. + let RibBody { key_values } = self.rib_body()?; + RibField::RecordField(Box::new(( + key, + RecordTypeIdentifier { key_values }, + ))) + } else if self.accept_optional(Token::SquareLeft)?.is_some() { + let inner_type = self.type_identifier()?; + self.accept_required(Token::SquareRight)?; + RibField::ListField(Box::new(( + key, + ListTypeIdentifier { inner_type }, + ))) + } else { + RibField::PrimitiveField(TypeIdentField { + field_name: key, + ty: self.type_identifier()?, + }) + }; + + Ok(field) + } + + // TODO: I'm ignoring the grammar difference between identifiers and type identifiers + // because it doesn't really make sense, I think. But this comment is here to + // remind me of that before I put this thing up for review. + // Otherwise the lexer will get a bit more complicated. + fn identifier(&mut self) -> ParseResult { + let (token, span) = self.next()?; + match token { + Token::Ident(s) => Ok(Identifier { ident: s.into() }), + _ => Err(ParseError::Expected(span)), + } + } + + fn type_identifier(&mut self) -> ParseResult { + let (token, span) = self.next()?; + match token { + Token::Ident(s) => Ok(TypeIdentifier { ident: s.into() }), + _ => Err(ParseError::Expected(span)), + } + } +} diff --git a/src/token.rs b/src/token.rs new file mode 100644 index 00000000..a3c16c3d --- /dev/null +++ b/src/token.rs @@ -0,0 +1,148 @@ +use logos::Logos; + +#[derive(Logos, Clone, Debug, PartialEq)] +#[logos(skip r"([ \t\n\f]|(//[^\n]*))+")] +pub enum Token<'s> { + #[regex("[a-zA-Z_][a-zA-Z0-9_]*")] + Ident(&'s str), + + // === Punctuation === + #[token("==")] + EqEq, + #[token("=")] + Eq, + #[token("!=")] + BangEq, + #[token("&&")] + AmpAmp, + #[token("||")] + PipePipe, + #[token(">=")] + AngleRightEq, + #[token("<=")] + AngleLeftEq, + #[token("-")] + Hyphen, + #[token(":")] + Colon, + #[token(";")] + SemiColon, + #[token(",")] + Comma, + #[token(".")] + Period, + + // === Delimiters === + #[token("{")] + CurlyLeft, + #[token("}")] + CurlyRight, + #[token("[")] + SquareLeft, + #[token("]")] + SquareRight, + #[token("(")] + RoundLeft, + #[token(")")] + RoundRight, + #[token("<")] + AngleLeft, + #[token(">")] + AngleRight, + + // === Keywords === + #[token("accept")] + Accept, + #[token("action")] + Action, + #[token("all")] + All, + #[token("apply")] + Apply, + #[token("contains")] + Contains, + #[token("define")] + Define, + #[token("exact")] + Exact, + #[token("exactly-one")] + ExactlyOne, + #[token("filter-map")] + FilterMap, + #[token("filter")] + Filter, + #[token("for")] + For, + #[token("import")] + Import, + #[token("longer")] + Longer, + #[token("matching")] + Matching, + #[token("module")] + Module, + #[token("netmask")] + NetMask, + #[token("not")] + Not, + #[token("orlonger")] + OrLonger, + #[token("output-stream")] + OutputStream, + #[token("prefix-length-range")] + PrefixLengthRange, + #[token("reject")] + Reject, + #[token("return")] + Return, + #[token("rib")] + Rib, + #[token("rx")] + Rx, + #[token("rx_tx")] + RxTx, + #[token("some")] + Some, + #[token("table")] + Table, + #[token("term")] + Term, + #[token("tx")] + Tx, + #[token("through")] + Through, + #[token("type")] + Type, + #[token("upto")] + UpTo, + #[token("use")] + Use, + #[token("with")] + With, + + // === Literals === + // String literal with escape sequences would look like this: + // #[regex(r#""([^"\\]|\\["\\bnfrt]|u[a-fA-F0-9]{4})*""#)] + // For now, keep it simple and just lex until the next quote. + #[regex(r#""[^"]*""#)] + String(&'s str), + // Integers can contain underscores, but cannot start with them. + #[regex(r"[0-9][0-9_]*")] + Integer(&'s str), + #[regex(r"0x[0-9A-Fa-f]+")] + Hex, + #[regex(r"AS[0-9]+")] + Asn, + #[regex(r"[0-9]+\.")] + Float, + // IpV4, + // IpV6, + // PrefixV4, + // PrefixV6, + #[regex("/[0-9]+")] + PrefixLength, + #[token("true")] + True, + #[token("false")] + False, +} From 1afe3ca6c874299b1e6384cb03ae10129bd75b34 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Wed, 21 Feb 2024 17:08:45 +0100 Subject: [PATCH 02/24] continue work on the manual parser --- examples/rib.roto | 2 +- src/ast.rs | 1 - src/parser.rs | 767 ++++++++++++++++++++++++++++++++++++++++++++-- src/token.rs | 28 +- 4 files changed, 761 insertions(+), 37 deletions(-) diff --git a/examples/rib.roto b/examples/rib.roto index 38bc07b8..3377c2c9 100644 --- a/examples/rib.roto +++ b/examples/rib.roto @@ -1,5 +1,5 @@ // one comment rib unrib contains Blaffer { // another comment - blaffer: Blaf + blaffer: Blaf, } diff --git a/src/ast.rs b/src/ast.rs index 1a7f7736..f5642d99 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -2544,7 +2544,6 @@ impl TryFrom<&'_ LiteralExpr> for TypeValue { } //------------ LiteralAccessExpr --------------------------------------------- - #[derive(Clone, Debug)] pub struct LiteralAccessExpr { pub literal: LiteralExpr, diff --git a/src/parser.rs b/src/parser.rs index 04142d40..0ad5b382 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,6 +1,16 @@ use crate::ast::{ - Identifier, RecordTypeIdentifier, Rib, RibBody, RibField, RootExpr, - SyntaxTree, TypeIdentField, TypeIdentifier, ListTypeIdentifier + AcceptReject, AccessExpr, AccessReceiver, AnonymousRecordValueExpr, + ApplyBody, ApplyScope, ApplySection, ArgExprList, AsnLiteral, + BooleanLiteral, ComputeExpr, Define, DefineBody, FieldAccessExpr, + FilterMap, FilterMapBody, FilterMapExpr, FilterMatchActionExpr, + FilterType, HexLiteral, Identifier, IntegerLiteral, IpAddress, Ipv4Addr, + Ipv6Addr, ListTypeIdentifier, ListValueExpr, LiteralAccessExpr, + LiteralExpr, MatchActionExpr, MatchOperator, MethodComputeExpr, + OutputStream, Prefix, PrefixLength, PrefixLengthLiteral, + PrefixLengthRange, PrefixMatchExpr, PrefixMatchType, + RecordTypeIdentifier, Rib, RibBody, RibField, RootExpr, RxTxType, + StringLiteral, SyntaxTree, Table, TypeIdentField, TypeIdentifier, + TypedRecordValueExpr, ValueExpr, }; use crate::token::Token; use logos::{Lexer, Span, SpannedIter}; @@ -78,9 +88,55 @@ impl<'source> Parser<'source> { Err(ParseError::Expected(span)) } } + + /// Parse a separated and delimited list of items + /// + /// Assuming that `{`, `}` and `,` are the opening, closing and separating + /// tokens, respectively. And the given parser passes `FOO`, then this + /// function corrsponds to the following grammar rule: + /// + /// ```ebnf + /// '{' (FOO (',' FOO)* ',')? '}' + /// ``` + /// + /// So, the list is allowed to be empty and a trailing separator is allowed. + fn separated( + &mut self, + open: Token, + close: Token, + sep: Token, + mut parser: impl FnMut(&mut Self) -> ParseResult, + ) -> ParseResult> { + self.accept_required(open)?; + + let mut items = Vec::new(); + + // If there are no fields, return the empty vec. + if self.accept_optional(close.clone())?.is_some() { + return Ok(items); + } + + // Parse the first field + items.push(parser(self)?); + + // Now each field must be separated by a comma + while self.accept_optional(sep.clone())?.is_some() { + // If we have found the curly right, we have just + // parsed the trailing comma. + if self.peek_is(close.clone()) { + break; + } + + items.push(parser(self)?); + } + + self.accept_required(close)?; + + Ok(items) + } } -/// # Parsing items +/// # Parsing complex expressions impl<'source> Parser<'source> { pub fn parse(input: &'source str) -> ParseResult { Self { @@ -99,18 +155,34 @@ impl<'source> Parser<'source> { Ok(SyntaxTree { expressions }) } + /// Parse a root expression + /// + /// ```ebnf + /// Root ::= Rib | Table | OutputStream | FilterMap | Type + /// ``` fn root(&mut self) -> ParseResult { let expr = match self.peek().ok_or(ParseError::EndOfInput)? { Token::Rib => RootExpr::Rib(self.rib()?), - // Token::Table => RootExpr::Table(self.table()), - // Token::OutputStream => RootExpr::OutputStream(self.output_stream()), - // Token::FilterMap => RootExpr::FilterMap(Box::new(self.filter_map()?)), + Token::Table => RootExpr::Table(self.table()?), + Token::OutputStream => { + RootExpr::OutputStream(self.output_stream()?) + } + Token::FilterMap | Token::Filter => { + RootExpr::FilterMap(Box::new(self.filter_map()?)) + } // Token::Type => RootExpr::Ty(self.record_type_assignment()), t => panic!("{:?}", t), }; Ok(expr) } + /// Parse a rib expression + /// + /// ```ebnf + /// Rib ::= 'rib' Identifier + /// 'contains' TypeIdentifier + /// RibBody + /// ``` fn rib(&mut self) -> ParseResult { self.accept_required(Token::Rib)?; let ident = self.identifier()?; @@ -125,25 +197,73 @@ impl<'source> Parser<'source> { }) } - fn rib_body(&mut self) -> ParseResult { - self.accept_required(Token::CurlyLeft)?; + /// Parse a table expression + /// + /// ```ebnf + /// Table ::= 'table' Identifier + /// 'contains' TypeIdentifier + /// RibBody + /// ``` + fn table(&mut self) -> ParseResult { + self.accept_required(Token::Table)?; + let ident = self.identifier()?; + self.accept_required(Token::Contains)?; + let contain_ty = self.type_identifier()?; + let body = self.rib_body()?; - let mut key_values = Vec::new(); + Ok(Table { + ident, + contain_ty, + body, + }) + } - let mut i = 0; - while !self.peek_is(Token::CurlyRight) { - if i > 0 { - self.accept_required(Token::Colon)?; - } - key_values.push(self.rib_field()?); - i += 1; - } + /// Parse an output stream expression + /// + /// ```ebnf + /// OutputStream ::= 'output-stream' Identifier + /// 'contains' TypeIdentifier + /// RibBody + /// ``` + fn output_stream(&mut self) -> ParseResult { + self.accept_required(Token::OutputStream)?; + let ident = self.identifier()?; + self.accept_required(Token::Contains)?; + let contain_ty = self.type_identifier()?; + let body = self.rib_body()?; + + Ok(OutputStream { + ident, + contain_ty, + body, + }) + } - self.accept_required(Token::CurlyRight)?; + /// Parse a rib body + /// + /// A rib body is enclosed in curly braces and the fields are separated + /// by commas. A trailing comma is allowed. + /// + /// ```ebnf + /// RibBody ::= '{' ( RibField ( ',' RibField )* ','? ) '}' + /// ``` + fn rib_body(&mut self) -> ParseResult { + let key_values = self.separated( + Token::CurlyLeft, + Token::CurlyRight, + Token::Comma, + Self::rib_field, + )?; Ok(RibBody { key_values }) } + /// Parse a rib field + /// + /// ```ebnf + /// RibField ::= Identifier ':' + /// (RibBody | '[' TypeIdentifier ']' | TypeIdentifier) + /// ``` fn rib_field(&mut self) -> ParseResult { let key = self.identifier()?; self.accept_required(Token::Colon)?; @@ -173,10 +293,613 @@ impl<'source> Parser<'source> { Ok(field) } - // TODO: I'm ignoring the grammar difference between identifiers and type identifiers - // because it doesn't really make sense, I think. But this comment is here to - // remind me of that before I put this thing up for review. - // Otherwise the lexer will get a bit more complicated. + /// Parse a filter-map or filter expression + /// + /// ```ebnf + /// FilterMap ::= ( 'filter-map' | 'filter' ) Identifier + /// For With FilterMapBody + /// ``` + fn filter_map(&mut self) -> ParseResult { + let (token, _span) = self.next()?; + let ty = match token { + Token::FilterMap => FilterType::FilterMap, + Token::Filter => FilterType::Filter, + _ => return Err(ParseError::Todo), + }; + + let ident = self.identifier()?; + let for_ident = self.for_statement()?; + let with_kv = self.with_statement()?; + let body = self.filter_map_body()?; + + Ok(FilterMap { + ty, + ident, + for_ident, + with_kv, + body, + }) + } + + /// Parse the body of a filter-map or filter + /// + /// ```ebnf + /// FilterMapBody ::= '{' Define? FilterMapExpr+ Apply? '}' + /// Define ::= 'define' For With DefineBody + /// Apply ::= 'apply' For With ApplyBody + /// ``` + /// + /// Not shown in the EBNF above, but the location of the define and apply + /// sections doesn't matter, but they can both only appear once. + fn filter_map_body(&mut self) -> ParseResult { + let mut define = None; + let mut expressions: Option> = None; + let mut apply = None; + + self.accept_required(Token::CurlyLeft)?; + + while self.accept_optional(Token::CurlyRight)?.is_none() { + if self.accept_optional(Token::Define)?.is_some() { + if define.is_some() { + // Cannot have multiple define sections + return Err(ParseError::Todo); + } + let for_kv = self.for_statement()?; + let with_kv = self.with_statement()?; + let body = self.define_body()?; + define = Some(Define { + for_kv, + with_kv, + body, + }); + } else if self.accept_optional(Token::Apply)?.is_some() { + if apply.is_some() { + // Cannot have multiple apply sections + return Err(ParseError::Todo); + } + let for_kv = self.for_statement()?; + let with_kv = self.with_statement()?; + let body = self.apply_body()?; + apply = Some(ApplySection { + for_kv, + with_kv, + body, + }); + } else { + let _ = expressions; + todo!("parse expr") + } + } + + Ok(FilterMapBody { + define: define.ok_or(ParseError::Todo)?, + expressions: expressions.unwrap_or_default(), + apply, + }) + } + + /// Parse the body of a define section + /// + /// ```ebnf + /// DefineBody ::= '{' RxTxType Use? Assignment* '}' + /// + /// RxTxType ::= ( 'rx_tx' TypeIdentField ';' + /// | 'rx' TypeIdentField ';' 'tx' TypeIdentField ';' + /// | 'rx' TypeIdentField ) + /// + /// Use ::= 'use' Identifier Identifier + /// + /// Assignment ::= Identifier '=' ValueExpr ';' + /// ``` + fn define_body(&mut self) -> ParseResult { + self.accept_required(Token::CurlyLeft)?; + + let rx_tx_type = match self.next()?.0 { + Token::RxTx => { + let field = self.type_ident_field()?; + self.accept_required(Token::SemiColon)?; + RxTxType::PassThrough(field) + } + Token::Rx => { + let rx_field = self.type_ident_field()?; + self.accept_required(Token::SemiColon)?; + if self.accept_optional(Token::Tx)?.is_some() { + let tx_field = self.type_ident_field()?; + self.accept_required(Token::SemiColon)?; + RxTxType::Split(rx_field, tx_field) + } else { + RxTxType::RxOnly(rx_field) + } + } + t => return Err(ParseError::Todo), + }; + + let mut use_ext_data = Vec::new(); + while self.accept_optional(Token::Use)?.is_some() { + use_ext_data.push((self.identifier()?, self.identifier()?)); + self.accept_required(Token::SemiColon)?; + } + + let mut assignments = Vec::new(); + while self.accept_optional(Token::CurlyRight)?.is_none() { + let id = self.identifier()?; + self.accept_required(Token::Eq)?; + let value = self.value_expr()?; + self.accept_required(Token::SemiColon)?; + assignments.push((id, value)); + } + + Ok(DefineBody { + rx_tx_type, + use_ext_data, + assignments, + }) + } + + /// Parse the body of an apply section + /// + /// ```ebnf + /// ApplyBody ::= ApplyScope* (AcceptReject ';')? + /// ``` + fn apply_body(&mut self) -> ParseResult { + self.accept_required(Token::CurlyLeft)?; + let mut scopes = Vec::new(); + + while !(self.peek_is(Token::Return) + || self.peek_is(Token::CurlyRight)) + { + scopes.push(self.apply_scope()?); + } + + let accept_reject = self.accept_reject()?; + + Ok(ApplyBody { + scopes, + accept_reject, + }) + } + + /// Parse a scope of the body of apply + /// + /// ```ebnf + /// ApplyScope ::= Use? + /// 'filter' MatchOperator ValueExpr + /// 'not'? 'matching' + /// Actions ';' + /// + /// ApplyUse ::= 'use' Identifier ';' + /// Actions ::= '{' Action* '}' + /// Action ::= ValueExpr ';' ( AcceptReject ';' )? + /// ``` + fn apply_scope(&mut self) -> ParseResult { + let scope = self + .accept_optional(Token::Use)? + .map(|_| { + let id = self.identifier()?; + self.accept_required(Token::SemiColon)?; + Ok(id) + }) + .transpose()?; + + self.accept_required(Token::Filter)?; + let operator = self.match_operator()?; + let filter_ident = self.value_expr()?; + let negate = self.accept_optional(Token::Not)?.is_some(); + self.accept_required(Token::Matching)?; + self.accept_required(Token::CurlyLeft)?; + + // TODO: This part needs to be checked by Jasper + // The original seems to have been: + // (ValueExpr ';' (AcceptReject ';')? )+ | AcceptReject ';' + // That does not seem to make a whole lot of sense. Probably, + // some filter is applied later, but we could probably be more + // precise while parsing. + let mut actions = Vec::new(); + while self.accept_optional(Token::CurlyRight)?.is_none() { + // We can try to parse a value expr first + let val = if !matches!(self.peek(), Some(Token::Return)) { + let val = self.value_expr()?; + self.accept_required(Token::SemiColon)?; + Some(val) + } else { + None + }; + + let accept_reject = self.accept_reject()?; + actions.push((val, accept_reject)); + } + + self.accept_required(Token::SemiColon)?; + + Ok(ApplyScope { + scope, + match_action: MatchActionExpr::FilterMatchAction( + FilterMatchActionExpr { + operator, + negate, + actions, + filter_ident, + }, + ), + }) + } + + /// Parse a statement returning accept or reject + /// + /// ```ebnf + /// AcceptReject ::= ('return' ( 'accept' | 'reject' ) ';')? + /// ``` + fn accept_reject(&mut self) -> ParseResult> { + // Note: this is different from the original parser + // In the original, the return is optional, but all the examples seem to + // require it, so it is now required, + // We could choose to remove it entirely as well. + if self.accept_optional(Token::Return)?.is_some() { + let value = match self.next()?.0 { + Token::Accept => AcceptReject::Accept, + Token::Reject => AcceptReject::Reject, + _ => return Err(ParseError::Todo), + }; + self.accept_required(Token::SemiColon)?; + Ok(Some(value)) + } else { + Ok(None) + } + } + + /// Parse a match operator + /// + /// ```ebnf + /// MatchOperator ::= 'match' ( Identifier 'with' )? + /// | 'some' | 'exactly-one' | 'all' + /// ``` + fn match_operator(&mut self) -> ParseResult { + let op = match self.next()?.0 { + Token::Match => { + if matches!(self.peek(), Some(Token::Ident(_))) { + let ident = self.identifier()?; + self.accept_required(Token::With)?; + MatchOperator::MatchValueWith(ident) + } else { + MatchOperator::Match + } + } + Token::Some => MatchOperator::Some, + Token::ExactlyOne => MatchOperator::ExactlyOne, + Token::All => MatchOperator::All, + _ => return Err(ParseError::Todo), + }; + + Ok(op) + } + + /// Parse a value expr + /// + /// ```ebnf + /// ValueExpr ::= '[' ValueExpr* '] + /// | Identifier? Record + /// | MethodCall + /// | Identifier AccessExpr + /// | PrefixMatchExpr + /// | Literal AccessExpr + /// ``` + fn value_expr(&mut self) -> ParseResult { + if self.peek_is(Token::SquareLeft) { + let values = self.separated( + Token::SquareLeft, + Token::SquareRight, + Token::Comma, + Self::value_expr, + )?; + return Ok(ValueExpr::ListExpr(ListValueExpr { values })); + } + + if self.peek_is(Token::CurlyLeft) { + return Ok(ValueExpr::AnonymousRecordExpr( + AnonymousRecordValueExpr { + key_values: self.record()?, + }, + )); + } + + if let Some(Token::Ident(_)) = self.peek() { + if self.peek_is(Token::CurlyLeft) { + return Ok(ValueExpr::TypedRecordExpr( + TypedRecordValueExpr { + type_id: self.type_identifier()?, + key_values: self.record()?, + }, + )); + } + + if self.peek_is(Token::RoundLeft) { + return Ok(ValueExpr::RootMethodCallExpr( + self.method_call()?, + )); + } + + let id = self.identifier()?; + let receiver = AccessReceiver::Ident(id); + let access_expr = self.access_expr()?; + + return Ok(ValueExpr::ComputeExpr(ComputeExpr { + receiver, + access_expr, + })); + } + + let literal = self.literal()?; + + // If we parsed a prefix, it may be followed by a prefix match + if let LiteralExpr::PrefixLiteral(prefix) = literal { + if let Some(ty) = self.prefix_match_type()? { + return Ok(ValueExpr::PrefixMatchExpr(PrefixMatchExpr { + prefix: Prefix { + addr: todo!(), + len: todo!(), + }, + ty, + })); + } + todo!() + } + + let access_expr = self.access_expr()?; + Ok(ValueExpr::LiteralAccessExpr(LiteralAccessExpr { + literal, + access_expr, + })) + } + + /// Parse an access expresion + /// + /// ```ebnf + /// AccessExpr ::= ( '.' ( MethodCallExpr | FieldAccessExpr ) )* + /// ``` + fn access_expr(&mut self) -> ParseResult> { + let mut access_expr = Vec::new(); + + while self.accept_optional(Token::Period)?.is_some() { + let ident = self.identifier()?; + if self.peek_is(Token::CurlyLeft) { + let args = self.arg_expr_list()?; + access_expr.push(AccessExpr::MethodComputeExpr( + MethodComputeExpr { ident, args }, + )) + } else { + // TODO: This is technically different from the nom + // parser, because the nom parser will eagerly get + // multiple fields. This is not necessary and the + // Vec in FieldAccessExpr can probably be removed. + access_expr.push(AccessExpr::FieldAccessExpr( + FieldAccessExpr { + field_names: vec![ident], + }, + )) + } + } + + Ok(access_expr) + } + + /// Parse any literal, including prefixes, ip addresses and communities + fn literal(&mut self) -> ParseResult { + // TODO: Implement the following literals: + // - StandardCommunity + // - LargeCommunity + // - ExtendedCommunity + + // A prefix length, it requires two tokens + if self.accept_optional(Token::Slash)?.is_some() { + let PrefixLength(len) = self.prefix_length()?; + return Ok(LiteralExpr::PrefixLengthLiteral( + PrefixLengthLiteral(len), + )); + } + + // If we see an IpAddress, we need to check whether it is followed by a + // slash and is therefore a prefix instead. + if matches!(self.peek(), Some(Token::IpV4(_) | Token::IpV6(_))) { + let _ip = self.ip_address()?; + if self.accept_optional(Token::Slash)?.is_some() { + let _prefix_len = self.prefix_length()?; + todo!("return prefix literal") + } else { + todo!("return ip literal") + } + } + + self.simple_literal() + } + + /// Parse literals that need no complex parsing, just one token + fn simple_literal(&mut self) -> ParseResult { + // TODO: Make proper errors using the spans + Ok(match self.next()?.0 { + Token::String(s) => { + LiteralExpr::StringLiteral(StringLiteral(s.into())) + } + Token::Integer(s) => LiteralExpr::IntegerLiteral(IntegerLiteral( + // This parse fails if the literal is too big, + // it should be handled properly + s.parse().unwrap(), + )), + Token::Hex(s) => LiteralExpr::HexLiteral(HexLiteral( + u64::from_str_radix(&s[2..], 16).unwrap(), + )), + Token::Asn(s) => LiteralExpr::AsnLiteral(AsnLiteral( + u32::from_str_radix(&s[2..], 16).unwrap(), + )), + Token::Bool(b) => LiteralExpr::BooleanLiteral(BooleanLiteral(b)), + Token::Float => { + unimplemented!("Floating point numbers are not supported yet") + } + _ => return Err(ParseError::Todo), + }) + } + + /// Parse an (anonymous) record + /// + /// ```ebnf + /// Record ::= '{' (RecordField (',' RecordField)* ','? )? '}' + /// RecordField ::= Identifier ':' ValueExpr + /// ``` + fn record(&mut self) -> ParseResult> { + self.separated( + Token::CurlyLeft, + Token::CurlyRight, + Token::Comma, + |parser| { + let key = parser.identifier()?; + parser.accept_required(Token::Colon)?; + let value = parser.value_expr()?; + Ok((key, value)) + }, + ) + } + + /// Parse a method call: an identifier followed by an argument list + /// + /// ```ebnf + /// MethodCall ::= Identifier ArgExprList + /// ``` + fn method_call(&mut self) -> ParseResult { + let ident = self.identifier()?; + let args = self.arg_expr_list()?; + Ok(MethodComputeExpr { ident, args }) + } + + /// Parse a list of arguments to a method + /// + /// ```ebnf + /// ArgExprList ::= '(' ( ValueExpr (',' ValueExpr)* ','? )? ')' + /// ``` + fn arg_expr_list(&mut self) -> ParseResult { + let args = self.separated( + Token::RoundLeft, + Token::RoundRight, + Token::Comma, + Self::value_expr, + )?; + + Ok(ArgExprList { args }) + } + + /// Parse an optional for clause for filter-map, define and apply + /// + /// ```ebnf + /// For ::= ( 'for' TypeIdentField)? + /// ``` + fn for_statement(&mut self) -> ParseResult> { + if self.accept_optional(Token::For)?.is_some() { + Ok(Some(self.type_ident_field()?)) + } else { + Ok(None) + } + } + + /// Parase an optional with clause for filter-map, define and apply + /// + /// ```ebnf + /// With ::= ( 'with' TypeIdentField (',' TypeIdentField)*)? + /// ``` + fn with_statement(&mut self) -> ParseResult> { + let mut key_values = Vec::new(); + + if self.accept_optional(Token::With)?.is_none() { + return Ok(key_values); + } + + key_values.push(self.type_ident_field()?); + while self.accept_optional(Token::Comma)?.is_some() { + key_values.push(self.type_ident_field()?); + } + + Ok(key_values) + } + + /// Parse a prefix match type, which can follow a prefix in some contexts + /// + /// ```ebnf + /// PrefixMatchType ::= 'longer' + /// | 'orlonger' + /// | 'prefix-length-range' PrefixLengthRange + /// | 'upto' PrefixLength + /// | 'netmask' IpAddress + /// ``` + fn prefix_match_type(&mut self) -> ParseResult> { + let match_type = if self.accept_optional(Token::Exact)?.is_some() { + PrefixMatchType::Exact + } else if self.accept_optional(Token::Longer)?.is_some() { + PrefixMatchType::Longer + } else if self.accept_optional(Token::OrLonger)?.is_some() { + PrefixMatchType::OrLonger + } else if self.accept_optional(Token::PrefixLengthRange)?.is_some() { + PrefixMatchType::PrefixLengthRange(self.prefix_length_range()?) + } else if self.accept_optional(Token::UpTo)?.is_some() { + PrefixMatchType::UpTo(self.prefix_length()?) + } else if self.accept_optional(Token::NetMask)?.is_some() { + PrefixMatchType::NetMask(self.ip_address()?) + } else { + return Ok(None); + }; + + Ok(Some(match_type)) + } + + /// Parse a prefix length range + /// + /// ```ebnf + /// PrefixLengthRange ::= PrefixLength '-' PrefixLength + /// ``` + fn prefix_length_range(&mut self) -> ParseResult { + let start = self.prefix_length()?; + self.accept_required(Token::Hyphen)?; + let end = self.prefix_length()?; + Ok(PrefixLengthRange { start, end }) + } + + /// Parse a prefix length + /// + /// ```ebnf + /// PrefixLength ::= '/' Integer + /// ``` + fn prefix_length(&mut self) -> ParseResult { + self.accept_required(Token::Slash)?; + return match self.next()?.0 { + Token::Integer(s) => Ok(PrefixLength(s.parse().unwrap())), + + _ => Err(ParseError::Todo), + }; + } + + /// Parse an identifier and a type identifier separated by a colon + /// + /// ```ebnf + /// TypeIdentField ::= Identifier ':' TypeIdentifier + /// ``` + fn type_ident_field(&mut self) -> ParseResult { + let field_name = self.identifier()?; + self.accept_required(Token::Colon)?; + let ty = self.type_identifier()?; + Ok(TypeIdentField { field_name, ty }) + } +} + +/// # Parsing single items +impl<'source> Parser<'source> { + fn ip_address(&mut self) -> ParseResult { + Ok(match self.next()?.0 { + Token::IpV4(s) => IpAddress::Ipv4(Ipv4Addr(s.parse().unwrap())), + Token::IpV6(s) => IpAddress::Ipv6(Ipv6Addr(s.parse().unwrap())), + _ => return Err(ParseError::Todo), + }) + } + + // TODO: I'm ignoring the grammar difference between identifiers and type + // identifiers because it doesn't really make sense, I think. But this + // comment is here to remind me of that before I put this thing up for + // review. Otherwise the lexer will get a bit more complicated. fn identifier(&mut self) -> ParseResult { let (token, span) = self.next()?; match token { diff --git a/src/token.rs b/src/token.rs index a3c16c3d..d32fc285 100644 --- a/src/token.rs +++ b/src/token.rs @@ -31,6 +31,8 @@ pub enum Token<'s> { Comma, #[token(".")] Period, + #[token("/")] + Slash, // === Delimiters === #[token("{")] @@ -77,6 +79,8 @@ pub enum Token<'s> { Import, #[token("longer")] Longer, + #[token("match")] + Match, #[token("matching")] Matching, #[token("module")] @@ -130,19 +134,17 @@ pub enum Token<'s> { #[regex(r"[0-9][0-9_]*")] Integer(&'s str), #[regex(r"0x[0-9A-Fa-f]+")] - Hex, + Hex(&'s str), #[regex(r"AS[0-9]+")] - Asn, - #[regex(r"[0-9]+\.")] + Asn(&'s str), + #[regex(r"[0-9]+\.[0-9]*")] Float, - // IpV4, - // IpV6, - // PrefixV4, - // PrefixV6, - #[regex("/[0-9]+")] - PrefixLength, - #[token("true")] - True, - #[token("false")] - False, + #[regex(r"([0-9]+\.){3}[0-9]+")] + IpV4(&'s str), + #[regex(r"([0-9a-zA-Z]+:){6}[0-9a-zA-Z]+")] + IpV6(&'s str), + + #[token("true", |_| true)] + #[token("false", |_| false)] + Bool(bool), } From 14024046b05eb0444df922f45e6f6b5ea5d92c83 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Thu, 22 Feb 2024 13:57:03 +0100 Subject: [PATCH 03/24] even more parser stuff --- src/parser.rs | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index 0ad5b382..7bc4e314 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -6,11 +6,11 @@ use crate::ast::{ FilterType, HexLiteral, Identifier, IntegerLiteral, IpAddress, Ipv4Addr, Ipv6Addr, ListTypeIdentifier, ListValueExpr, LiteralAccessExpr, LiteralExpr, MatchActionExpr, MatchOperator, MethodComputeExpr, - OutputStream, Prefix, PrefixLength, PrefixLengthLiteral, + OutputStream, PrefixLength, PrefixLengthLiteral, PrefixLengthRange, PrefixMatchExpr, PrefixMatchType, - RecordTypeIdentifier, Rib, RibBody, RibField, RootExpr, RxTxType, - StringLiteral, SyntaxTree, Table, TypeIdentField, TypeIdentifier, - TypedRecordValueExpr, ValueExpr, + RecordTypeAssignment, RecordTypeIdentifier, Rib, RibBody, RibField, + RootExpr, RxTxType, StringLiteral, SyntaxTree, Table, TypeIdentField, + TypeIdentifier, TypedRecordValueExpr, ValueExpr, }; use crate::token::Token; use logos::{Lexer, Span, SpannedIter}; @@ -73,6 +73,7 @@ impl<'source> Parser<'source> { /// Check for an optional token fn accept_optional(&mut self, token: Token) -> ParseResult> { if self.peek_is(token) { + // TODO: this should probably be an unwrap? Ok(Some(self.next()?.1)) } else { Ok(None) @@ -170,8 +171,8 @@ impl<'source> Parser<'source> { Token::FilterMap | Token::Filter => { RootExpr::FilterMap(Box::new(self.filter_map()?)) } - // Token::Type => RootExpr::Ty(self.record_type_assignment()), - t => panic!("{:?}", t), + Token::Type => RootExpr::Ty(self.record_type_assignment()?), + _ => return Err(ParseError::Todo), }; Ok(expr) } @@ -330,10 +331,10 @@ impl<'source> Parser<'source> { /// ``` /// /// Not shown in the EBNF above, but the location of the define and apply - /// sections doesn't matter, but they can both only appear once. + /// sections doesn't matter, but they can both only appear once. fn filter_map_body(&mut self) -> ParseResult { let mut define = None; - let mut expressions: Option> = None; + let mut _expressions: Option> = None; let mut apply = None; self.accept_required(Token::CurlyLeft)?; @@ -366,14 +367,13 @@ impl<'source> Parser<'source> { body, }); } else { - let _ = expressions; todo!("parse expr") } } Ok(FilterMapBody { define: define.ok_or(ParseError::Todo)?, - expressions: expressions.unwrap_or_default(), + expressions: _expressions.unwrap_or_default(), apply, }) } @@ -411,7 +411,7 @@ impl<'source> Parser<'source> { RxTxType::RxOnly(rx_field) } } - t => return Err(ParseError::Todo), + _ => return Err(ParseError::Todo), }; let mut use_ext_data = Vec::new(); @@ -634,10 +634,7 @@ impl<'source> Parser<'source> { if let LiteralExpr::PrefixLiteral(prefix) = literal { if let Some(ty) = self.prefix_match_type()? { return Ok(ValueExpr::PrefixMatchExpr(PrefixMatchExpr { - prefix: Prefix { - addr: todo!(), - len: todo!(), - }, + prefix, ty, })); } @@ -681,7 +678,13 @@ impl<'source> Parser<'source> { Ok(access_expr) } - + + fn record_type_assignment( + &mut self, + ) -> ParseResult { + todo!() + } + /// Parse any literal, including prefixes, ip addresses and communities fn literal(&mut self) -> ParseResult { // TODO: Implement the following literals: From 85590577293572431cf2a184d4b7ce824433820c Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Tue, 27 Feb 2024 00:08:35 +0100 Subject: [PATCH 04/24] restructure parser --- src/parser/filter_map.rs | 330 +++++++++++++++++++++++++++++++ src/{parser.rs => parser/mod.rs} | 221 +-------------------- src/parser/rib_like.rs | 121 ++++++++++++ src/token.rs | 6 + 4 files changed, 467 insertions(+), 211 deletions(-) create mode 100644 src/parser/filter_map.rs rename src/{parser.rs => parser/mod.rs} (77%) create mode 100644 src/parser/rib_like.rs diff --git a/src/parser/filter_map.rs b/src/parser/filter_map.rs new file mode 100644 index 00000000..f71ede86 --- /dev/null +++ b/src/parser/filter_map.rs @@ -0,0 +1,330 @@ +use crate::{ + ast::{ + ActionSection, AndExpr, ApplySection, BooleanExpr, CompareArg, + CompareExpr, CompareOp, Define, FilterMap, FilterMapBody, + FilterMapExpr, FilterType, GroupedLogicalExpr, LogicalExpr, + MatchOperator, NotExpr, OrExpr, TermBody, TermPatternMatchArm, + TermScope, TermSection, ValueExpr, + }, + token::Token, +}; + +use super::{ParseError, ParseResult, Parser}; + +impl<'source> Parser<'source> { + /// Parse a filter-map or filter expression + /// + /// ```ebnf + /// FilterMap ::= ( 'filter-map' | 'filter' ) Identifier + /// For With FilterMapBody + /// ``` + pub(super) fn filter_map(&mut self) -> ParseResult { + let (token, _span) = self.next()?; + let ty = match token { + Token::FilterMap => FilterType::FilterMap, + Token::Filter => FilterType::Filter, + _ => return Err(ParseError::Todo), + }; + + let ident = self.identifier()?; + let for_ident = self.for_statement()?; + let with_kv = self.with_statement()?; + let body = self.filter_map_body()?; + + Ok(FilterMap { + ty, + ident, + for_ident, + with_kv, + body, + }) + } + + /// Parse the body of a filter-map or filter + /// + /// ```ebnf + /// FilterMapBody ::= '{' Define? FilterMapExpr+ Apply? '}' + /// Define ::= 'define' For With DefineBody + /// Apply ::= 'apply' For With ApplyBody + /// ``` + /// + /// Not shown in the EBNF above, but the location of the define and apply + /// sections doesn't matter, but they can both only appear once. + fn filter_map_body(&mut self) -> ParseResult { + let mut define = None; + let mut expressions: Vec = Vec::new(); + let mut apply = None; + + self.accept_required(Token::CurlyLeft)?; + + while self.accept_optional(Token::CurlyRight)?.is_none() { + if self.accept_optional(Token::Define)?.is_some() { + if define.is_some() { + // Cannot have multiple define sections + return Err(ParseError::Todo); + } + let for_kv = self.for_statement()?; + let with_kv = self.with_statement()?; + let body = self.define_body()?; + define = Some(Define { + for_kv, + with_kv, + body, + }); + } else if self.accept_optional(Token::Apply)?.is_some() { + if apply.is_some() { + // Cannot have multiple apply sections + return Err(ParseError::Todo); + } + let for_kv = self.for_statement()?; + let with_kv = self.with_statement()?; + let body = self.apply_body()?; + apply = Some(ApplySection { + for_kv, + with_kv, + body, + }); + } else { + expressions.push(self.filter_map_expr()?); + } + } + + Ok(FilterMapBody { + define: define.ok_or(ParseError::Todo)?, + expressions, + apply, + }) + } + + /// Parse a filter map expression, which is a term or an action + /// + /// ```ebnf + /// FilterMapExpr ::= Term | Action + /// ``` + fn filter_map_expr(&mut self) -> ParseResult { + if self.peek_is(Token::Term) { + Ok(FilterMapExpr::Term(self.term()?)) + } else if self.peek_is(Token::Action) { + Ok(FilterMapExpr::Action(self.action()?)) + } else { + Err(ParseError::Todo) + } + } + + fn term(&mut self) -> ParseResult { + self.accept_required(Token::Term)?; + let ident = self.identifier()?; + let for_kv = self.for_statement()?; + let with_kv = self.with_statement()?; + + let mut scopes = Vec::new(); + self.accept_required(Token::CurlyLeft)?; + while self.accept_optional(Token::CurlyRight)?.is_none() { + scopes.push(self.term_scope()?); + } + + Ok(TermSection { + ident, + for_kv, + with_kv, + body: TermBody { scopes }, + }) + } + + fn term_scope(&mut self) -> ParseResult { + let operator = self.match_operator()?; + + // In this case, we'll start pattern matching, otherwise, + // the match will contain logical expressions. + if let MatchOperator::MatchValueWith(_) = operator { + let mut match_arms = Vec::new(); + self.accept_required(Token::CurlyLeft)?; + while self.accept_optional(Token::CurlyRight)?.is_none() { + match_arms.push(self.match_arm()?); + } + + Ok(TermScope { + // TODO: remove the scope field (and rename this type probably) + scope: None, + operator, + match_arms, + }) + } else { + let expr = self.logical_expr()?; + self.accept_required(Token::SemiColon)?; + Ok(TermScope { + scope: None, + operator, + match_arms: vec![(None, vec![expr])], + }) + } + } + + fn match_arm( + &mut self, + ) -> ParseResult<(Option, Vec)> { + let variant_id = self.identifier()?; + + let data_field = self + .accept_optional(Token::RoundLeft)? + .map(|_| { + let field = self.identifier()?; + self.accept_required(Token::RoundRight)?; + Ok(field) + }) + .transpose()?; + + self.accept_required(Token::Arrow)?; + + let mut expr = Vec::new(); + if self.accept_optional(Token::CurlyLeft)?.is_some() { + while self.accept_optional(Token::CurlyRight)?.is_none() { + expr.push(self.logical_expr()?); + self.accept_required(Token::SemiColon)?; + } + } else { + expr.push(self.logical_expr()?); + // This comma might need to be optional, but it's probably good + // practice to require it. + self.accept_required(Token::Comma)?; + } + + Ok(( + Some(TermPatternMatchArm { + variant_id, + data_field, + }), + expr, + )) + } + + /// Parse a logical expression + /// + /// ```ebnf + /// LogicalExpr ::= '!' BooleanExpr + /// | BooleanExpr '||' BooleanExpr + /// | BooleanExpr '&&' BooleanExpr + /// | BooleanExpr + /// ``` + fn logical_expr(&mut self) -> ParseResult { + if self.accept_optional(Token::Bang)?.is_some() { + let expr = self.boolean_expr()?; + return Ok(LogicalExpr::NotExpr(NotExpr { expr })); + } + + let left = self.boolean_expr()?; + + Ok(if self.accept_optional(Token::PipePipe)?.is_some() { + let right = self.boolean_expr()?; + LogicalExpr::OrExpr(OrExpr { left, right }) + } else if self.accept_optional(Token::AmpAmp)?.is_some() { + let right = self.boolean_expr()?; + LogicalExpr::AndExpr(AndExpr { left, right }) + } else { + LogicalExpr::BooleanExpr(left) + }) + } + + /// Parse a boolean expression + /// + /// ```ebnf + /// BooleanExpr ::= GroupedLogicalExpr + /// | CompareExpr + /// | ComputeExpr + /// | LiteralAccessExpr + /// | PrefixMatchExpr + /// + /// CompareExpr ::= CompareArg CompareOp CompareArg + /// CompareArg ::= ValueExpr | GroupedLogicalExpr + /// ``` + fn boolean_expr(&mut self) -> ParseResult { + let left = self.logical_or_value_expr()?; + + if let Some(op) = self.try_compare_operator()? { + let right = self.logical_or_value_expr()?; + return Ok(BooleanExpr::CompareExpr(Box::new(CompareExpr { + left, + op, + right, + }))); + } + + // If it's not a compare expression, we need to filter out some + // possibilities. + // - A grouped logical expr does not appear in any of the other + // production rules, so we can return it directly. + // - A value expr is too general and needs to be asserted to be one + // of the allowed constructs. + let v = match left { + CompareArg::GroupedLogicalExpr(l) => { + return Ok(BooleanExpr::GroupedLogicalExpr(l)) + } + CompareArg::ValueExpr(v) => v, + }; + + Ok(match v { + ValueExpr::LiteralAccessExpr(x) => { + BooleanExpr::LiteralAccessExpr(x) + } + ValueExpr::PrefixMatchExpr(x) => BooleanExpr::PrefixMatchExpr(x), + ValueExpr::ComputeExpr(x) => BooleanExpr::ComputeExpr(x), + ValueExpr::RootMethodCallExpr(_) + | ValueExpr::AnonymousRecordExpr(_) + | ValueExpr::TypedRecordExpr(_) + | ValueExpr::ListExpr(_) => return Err(ParseError::Todo), + }) + } + + fn logical_or_value_expr(&mut self) -> ParseResult { + Ok(if self.peek_is(Token::RoundLeft) { + CompareArg::GroupedLogicalExpr(self.grouped_logical_expr()?) + } else { + CompareArg::ValueExpr(self.value_expr()?) + }) + } + + /// Optionally parse a compare operator + /// + /// This method returns option, because we are never sure that there is + /// going to be a comparison operator. + /// + /// ```ebnf + /// CompareOp ::= '==' | '!=' | '<' | '<=' | '>' | '>=' | 'not'? 'in' + /// ``` + fn try_compare_operator(&mut self) -> ParseResult> { + let Some(tok) = self.peek() else { + return Ok(None); + }; + + let op = match tok { + Token::EqEq => CompareOp::Eq, + Token::BangEq => CompareOp::Ne, + Token::AngleLeft => CompareOp::Lt, + Token::AngleRight => CompareOp::Gt, + Token::AngleLeftEq => CompareOp::Le, + Token::AngleRightEq => CompareOp::Ge, + Token::In => CompareOp::In, + Token::Not => { + self.accept_required(Token::In)?; + CompareOp::NotIn + } + _ => return Ok(None), + }; + + self.next()?; + Ok(Some(op)) + } + + fn grouped_logical_expr(&mut self) -> ParseResult { + self.accept_required(Token::RoundLeft)?; + let expr = self.logical_expr()?; + self.accept_required(Token::RoundRight)?; + Ok(GroupedLogicalExpr { + expr: Box::new(expr), + }) + } + + fn action(&mut self) -> ParseResult { + todo!() + } +} diff --git a/src/parser.rs b/src/parser/mod.rs similarity index 77% rename from src/parser.rs rename to src/parser/mod.rs index 7bc4e314..7eb2e6ab 100644 --- a/src/parser.rs +++ b/src/parser/mod.rs @@ -1,21 +1,21 @@ use crate::ast::{ AcceptReject, AccessExpr, AccessReceiver, AnonymousRecordValueExpr, - ApplyBody, ApplyScope, ApplySection, ArgExprList, AsnLiteral, - BooleanLiteral, ComputeExpr, Define, DefineBody, FieldAccessExpr, - FilterMap, FilterMapBody, FilterMapExpr, FilterMatchActionExpr, - FilterType, HexLiteral, Identifier, IntegerLiteral, IpAddress, Ipv4Addr, - Ipv6Addr, ListTypeIdentifier, ListValueExpr, LiteralAccessExpr, - LiteralExpr, MatchActionExpr, MatchOperator, MethodComputeExpr, - OutputStream, PrefixLength, PrefixLengthLiteral, + ApplyBody, ApplyScope, ArgExprList, AsnLiteral, BooleanLiteral, + ComputeExpr, DefineBody, FieldAccessExpr, FilterMatchActionExpr, + HexLiteral, Identifier, IntegerLiteral, IpAddress, Ipv4Addr, Ipv6Addr, + ListValueExpr, LiteralAccessExpr, LiteralExpr, MatchActionExpr, + MatchOperator, MethodComputeExpr, PrefixLength, PrefixLengthLiteral, PrefixLengthRange, PrefixMatchExpr, PrefixMatchType, - RecordTypeAssignment, RecordTypeIdentifier, Rib, RibBody, RibField, - RootExpr, RxTxType, StringLiteral, SyntaxTree, Table, TypeIdentField, - TypeIdentifier, TypedRecordValueExpr, ValueExpr, + RecordTypeAssignment, RootExpr, RxTxType, StringLiteral, SyntaxTree, + TypeIdentField, TypeIdentifier, TypedRecordValueExpr, ValueExpr, }; use crate::token::Token; use logos::{Lexer, Span, SpannedIter}; use std::iter::Peekable; +mod rib_like; +mod filter_map; + type ParseResult = Result; #[derive(Debug)] @@ -177,207 +177,6 @@ impl<'source> Parser<'source> { Ok(expr) } - /// Parse a rib expression - /// - /// ```ebnf - /// Rib ::= 'rib' Identifier - /// 'contains' TypeIdentifier - /// RibBody - /// ``` - fn rib(&mut self) -> ParseResult { - self.accept_required(Token::Rib)?; - let ident = self.identifier()?; - self.accept_required(Token::Contains)?; - let contain_ty = self.type_identifier()?; - let body = self.rib_body()?; - - Ok(Rib { - ident, - contain_ty, - body, - }) - } - - /// Parse a table expression - /// - /// ```ebnf - /// Table ::= 'table' Identifier - /// 'contains' TypeIdentifier - /// RibBody - /// ``` - fn table(&mut self) -> ParseResult
{ - self.accept_required(Token::Table)?; - let ident = self.identifier()?; - self.accept_required(Token::Contains)?; - let contain_ty = self.type_identifier()?; - let body = self.rib_body()?; - - Ok(Table { - ident, - contain_ty, - body, - }) - } - - /// Parse an output stream expression - /// - /// ```ebnf - /// OutputStream ::= 'output-stream' Identifier - /// 'contains' TypeIdentifier - /// RibBody - /// ``` - fn output_stream(&mut self) -> ParseResult { - self.accept_required(Token::OutputStream)?; - let ident = self.identifier()?; - self.accept_required(Token::Contains)?; - let contain_ty = self.type_identifier()?; - let body = self.rib_body()?; - - Ok(OutputStream { - ident, - contain_ty, - body, - }) - } - - /// Parse a rib body - /// - /// A rib body is enclosed in curly braces and the fields are separated - /// by commas. A trailing comma is allowed. - /// - /// ```ebnf - /// RibBody ::= '{' ( RibField ( ',' RibField )* ','? ) '}' - /// ``` - fn rib_body(&mut self) -> ParseResult { - let key_values = self.separated( - Token::CurlyLeft, - Token::CurlyRight, - Token::Comma, - Self::rib_field, - )?; - - Ok(RibBody { key_values }) - } - - /// Parse a rib field - /// - /// ```ebnf - /// RibField ::= Identifier ':' - /// (RibBody | '[' TypeIdentifier ']' | TypeIdentifier) - /// ``` - fn rib_field(&mut self) -> ParseResult { - let key = self.identifier()?; - self.accept_required(Token::Colon)?; - - let field = if self.peek_is(Token::CurlyLeft) { - // TODO: This recursion seems to be the right thing to do, maybe - // the syntax tree should reflect that. - let RibBody { key_values } = self.rib_body()?; - RibField::RecordField(Box::new(( - key, - RecordTypeIdentifier { key_values }, - ))) - } else if self.accept_optional(Token::SquareLeft)?.is_some() { - let inner_type = self.type_identifier()?; - self.accept_required(Token::SquareRight)?; - RibField::ListField(Box::new(( - key, - ListTypeIdentifier { inner_type }, - ))) - } else { - RibField::PrimitiveField(TypeIdentField { - field_name: key, - ty: self.type_identifier()?, - }) - }; - - Ok(field) - } - - /// Parse a filter-map or filter expression - /// - /// ```ebnf - /// FilterMap ::= ( 'filter-map' | 'filter' ) Identifier - /// For With FilterMapBody - /// ``` - fn filter_map(&mut self) -> ParseResult { - let (token, _span) = self.next()?; - let ty = match token { - Token::FilterMap => FilterType::FilterMap, - Token::Filter => FilterType::Filter, - _ => return Err(ParseError::Todo), - }; - - let ident = self.identifier()?; - let for_ident = self.for_statement()?; - let with_kv = self.with_statement()?; - let body = self.filter_map_body()?; - - Ok(FilterMap { - ty, - ident, - for_ident, - with_kv, - body, - }) - } - - /// Parse the body of a filter-map or filter - /// - /// ```ebnf - /// FilterMapBody ::= '{' Define? FilterMapExpr+ Apply? '}' - /// Define ::= 'define' For With DefineBody - /// Apply ::= 'apply' For With ApplyBody - /// ``` - /// - /// Not shown in the EBNF above, but the location of the define and apply - /// sections doesn't matter, but they can both only appear once. - fn filter_map_body(&mut self) -> ParseResult { - let mut define = None; - let mut _expressions: Option> = None; - let mut apply = None; - - self.accept_required(Token::CurlyLeft)?; - - while self.accept_optional(Token::CurlyRight)?.is_none() { - if self.accept_optional(Token::Define)?.is_some() { - if define.is_some() { - // Cannot have multiple define sections - return Err(ParseError::Todo); - } - let for_kv = self.for_statement()?; - let with_kv = self.with_statement()?; - let body = self.define_body()?; - define = Some(Define { - for_kv, - with_kv, - body, - }); - } else if self.accept_optional(Token::Apply)?.is_some() { - if apply.is_some() { - // Cannot have multiple apply sections - return Err(ParseError::Todo); - } - let for_kv = self.for_statement()?; - let with_kv = self.with_statement()?; - let body = self.apply_body()?; - apply = Some(ApplySection { - for_kv, - with_kv, - body, - }); - } else { - todo!("parse expr") - } - } - - Ok(FilterMapBody { - define: define.ok_or(ParseError::Todo)?, - expressions: _expressions.unwrap_or_default(), - apply, - }) - } - /// Parse the body of a define section /// /// ```ebnf diff --git a/src/parser/rib_like.rs b/src/parser/rib_like.rs new file mode 100644 index 00000000..922e6488 --- /dev/null +++ b/src/parser/rib_like.rs @@ -0,0 +1,121 @@ +use super::{Parser, ParseResult}; +use crate::{token::Token, ast::{Rib, Table, OutputStream, RibField, RecordTypeIdentifier, RibBody, ListTypeIdentifier, TypeIdentField}}; + +impl<'source> Parser<'source> { + /// Parse a rib expression + /// + /// ```ebnf + /// Rib ::= 'rib' Identifier + /// 'contains' TypeIdentifier + /// RibBody + /// ``` + pub(super) fn rib(&mut self) -> ParseResult { + self.accept_required(Token::Rib)?; + let ident = self.identifier()?; + self.accept_required(Token::Contains)?; + let contain_ty = self.type_identifier()?; + let body = self.rib_body()?; + + Ok(Rib { + ident, + contain_ty, + body, + }) + } + + /// Parse a table expression + /// + /// ```ebnf + /// Table ::= 'table' Identifier + /// 'contains' TypeIdentifier + /// RibBody + /// ``` + pub(super) fn table(&mut self) -> ParseResult
{ + self.accept_required(Token::Table)?; + let ident = self.identifier()?; + self.accept_required(Token::Contains)?; + let contain_ty = self.type_identifier()?; + let body = self.rib_body()?; + + Ok(Table { + ident, + contain_ty, + body, + }) + } + + /// Parse an output stream expression + /// + /// ```ebnf + /// OutputStream ::= 'output-stream' Identifier + /// 'contains' TypeIdentifier + /// RibBody + /// ``` + pub(super) fn output_stream(&mut self) -> ParseResult { + self.accept_required(Token::OutputStream)?; + let ident = self.identifier()?; + self.accept_required(Token::Contains)?; + let contain_ty = self.type_identifier()?; + let body = self.rib_body()?; + + Ok(OutputStream { + ident, + contain_ty, + body, + }) + } + + /// Parse a rib body + /// + /// A rib body is enclosed in curly braces and the fields are separated + /// by commas. A trailing comma is allowed. + /// + /// ```ebnf + /// RibBody ::= '{' ( RibField ( ',' RibField )* ','? ) '}' + /// ``` + fn rib_body(&mut self) -> ParseResult { + let key_values = self.separated( + Token::CurlyLeft, + Token::CurlyRight, + Token::Comma, + Self::rib_field, + )?; + + Ok(RibBody { key_values }) + } + + /// Parse a rib field + /// + /// ```ebnf + /// RibField ::= Identifier ':' + /// (RibBody | '[' TypeIdentifier ']' | TypeIdentifier) + /// ``` + fn rib_field(&mut self) -> ParseResult { + let key = self.identifier()?; + self.accept_required(Token::Colon)?; + + let field = if self.peek_is(Token::CurlyLeft) { + // TODO: This recursion seems to be the right thing to do, maybe + // the syntax tree should reflect that. + let RibBody { key_values } = self.rib_body()?; + RibField::RecordField(Box::new(( + key, + RecordTypeIdentifier { key_values }, + ))) + } else if self.accept_optional(Token::SquareLeft)?.is_some() { + let inner_type = self.type_identifier()?; + self.accept_required(Token::SquareRight)?; + RibField::ListField(Box::new(( + key, + ListTypeIdentifier { inner_type }, + ))) + } else { + RibField::PrimitiveField(TypeIdentField { + field_name: key, + ty: self.type_identifier()?, + }) + }; + + Ok(field) + } +} diff --git a/src/token.rs b/src/token.rs index d32fc285..d2680cb4 100644 --- a/src/token.rs +++ b/src/token.rs @@ -21,6 +21,8 @@ pub enum Token<'s> { AngleRightEq, #[token("<=")] AngleLeftEq, + #[token("->")] + Arrow, #[token("-")] Hyphen, #[token(":")] @@ -33,6 +35,8 @@ pub enum Token<'s> { Period, #[token("/")] Slash, + #[token("!")] + Bang, // === Delimiters === #[token("{")] @@ -77,6 +81,8 @@ pub enum Token<'s> { For, #[token("import")] Import, + #[token("in")] + In, #[token("longer")] Longer, #[token("match")] From fcdf435b5ca53fe389ab95e8f175b263c0945377 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Tue, 27 Feb 2024 14:53:30 +0100 Subject: [PATCH 05/24] more parser work --- src/parser/mod.rs | 116 ++++++++++++++++++++--------------------- src/parser/rib_like.rs | 23 +++++++- src/token.rs | 5 ++ 3 files changed, 84 insertions(+), 60 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 7eb2e6ab..9bb121eb 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -1,20 +1,22 @@ use crate::ast::{ AcceptReject, AccessExpr, AccessReceiver, AnonymousRecordValueExpr, ApplyBody, ApplyScope, ArgExprList, AsnLiteral, BooleanLiteral, - ComputeExpr, DefineBody, FieldAccessExpr, FilterMatchActionExpr, - HexLiteral, Identifier, IntegerLiteral, IpAddress, Ipv4Addr, Ipv6Addr, - ListValueExpr, LiteralAccessExpr, LiteralExpr, MatchActionExpr, - MatchOperator, MethodComputeExpr, PrefixLength, PrefixLengthLiteral, + ComputeExpr, DefineBody, ExtendedCommunityLiteral, FieldAccessExpr, + FilterMatchActionExpr, HexLiteral, Identifier, IntegerLiteral, IpAddress, + Ipv4Addr, Ipv6Addr, LargeCommunityLiteral, ListValueExpr, + LiteralAccessExpr, LiteralExpr, MatchActionExpr, MatchOperator, + MethodComputeExpr, Prefix, PrefixLength, PrefixLengthLiteral, PrefixLengthRange, PrefixMatchExpr, PrefixMatchType, - RecordTypeAssignment, RootExpr, RxTxType, StringLiteral, SyntaxTree, - TypeIdentField, TypeIdentifier, TypedRecordValueExpr, ValueExpr, + RecordTypeAssignment, RootExpr, RxTxType, StandardCommunityLiteral, + StringLiteral, SyntaxTree, TypeIdentField, TypeIdentifier, + TypedRecordValueExpr, ValueExpr, }; use crate::token::Token; use logos::{Lexer, Span, SpannedIter}; use std::iter::Peekable; -mod rib_like; mod filter_map; +mod rib_like; type ParseResult = Result; @@ -261,60 +263,41 @@ impl<'source> Parser<'source> { /// Parse a scope of the body of apply /// /// ```ebnf - /// ApplyScope ::= Use? - /// 'filter' MatchOperator ValueExpr + /// ApplyScope ::= 'filter' 'match' ValueExpr /// 'not'? 'matching' /// Actions ';' /// - /// ApplyUse ::= 'use' Identifier ';' - /// Actions ::= '{' Action* '}' - /// Action ::= ValueExpr ';' ( AcceptReject ';' )? + /// Actions ::= '{' (ValueExpr ';')* ( AcceptReject ';' )? '}' /// ``` fn apply_scope(&mut self) -> ParseResult { - let scope = self - .accept_optional(Token::Use)? - .map(|_| { - let id = self.identifier()?; - self.accept_required(Token::SemiColon)?; - Ok(id) - }) - .transpose()?; - self.accept_required(Token::Filter)?; - let operator = self.match_operator()?; + self.accept_required(Token::Match)?; + let filter_ident = self.value_expr()?; let negate = self.accept_optional(Token::Not)?.is_some(); self.accept_required(Token::Matching)?; self.accept_required(Token::CurlyLeft)?; - // TODO: This part needs to be checked by Jasper - // The original seems to have been: - // (ValueExpr ';' (AcceptReject ';')? )+ | AcceptReject ';' - // That does not seem to make a whole lot of sense. Probably, - // some filter is applied later, but we could probably be more - // precise while parsing. let mut actions = Vec::new(); while self.accept_optional(Token::CurlyRight)?.is_none() { - // We can try to parse a value expr first - let val = if !matches!(self.peek(), Some(Token::Return)) { - let val = self.value_expr()?; - self.accept_required(Token::SemiColon)?; - Some(val) - } else { - None - }; + if let Some(accept_reject) = self.accept_reject()? { + self.accept_required(Token::CurlyRight)?; + actions.push((None, Some(accept_reject))); + break; + } - let accept_reject = self.accept_reject()?; - actions.push((val, accept_reject)); + let val = self.value_expr()?; + self.accept_required(Token::SemiColon)?; + actions.push((Some(val), None)); } self.accept_required(Token::SemiColon)?; Ok(ApplyScope { - scope, + scope: None, match_action: MatchActionExpr::FilterMatchAction( FilterMatchActionExpr { - operator, + operator: MatchOperator::Match, negate, actions, filter_ident, @@ -430,14 +413,14 @@ impl<'source> Parser<'source> { let literal = self.literal()?; // If we parsed a prefix, it may be followed by a prefix match - if let LiteralExpr::PrefixLiteral(prefix) = literal { + // If not, it can be an access expression + if let LiteralExpr::PrefixLiteral(prefix) = &literal { if let Some(ty) = self.prefix_match_type()? { return Ok(ValueExpr::PrefixMatchExpr(PrefixMatchExpr { - prefix, + prefix: prefix.clone(), ty, })); } - todo!() } let access_expr = self.access_expr()?; @@ -478,12 +461,6 @@ impl<'source> Parser<'source> { Ok(access_expr) } - fn record_type_assignment( - &mut self, - ) -> ParseResult { - todo!() - } - /// Parse any literal, including prefixes, ip addresses and communities fn literal(&mut self) -> ParseResult { // TODO: Implement the following literals: @@ -502,12 +479,12 @@ impl<'source> Parser<'source> { // If we see an IpAddress, we need to check whether it is followed by a // slash and is therefore a prefix instead. if matches!(self.peek(), Some(Token::IpV4(_) | Token::IpV6(_))) { - let _ip = self.ip_address()?; - if self.accept_optional(Token::Slash)?.is_some() { - let _prefix_len = self.prefix_length()?; - todo!("return prefix literal") + let addr = self.ip_address()?; + if self.peek_is(Token::Slash) { + let len = self.prefix_length()?; + return Ok(LiteralExpr::PrefixLiteral(Prefix { addr, len })); } else { - todo!("return ip literal") + return Ok(LiteralExpr::IpAddressLiteral(addr)); } } @@ -536,6 +513,33 @@ impl<'source> Parser<'source> { Token::Float => { unimplemented!("Floating point numbers are not supported yet") } + Token::Community(s) => { + // We offload the validation of the community to routecore + // TODO: Change the AST so that it doesn't contain strings, but + // routecore communities. + use routecore::bgp::communities::Community; + let c: Community = s.parse().map_err(|_| ParseError::Todo)?; + match c { + Community::Standard(x) => { + LiteralExpr::StandardCommunityLiteral( + StandardCommunityLiteral(x.to_string()), + ) + } + Community::Extended(x) => { + LiteralExpr::ExtendedCommunityLiteral( + ExtendedCommunityLiteral(x.to_string()), + ) + } + Community::Large(x) => { + LiteralExpr::LargeCommunityLiteral( + LargeCommunityLiteral(x.to_string()), + ) + } + Community::Ipv6Extended(_) => { + unimplemented!("IPv6 extended communities are not supported yet") + } + } + } _ => return Err(ParseError::Todo), }) } @@ -698,10 +702,6 @@ impl<'source> Parser<'source> { }) } - // TODO: I'm ignoring the grammar difference between identifiers and type - // identifiers because it doesn't really make sense, I think. But this - // comment is here to remind me of that before I put this thing up for - // review. Otherwise the lexer will get a bit more complicated. fn identifier(&mut self) -> ParseResult { let (token, span) = self.next()?; match token { diff --git a/src/parser/rib_like.rs b/src/parser/rib_like.rs index 922e6488..fa4495c5 100644 --- a/src/parser/rib_like.rs +++ b/src/parser/rib_like.rs @@ -1,5 +1,11 @@ -use super::{Parser, ParseResult}; -use crate::{token::Token, ast::{Rib, Table, OutputStream, RibField, RecordTypeIdentifier, RibBody, ListTypeIdentifier, TypeIdentField}}; +use super::{ParseResult, Parser}; +use crate::{ + ast::{ + ListTypeIdentifier, OutputStream, RecordTypeAssignment, + RecordTypeIdentifier, Rib, RibBody, RibField, Table, TypeIdentField, + }, + token::Token, +}; impl<'source> Parser<'source> { /// Parse a rib expression @@ -65,6 +71,19 @@ impl<'source> Parser<'source> { }) } + pub(super) fn record_type_assignment( + &mut self, + ) -> ParseResult { + self.accept_required(Token::Type)?; + let ident = self.type_identifier()?; + let body = self.rib_body()?; + let record_type = RecordTypeIdentifier { + key_values: body.key_values, + }; + + Ok(RecordTypeAssignment { ident, record_type }) + } + /// Parse a rib body /// /// A rib body is enclosed in curly braces and the fields are separated diff --git a/src/token.rs b/src/token.rs index d2680cb4..85ea26b4 100644 --- a/src/token.rs +++ b/src/token.rs @@ -150,6 +150,11 @@ pub enum Token<'s> { #[regex(r"([0-9a-zA-Z]+:){6}[0-9a-zA-Z]+")] IpV6(&'s str), + // This regex is a super set of all the forms of communities: + // standard, large and extended. + #[regex(r"([0-9a-zA-Z]+:)?(0x)?[0-9a-fA-F]+:(0x)?[0-9a-fA-F]+")] + Community(&'s str), + #[token("true", |_| true)] #[token("false", |_| false)] Bool(bool), From 2cad1d662fee3602a293d1e7dea7ed38a79d401d Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Wed, 28 Feb 2024 10:00:28 +0100 Subject: [PATCH 06/24] more parser work --- Cargo.toml | 1 + src/ast.rs | 8 ++- src/compiler/compile.rs | 40 +++++++++---- src/parser/filter_map.rs | 46 ++++++++++++--- src/parser/mod.rs | 106 ++++++++++++++++++++++++---------- src/token.rs | 81 +++++++++++++++++++++++++- tests/accept_reject_simple.rs | 5 +- tests/helpers.rs | 4 +- 8 files changed, 236 insertions(+), 55 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3815c236..e10aa092 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ smallvec = { version = "1.11", features = [ "const_generics", "serde" ] } serde = { version = "1.0", features = [ "derive", "rc" ] } routecore = { version = "0.4.0", features = ["bgp", "bmp", "serde"] } rotonda-store = { version = "0.3.0" } +miette = { version = "7.1.0", features = ["fancy"] } [dev-dependencies] env_logger = "0.10" diff --git a/src/ast.rs b/src/ast.rs index f5642d99..08511fd9 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -10,7 +10,7 @@ use nom::character::complete::{ }; use nom::combinator::{all_consuming, cut, not, opt, recognize}; use nom::error::{ - context, ErrorKind, FromExternalError, ParseError, VerboseError, + context, ErrorKind, FromExternalError, ParseError as _, VerboseError, }; use nom::multi::{ fold_many0, many0, many1, separated_list0, separated_list1, @@ -33,6 +33,7 @@ use routecore::bgp::communities::{ExtendedCommunity, LargeCommunity, StandardCom use routecore::asn::Asn; use crate::compiler::error::CompileError; +use crate::parser::{Parser, ParseError}; use crate::types::typevalue::TypeValue; use crate::{first_into_compile_err, parse_string}; @@ -50,8 +51,9 @@ pub struct SyntaxTree { impl SyntaxTree { pub fn parse_str( input: &str, - ) -> Result<(&str, Self), VerboseError<&str>> { - Self::parse_root(input).finish() + ) -> Result<(&str, Self), ParseError> { + Parser::parse(input).map(|tree| ("", tree)) + // Self::parse_root(input).finish() } fn parse_root(input: &str) -> IResult<&str, Self, VerboseError<&str>> { diff --git a/src/compiler/compile.rs b/src/compiler/compile.rs index 39fb28aa..2601df9a 100644 --- a/src/compiler/compile.rs +++ b/src/compiler/compile.rs @@ -40,21 +40,22 @@ use crate::{ ast::{self, AcceptReject, FilterType, ShortString, SyntaxTree}, blocks::Scope, compiler::recurse_compile::recurse_compile, + parser::ParseError, symbols::{ self, DepsGraph, GlobalSymbolTable, MatchActionType, Symbol, SymbolKind, SymbolTable, }, traits::Token, - types::{datasources::DataSource, typevalue::TypeValue}, types::{ - datasources::Table, + datasources::{DataSource, Table}, typedef::{RecordTypeDef, TypeDef}, + typevalue::TypeValue, }, vm::{ compute_hash, Command, CommandArg, CompiledCollectionField, CompiledField, CompiledPrimitiveField, CompiledVariable, - ExtDataSource, FilterMapArg, FilterMapArgs, OpCode, StackRefPos, - VariablesRefTable, FieldIndex, + ExtDataSource, FieldIndex, FilterMapArg, FilterMapArgs, OpCode, + StackRefPos, VariablesRefTable, }, }; @@ -818,8 +819,20 @@ impl<'a> Compiler { pub fn parse_source_code( &mut self, source_code: &'a str, - ) -> Result<(), VerboseError<&'a str>> { - self.ast = SyntaxTree::parse_str(source_code)?.1; + ) -> Result<(), miette::Report> { + match SyntaxTree::parse_str(source_code) { + Ok((_, ast)) => { + let ast2 = SyntaxTree::parse_str(source_code).unwrap().1; + eprintln!("{ast:?}"); + eprintln!(); + eprintln!("{ast2:?}"); + self.ast = ast; + } + Err(e) => { + return Err(miette::Report::new(e) + .with_source_code(source_code.to_string())); + } + }; Ok(()) } @@ -946,7 +959,7 @@ impl<'a> Compiler { source_code: &'a str, ) -> Result { self.parse_source_code(source_code) - .map_err(|err| format!("Parse error: {err}"))?; + .map_err(|err| format!("{:?}", err))?; self.eval_ast() .map_err(|err| format!("Eval error: {err}"))?; self.inject_compile_time_arguments() @@ -1169,7 +1182,9 @@ fn compile_filter_map( // compile the variables used in the terms state = compile_assignments(state)?; - if state.cur_mem_pos == 0 { state.cur_mem_pos = 2 }; + if state.cur_mem_pos == 0 { + state.cur_mem_pos = 2 + }; (mir, state) = compile_apply_section(mir, state)?; state.cur_mir_block = MirBlock::new(); @@ -1255,7 +1270,8 @@ fn compile_assignments( // can just increase it, but if there are none, we're going to skip // over 0 and 1, since they should host the RxType and TxType // respectively. - state.cur_mem_pos = u32::max(2, 1 + state.used_variables.len() as u32); + state.cur_mem_pos = + u32::max(2, 1 + state.used_variables.len() as u32); trace!( "VAR {:?} MEM POS {} TEMP POS START {}", var.0, @@ -1811,7 +1827,11 @@ fn compile_apply_section( } } - state = compile_match_action(match_action.get_match_action(), vec![], state)?; + state = compile_match_action( + match_action.get_match_action(), + vec![], + state, + )?; } _ => { return Err(CompileError::new("invalid match action".into())); diff --git a/src/parser/filter_map.rs b/src/parser/filter_map.rs index f71ede86..4c787353 100644 --- a/src/parser/filter_map.rs +++ b/src/parser/filter_map.rs @@ -1,7 +1,8 @@ use crate::{ ast::{ - ActionSection, AndExpr, ApplySection, BooleanExpr, CompareArg, - CompareExpr, CompareOp, Define, FilterMap, FilterMapBody, + AccessExpr, AccessReceiver, ActionSection, ActionSectionBody, + AndExpr, ApplySection, BooleanExpr, CompareArg, CompareExpr, + CompareOp, ComputeExpr, Define, FilterMap, FilterMapBody, FilterMapExpr, FilterType, GroupedLogicalExpr, LogicalExpr, MatchOperator, NotExpr, OrExpr, TermBody, TermPatternMatchArm, TermScope, TermSection, ValueExpr, @@ -23,7 +24,7 @@ impl<'source> Parser<'source> { let ty = match token { Token::FilterMap => FilterType::FilterMap, Token::Filter => FilterType::Filter, - _ => return Err(ParseError::Todo), + _ => return Err(ParseError::Todo(1)), }; let ident = self.identifier()?; @@ -61,7 +62,7 @@ impl<'source> Parser<'source> { if self.accept_optional(Token::Define)?.is_some() { if define.is_some() { // Cannot have multiple define sections - return Err(ParseError::Todo); + return Err(ParseError::Todo(2)); } let for_kv = self.for_statement()?; let with_kv = self.with_statement()?; @@ -74,7 +75,7 @@ impl<'source> Parser<'source> { } else if self.accept_optional(Token::Apply)?.is_some() { if apply.is_some() { // Cannot have multiple apply sections - return Err(ParseError::Todo); + return Err(ParseError::Todo(3)); } let for_kv = self.for_statement()?; let with_kv = self.with_statement()?; @@ -90,7 +91,7 @@ impl<'source> Parser<'source> { } Ok(FilterMapBody { - define: define.ok_or(ParseError::Todo)?, + define: define.ok_or(ParseError::Todo(4))?, expressions, apply, }) @@ -107,7 +108,7 @@ impl<'source> Parser<'source> { } else if self.peek_is(Token::Action) { Ok(FilterMapExpr::Action(self.action()?)) } else { - Err(ParseError::Todo) + Err(ParseError::Todo(5)) } } @@ -150,8 +151,10 @@ impl<'source> Parser<'source> { match_arms, }) } else { + self.accept_required(Token::CurlyLeft)?; let expr = self.logical_expr()?; self.accept_required(Token::SemiColon)?; + self.accept_required(Token::CurlyRight)?; Ok(TermScope { scope: None, operator, @@ -271,7 +274,7 @@ impl<'source> Parser<'source> { ValueExpr::RootMethodCallExpr(_) | ValueExpr::AnonymousRecordExpr(_) | ValueExpr::TypedRecordExpr(_) - | ValueExpr::ListExpr(_) => return Err(ParseError::Todo), + | ValueExpr::ListExpr(_) => return Err(ParseError::Todo(6)), }) } @@ -325,6 +328,31 @@ impl<'source> Parser<'source> { } fn action(&mut self) -> ParseResult { - todo!() + self.accept_required(Token::Action)?; + let ident = self.identifier()?; + let with_kv = self.with_statement()?; + + let mut expressions = Vec::new(); + self.accept_required(Token::CurlyLeft)?; + while self.accept_optional(Token::CurlyRight)?.is_none() { + let value_expr = self.value_expr()?; + self.accept_required(Token::SemiColon)?; + match value_expr { + ValueExpr::ComputeExpr(x) => expressions.push(x), + ValueExpr::RootMethodCallExpr(x) => { + expressions.push(ComputeExpr { + receiver: AccessReceiver::GlobalScope, + access_expr: vec![AccessExpr::MethodComputeExpr(x)], + }) + } + _ => return Err(ParseError::Todo(7)), + } + } + + Ok(ActionSection { + ident, + with_kv, + body: ActionSectionBody { expressions }, + }) } } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 9bb121eb..6a8b7f86 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -6,13 +6,13 @@ use crate::ast::{ Ipv4Addr, Ipv6Addr, LargeCommunityLiteral, ListValueExpr, LiteralAccessExpr, LiteralExpr, MatchActionExpr, MatchOperator, MethodComputeExpr, Prefix, PrefixLength, PrefixLengthLiteral, - PrefixLengthRange, PrefixMatchExpr, PrefixMatchType, - RecordTypeAssignment, RootExpr, RxTxType, StandardCommunityLiteral, - StringLiteral, SyntaxTree, TypeIdentField, TypeIdentifier, - TypedRecordValueExpr, ValueExpr, + PrefixLengthRange, PrefixMatchExpr, PrefixMatchType, RootExpr, RxTxType, + StandardCommunityLiteral, StringLiteral, SyntaxTree, TypeIdentField, + TypeIdentifier, TypedRecordValueExpr, ValueExpr, }; use crate::token::Token; use logos::{Lexer, Span, SpannedIter}; +use miette::Diagnostic; use std::iter::Peekable; mod filter_map; @@ -20,26 +20,41 @@ mod rib_like; type ParseResult = Result; -#[derive(Debug)] +#[derive(Clone, Debug, Diagnostic)] pub enum ParseError { EndOfInput, InvalidToken(Span), /// Dummy variant where more precise messages should be made - Todo, - Expected(Span), + /// + /// The argument is just a unique identifier + Todo(usize), + Expected { + expected: String, + got: String, + #[label("expected {expected}")] + span: Span, + }, } impl std::fmt::Display for ParseError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::EndOfInput => write!(f, "reached end of input"), + Self::EndOfInput => write!(f, "unexpected end of input"), Self::InvalidToken(_) => write!(f, "invalid token"), - Self::Todo => write!(f, "add a nice message here"), - Self::Expected(s) => write!(f, "expected at {s:?}"), + Self::Todo(n) => write!(f, "add a nice message here {n}"), + Self::Expected { + expected, + got, + span, + } => { + write!(f, "expected '{expected}' but got '{got}' at {span:?}") + } } } } +impl std::error::Error for ParseError {} + pub struct Parser<'source> { lexer: Peekable>>, } @@ -88,7 +103,11 @@ impl<'source> Parser<'source> { if next == token { Ok(()) } else { - Err(ParseError::Expected(span)) + Err(ParseError::Expected { + expected: token.to_string(), + got: next.to_string(), + span, + }) } } @@ -174,7 +193,16 @@ impl<'source> Parser<'source> { RootExpr::FilterMap(Box::new(self.filter_map()?)) } Token::Type => RootExpr::Ty(self.record_type_assignment()?), - _ => return Err(ParseError::Todo), + _ => { + let (token, span) = self.next()?; + return Err(ParseError::Expected { + expected: + "a rib, table, output-stream, filter or filter-map" + .into(), + got: token.to_string(), + span, + }); + } }; Ok(expr) } @@ -212,7 +240,7 @@ impl<'source> Parser<'source> { RxTxType::RxOnly(rx_field) } } - _ => return Err(ParseError::Todo), + _ => return Err(ParseError::Todo(9)), }; let mut use_ext_data = Vec::new(); @@ -253,7 +281,7 @@ impl<'source> Parser<'source> { } let accept_reject = self.accept_reject()?; - + self.accept_required(Token::CurlyRight)?; Ok(ApplyBody { scopes, accept_reject, @@ -287,7 +315,7 @@ impl<'source> Parser<'source> { } let val = self.value_expr()?; - self.accept_required(Token::SemiColon)?; + self.accept_required(Token::SemiColon)?; actions.push((Some(val), None)); } @@ -320,7 +348,7 @@ impl<'source> Parser<'source> { let value = match self.next()?.0 { Token::Accept => AcceptReject::Accept, Token::Reject => AcceptReject::Reject, - _ => return Err(ParseError::Todo), + _ => return Err(ParseError::Todo(10)), }; self.accept_required(Token::SemiColon)?; Ok(Some(value)) @@ -349,7 +377,7 @@ impl<'source> Parser<'source> { Token::Some => MatchOperator::Some, Token::ExactlyOne => MatchOperator::ExactlyOne, Token::All => MatchOperator::All, - _ => return Err(ParseError::Todo), + _ => return Err(ParseError::Todo(11)), }; Ok(op) @@ -385,22 +413,24 @@ impl<'source> Parser<'source> { } if let Some(Token::Ident(_)) = self.peek() { + let id = self.identifier()?; if self.peek_is(Token::CurlyLeft) { + let Identifier { ident: s } = id; return Ok(ValueExpr::TypedRecordExpr( TypedRecordValueExpr { - type_id: self.type_identifier()?, + type_id: TypeIdentifier { ident: s }, key_values: self.record()?, }, )); } if self.peek_is(Token::RoundLeft) { + let args = self.arg_expr_list()?; return Ok(ValueExpr::RootMethodCallExpr( - self.method_call()?, + MethodComputeExpr { ident: id, args }, )); } - let id = self.identifier()?; let receiver = AccessReceiver::Ident(id); let access_expr = self.access_expr()?; @@ -440,7 +470,7 @@ impl<'source> Parser<'source> { while self.accept_optional(Token::Period)?.is_some() { let ident = self.identifier()?; - if self.peek_is(Token::CurlyLeft) { + if self.peek_is(Token::RoundLeft) { let args = self.arg_expr_list()?; access_expr.push(AccessExpr::MethodComputeExpr( MethodComputeExpr { ident, args }, @@ -494,7 +524,8 @@ impl<'source> Parser<'source> { /// Parse literals that need no complex parsing, just one token fn simple_literal(&mut self) -> ParseResult { // TODO: Make proper errors using the spans - Ok(match self.next()?.0 { + let (token, span) = self.next()?; + Ok(match token { Token::String(s) => { LiteralExpr::StringLiteral(StringLiteral(s.into())) } @@ -518,7 +549,8 @@ impl<'source> Parser<'source> { // TODO: Change the AST so that it doesn't contain strings, but // routecore communities. use routecore::bgp::communities::Community; - let c: Community = s.parse().map_err(|_| ParseError::Todo)?; + let c: Community = + s.parse().map_err(|_| ParseError::Todo(12))?; match c { Community::Standard(x) => { LiteralExpr::StandardCommunityLiteral( @@ -536,11 +568,19 @@ impl<'source> Parser<'source> { ) } Community::Ipv6Extended(_) => { - unimplemented!("IPv6 extended communities are not supported yet") + unimplemented!( + "IPv6 extended communities are not supported yet" + ) } } } - _ => return Err(ParseError::Todo), + t => { + return Err(ParseError::Expected { + expected: "a literal".into(), + got: t.to_string(), + span, + }) + } }) } @@ -675,7 +715,7 @@ impl<'source> Parser<'source> { return match self.next()?.0 { Token::Integer(s) => Ok(PrefixLength(s.parse().unwrap())), - _ => Err(ParseError::Todo), + _ => Err(ParseError::Todo(14)), }; } @@ -698,7 +738,7 @@ impl<'source> Parser<'source> { Ok(match self.next()?.0 { Token::IpV4(s) => IpAddress::Ipv4(Ipv4Addr(s.parse().unwrap())), Token::IpV6(s) => IpAddress::Ipv6(Ipv6Addr(s.parse().unwrap())), - _ => return Err(ParseError::Todo), + _ => return Err(ParseError::Todo(14)), }) } @@ -706,7 +746,11 @@ impl<'source> Parser<'source> { let (token, span) = self.next()?; match token { Token::Ident(s) => Ok(Identifier { ident: s.into() }), - _ => Err(ParseError::Expected(span)), + _ => Err(ParseError::Expected { + expected: "an identifier".into(), + got: token.to_string(), + span, + }), } } @@ -714,7 +758,11 @@ impl<'source> Parser<'source> { let (token, span) = self.next()?; match token { Token::Ident(s) => Ok(TypeIdentifier { ident: s.into() }), - _ => Err(ParseError::Expected(span)), + _ => Err(ParseError::Expected { + expected: "an identifier".into(), + got: token.to_string(), + span, + }), } } } diff --git a/src/token.rs b/src/token.rs index 85ea26b4..493f81a9 100644 --- a/src/token.rs +++ b/src/token.rs @@ -1,9 +1,11 @@ +use std::fmt::Display; + use logos::Logos; #[derive(Logos, Clone, Debug, PartialEq)] #[logos(skip r"([ \t\n\f]|(//[^\n]*))+")] pub enum Token<'s> { - #[regex("[a-zA-Z_][a-zA-Z0-9_]*")] + #[regex("[a-zA-Z_][a-zA-Z0-9_-]*")] Ident(&'s str), // === Punctuation === @@ -159,3 +161,80 @@ pub enum Token<'s> { #[token("false", |_| false)] Bool(bool), } + +impl<'source> Display for Token<'source> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let s = match self { + Token::Ident(_) => "identifier", + Token::EqEq => "==", + Token::Eq => "=", + Token::BangEq => "!=", + Token::AmpAmp => "&&", + Token::PipePipe => "||", + Token::AngleRightEq => ">=", + Token::AngleLeftEq => "<=", + Token::Arrow => "->", + Token::Hyphen => "-", + Token::Colon => ":", + Token::SemiColon => ";", + Token::Comma => ",", + Token::Period => ".", + Token::Slash => "/", + Token::Bang => "!", + Token::CurlyLeft => "{", + Token::CurlyRight => "}", + Token::SquareLeft => "[", + Token::SquareRight => "]", + Token::RoundLeft => "(", + Token::RoundRight => ")", + Token::AngleLeft => "<", + Token::AngleRight => ">", + Token::Accept => "accept", + Token::Action => "action", + Token::All => "all", + Token::Apply => "apply", + Token::Contains => "contains", + Token::Define => "define", + Token::Exact => "exact", + Token::ExactlyOne => "exactly-one", + Token::FilterMap => "filter-map", + Token::Filter => "filter", + Token::For => "for", + Token::Import => "import", + Token::In => "in", + Token::Longer => "longer", + Token::Match => "match", + Token::Matching => "matching", + Token::Module => "module", + Token::NetMask => "net-mask", + Token::Not => "not", + Token::OrLonger => "or-longer", + Token::OutputStream => "output-stream", + Token::PrefixLengthRange => "prefix-length-range", + Token::Reject => "reject", + Token::Return => "return", + Token::Rib => "rib", + Token::Rx => "rx", + Token::RxTx => "rx_tx", + Token::Some => "some", + Token::Table => "table", + Token::Term => "term", + Token::Tx => "tx", + Token::Through => "through", + Token::Type => "type", + Token::UpTo => "up-to", + Token::Use => "use", + Token::With => "with", + Token::String(_) => "", + Token::Integer(_) => "", + Token::Hex(_) => "hex literal", + Token::Asn(_) => "ASN", + Token::Float => "float", + Token::IpV4(_) => "IPv4", + Token::IpV6(_) => "IPv6", + Token::Community(_) => "community", + Token::Bool(_) => "bool", + }; + write!(f, "{s}") + } +} \ No newline at end of file diff --git a/tests/accept_reject_simple.rs b/tests/accept_reject_simple.rs index 2903eebb..dfa5502c 100644 --- a/tests/accept_reject_simple.rs +++ b/tests/accept_reject_simple.rs @@ -101,7 +101,10 @@ fn test_filter_map_10() { ); let test_run = test_data(FilterMap("my-filter-map".into()), src_line); - assert!(test_run.is_ok()); + if let Err(e) = &test_run { + println!("{}", e); + assert!(false); + } let VmResult { accept_reject, .. } = test_run.unwrap(); assert_eq!(accept_reject, AcceptReject::Accept); diff --git a/tests/helpers.rs b/tests/helpers.rs index e131a68f..c72b8a9d 100644 --- a/tests/helpers.rs +++ b/tests/helpers.rs @@ -26,8 +26,8 @@ impl<'a> TestCompiler<'a> { let parse_res = self.compiler.parse_source_code(self.source_code); trace!("{} {:#?}", self.name, self.compiler.ast); - if let Err(e) = parse_res.clone() { - trace!("{}", convert_error(self.source_code, e)); + if let Err(e) = &parse_res { + trace!("{:?}", e); } match expect_success { From db48792c908fe0da2c210237eb03314c213a8c56 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Wed, 28 Feb 2024 10:35:37 +0100 Subject: [PATCH 07/24] add parser comparison for debugging --- src/ast.rs | 2 +- src/compiler/compile.rs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ast.rs b/src/ast.rs index 08511fd9..848e86c5 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -56,7 +56,7 @@ impl SyntaxTree { // Self::parse_root(input).finish() } - fn parse_root(input: &str) -> IResult<&str, Self, VerboseError<&str>> { + pub fn parse_root(input: &str) -> IResult<&str, Self, VerboseError<&str>> { let (input, expressions) = all_consuming(many1(preceded( skip_opt_ws, terminated(RootExpr::parse, skip_opt_ws), diff --git a/src/compiler/compile.rs b/src/compiler/compile.rs index 2601df9a..16318656 100644 --- a/src/compiler/compile.rs +++ b/src/compiler/compile.rs @@ -822,7 +822,8 @@ impl<'a> Compiler { ) -> Result<(), miette::Report> { match SyntaxTree::parse_str(source_code) { Ok((_, ast)) => { - let ast2 = SyntaxTree::parse_str(source_code).unwrap().1; + use nom::Finish; + let ast2 = SyntaxTree::parse_root(source_code).finish().unwrap().1; eprintln!("{ast:?}"); eprintln!(); eprintln!("{ast2:?}"); From 9634f098ac2e931d0efaea43ad65c55610e8cd30 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Wed, 28 Feb 2024 10:54:38 +0100 Subject: [PATCH 08/24] flip meaning of negate and restrict AS to decimal in nom parser --- src/ast.rs | 4 ++-- src/eval.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ast.rs b/src/ast.rs index 848e86c5..d3a7b172 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -963,7 +963,7 @@ impl ApplyScope { FilterMatchActionExpr { operator: expr.0, negate: if let Some(negate) = expr.1 .1 { - negate.is_none() + negate.is_some() } else { false }, @@ -1977,7 +1977,7 @@ impl AsnLiteral { tuple(( recognize(pair( tag("AS"), - take_while1(|ch: char| ch.is_ascii_hexdigit()), + take_while1(|ch: char| ch.is_ascii_digit()), )), not(char(':')), )), diff --git a/src/eval.rs b/src/eval.rs index da51ec95..3d503762 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -1114,11 +1114,11 @@ impl ast::ApplyScope { term.name, if fma.negate { symbols::SymbolKind::MatchAction( - MatchActionType::Filter, + MatchActionType::Negate, ) } else { symbols::SymbolKind::MatchAction( - MatchActionType::Negate, + MatchActionType::Filter, ) }, // The AcceptReject value from the Apply section does not end up From 969273c00978f896d67c3e6c6a5bf81127e18d7b Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Wed, 28 Feb 2024 10:57:10 +0100 Subject: [PATCH 09/24] restrict AS to decimal in new parser --- src/parser/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 6a8b7f86..62047e08 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -538,7 +538,7 @@ impl<'source> Parser<'source> { u64::from_str_radix(&s[2..], 16).unwrap(), )), Token::Asn(s) => LiteralExpr::AsnLiteral(AsnLiteral( - u32::from_str_radix(&s[2..], 16).unwrap(), + s[2..].parse::().unwrap(), )), Token::Bool(b) => LiteralExpr::BooleanLiteral(BooleanLiteral(b)), Token::Float => { From c4ddfa0ebcf90bbdc5721704d898fa9a5c06a617 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Wed, 28 Feb 2024 16:48:23 +0100 Subject: [PATCH 10/24] parser seems to be passing all the tests! --- Cargo.toml | 2 +- src/ast.rs | 11 +- src/compiler/compile.rs | 11 +- src/parser/filter_map.rs | 55 +++++--- src/parser/mod.rs | 271 ++++++++++++++++++++++++++++++--------- src/token.rs | 29 +++-- tests/bgp_filters.rs | 18 ++- tests/bmp_message.rs | 40 +++++- tests/compare.rs | 10 +- tests/data_sources.rs | 19 ++- tests/end_to_end.rs | 20 ++- tests/filter_maps.rs | 8 +- tests/helpers.rs | 2 +- tests/lists.rs | 7 +- tests/records.rs | 9 +- 15 files changed, 371 insertions(+), 141 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e10aa092..066da038 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ smallvec = { version = "1.11", features = [ "const_generics", "serde" ] } serde = { version = "1.0", features = [ "derive", "rc" ] } routecore = { version = "0.4.0", features = ["bgp", "bmp", "serde"] } rotonda-store = { version = "0.3.0" } -miette = { version = "7.1.0", features = ["fancy"] } +miette = { version = "7.1.0", features = ["fancy"] } [dev-dependencies] env_logger = "0.10" diff --git a/src/ast.rs b/src/ast.rs index d3a7b172..776e47a6 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -24,7 +24,7 @@ use nom::{ combinator::map, IResult, }; -use nom::{AsChar, Finish}; +use nom::AsChar; use serde::{Serialize, Serializer}; use smallvec::SmallVec; @@ -51,9 +51,12 @@ pub struct SyntaxTree { impl SyntaxTree { pub fn parse_str( input: &str, - ) -> Result<(&str, Self), ParseError> { - Parser::parse(input).map(|tree| ("", tree)) - // Self::parse_root(input).finish() + ) -> Result { + let tree = Parser::parse(input)?; + if tree.expressions.is_empty() { + return Err(ParseError::EmptyInput); + } + Ok(tree) } pub fn parse_root(input: &str) -> IResult<&str, Self, VerboseError<&str>> { diff --git a/src/compiler/compile.rs b/src/compiler/compile.rs index 16318656..86c96716 100644 --- a/src/compiler/compile.rs +++ b/src/compiler/compile.rs @@ -34,13 +34,11 @@ use std::{ }; use log::{log_enabled, trace, Level}; -use nom::error::VerboseError; use crate::{ ast::{self, AcceptReject, FilterType, ShortString, SyntaxTree}, blocks::Scope, compiler::recurse_compile::recurse_compile, - parser::ParseError, symbols::{ self, DepsGraph, GlobalSymbolTable, MatchActionType, Symbol, SymbolKind, SymbolTable, @@ -821,14 +819,7 @@ impl<'a> Compiler { source_code: &'a str, ) -> Result<(), miette::Report> { match SyntaxTree::parse_str(source_code) { - Ok((_, ast)) => { - use nom::Finish; - let ast2 = SyntaxTree::parse_root(source_code).finish().unwrap().1; - eprintln!("{ast:?}"); - eprintln!(); - eprintln!("{ast2:?}"); - self.ast = ast; - } + Ok(ast) => self.ast = ast, Err(e) => { return Err(miette::Report::new(e) .with_source_code(source_code.to_string())); diff --git a/src/parser/filter_map.rs b/src/parser/filter_map.rs index 4c787353..39732d9f 100644 --- a/src/parser/filter_map.rs +++ b/src/parser/filter_map.rs @@ -3,9 +3,9 @@ use crate::{ AccessExpr, AccessReceiver, ActionSection, ActionSectionBody, AndExpr, ApplySection, BooleanExpr, CompareArg, CompareExpr, CompareOp, ComputeExpr, Define, FilterMap, FilterMapBody, - FilterMapExpr, FilterType, GroupedLogicalExpr, LogicalExpr, - MatchOperator, NotExpr, OrExpr, TermBody, TermPatternMatchArm, - TermScope, TermSection, ValueExpr, + FilterMapExpr, FilterType, GroupedLogicalExpr, ListCompareExpr, + LogicalExpr, MatchOperator, NotExpr, OrExpr, TermBody, + TermPatternMatchArm, TermScope, TermSection, ValueExpr, }, token::Token, }; @@ -141,7 +141,8 @@ impl<'source> Parser<'source> { let mut match_arms = Vec::new(); self.accept_required(Token::CurlyLeft)?; while self.accept_optional(Token::CurlyRight)?.is_none() { - match_arms.push(self.match_arm()?); + let (pattern, expr) = self.match_arm()?; + match_arms.push((Some(pattern), expr)); } Ok(TermScope { @@ -152,20 +153,22 @@ impl<'source> Parser<'source> { }) } else { self.accept_required(Token::CurlyLeft)?; - let expr = self.logical_expr()?; - self.accept_required(Token::SemiColon)?; - self.accept_required(Token::CurlyRight)?; + let mut match_arms = Vec::new(); + while self.accept_optional(Token::CurlyRight)?.is_none() { + match_arms.push((None, vec![self.logical_expr()?])); + self.accept_required(Token::SemiColon)?; + } Ok(TermScope { scope: None, operator, - match_arms: vec![(None, vec![expr])], + match_arms, }) } } - fn match_arm( + pub(super) fn match_arm( &mut self, - ) -> ParseResult<(Option, Vec)> { + ) -> ParseResult<(TermPatternMatchArm, Vec)> { let variant_id = self.identifier()?; let data_field = self @@ -185,6 +188,7 @@ impl<'source> Parser<'source> { expr.push(self.logical_expr()?); self.accept_required(Token::SemiColon)?; } + self.accept_optional(Token::Comma)?; } else { expr.push(self.logical_expr()?); // This comma might need to be optional, but it's probably good @@ -193,10 +197,10 @@ impl<'source> Parser<'source> { } Ok(( - Some(TermPatternMatchArm { + TermPatternMatchArm { variant_id, data_field, - }), + }, expr, )) } @@ -244,12 +248,22 @@ impl<'source> Parser<'source> { let left = self.logical_or_value_expr()?; if let Some(op) = self.try_compare_operator()? { - let right = self.logical_or_value_expr()?; - return Ok(BooleanExpr::CompareExpr(Box::new(CompareExpr { - left, - op, - right, - }))); + if op == CompareOp::In || op == CompareOp::NotIn { + let CompareArg::ValueExpr(left) = left else { + return Err(ParseError::Todo(16)); + }; + let right = self.value_expr()?; + return Ok(BooleanExpr::ListCompareExpr(Box::new( + ListCompareExpr { left, op, right }, + ))); + } else { + let right = self.logical_or_value_expr()?; + return Ok(BooleanExpr::CompareExpr(Box::new(CompareExpr { + left, + op, + right, + }))); + } } // If it's not a compare expression, we need to filter out some @@ -308,8 +322,9 @@ impl<'source> Parser<'source> { Token::AngleRightEq => CompareOp::Ge, Token::In => CompareOp::In, Token::Not => { + self.accept_required(Token::Not)?; self.accept_required(Token::In)?; - CompareOp::NotIn + return Ok(Some(CompareOp::NotIn)); } _ => return Ok(None), }; @@ -326,7 +341,7 @@ impl<'source> Parser<'source> { expr: Box::new(expr), }) } - + fn action(&mut self) -> ParseResult { self.accept_required(Token::Action)?; let ident = self.identifier()?; diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 62047e08..10c94692 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -1,14 +1,15 @@ use crate::ast::{ - AcceptReject, AccessExpr, AccessReceiver, AnonymousRecordValueExpr, - ApplyBody, ApplyScope, ArgExprList, AsnLiteral, BooleanLiteral, - ComputeExpr, DefineBody, ExtendedCommunityLiteral, FieldAccessExpr, - FilterMatchActionExpr, HexLiteral, Identifier, IntegerLiteral, IpAddress, - Ipv4Addr, Ipv6Addr, LargeCommunityLiteral, ListValueExpr, - LiteralAccessExpr, LiteralExpr, MatchActionExpr, MatchOperator, - MethodComputeExpr, Prefix, PrefixLength, PrefixLengthLiteral, + AcceptReject, AccessExpr, AccessReceiver, ActionCallExpr, + AnonymousRecordValueExpr, ApplyBody, ApplyScope, ArgExprList, AsnLiteral, + BooleanLiteral, ComputeExpr, DefineBody, ExtendedCommunityLiteral, + FieldAccessExpr, FilterMatchActionExpr, HexLiteral, Identifier, + IntegerLiteral, IpAddress, Ipv4Addr, Ipv6Addr, LargeCommunityLiteral, + ListValueExpr, LiteralAccessExpr, LiteralExpr, MatchActionExpr, + MatchOperator, MethodComputeExpr, PatternMatchActionArm, + PatternMatchActionExpr, Prefix, PrefixLength, PrefixLengthLiteral, PrefixLengthRange, PrefixMatchExpr, PrefixMatchType, RootExpr, RxTxType, - StandardCommunityLiteral, StringLiteral, SyntaxTree, TypeIdentField, - TypeIdentifier, TypedRecordValueExpr, ValueExpr, + StandardCommunityLiteral, StringLiteral, SyntaxTree, TermCallExpr, + TypeIdentField, TypeIdentifier, TypedRecordValueExpr, ValueExpr, }; use crate::token::Token; use logos::{Lexer, Span, SpannedIter}; @@ -22,8 +23,9 @@ type ParseResult = Result; #[derive(Clone, Debug, Diagnostic)] pub enum ParseError { + EmptyInput, EndOfInput, - InvalidToken(Span), + InvalidToken(#[label("invalid token")] Span), /// Dummy variant where more precise messages should be made /// /// The argument is just a unique identifier @@ -34,20 +36,33 @@ pub enum ParseError { #[label("expected {expected}")] span: Span, }, + InvalidLiteral { + description: String, + token: String, + #[label("invalid {description} literal")] + span: Span, + inner_error: String, + }, } impl std::fmt::Display for ParseError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Parse error: ")?; match self { + Self::EmptyInput => write!(f, "input was empty"), Self::EndOfInput => write!(f, "unexpected end of input"), Self::InvalidToken(_) => write!(f, "invalid token"), Self::Todo(n) => write!(f, "add a nice message here {n}"), - Self::Expected { - expected, - got, - span, + Self::Expected { expected, got, .. } => { + write!(f, "expected '{expected}' but got '{got}'") + } + Self::InvalidLiteral { + description, + token, + inner_error, + .. } => { - write!(f, "expected '{expected}' but got '{got}' at {span:?}") + write!(f, "found an invalid {description} literal '{token}': {inner_error}") } } } @@ -275,6 +290,8 @@ impl<'source> Parser<'source> { let mut scopes = Vec::new(); while !(self.peek_is(Token::Return) + || self.peek_is(Token::Accept) + || self.peek_is(Token::Reject) || self.peek_is(Token::CurlyRight)) { scopes.push(self.apply_scope()?); @@ -298,8 +315,25 @@ impl<'source> Parser<'source> { /// Actions ::= '{' (ValueExpr ';')* ( AcceptReject ';' )? '}' /// ``` fn apply_scope(&mut self) -> ParseResult { + if self.peek_is(Token::Match) { + return self.apply_match(); + } + self.accept_required(Token::Filter)?; - self.accept_required(Token::Match)?; + + // This is not exactly self.match_operator because match ... with is + // not allowed. + let operator = if self.accept_optional(Token::Match)?.is_some() { + MatchOperator::Match + } else if self.accept_optional(Token::ExactlyOne)?.is_some() { + MatchOperator::ExactlyOne + } else if self.accept_optional(Token::Some)?.is_some() { + MatchOperator::Some + } else if self.accept_optional(Token::All)?.is_some() { + MatchOperator::All + } else { + return Err(ParseError::Todo(20)); + }; let filter_ident = self.value_expr()?; let negate = self.accept_optional(Token::Not)?.is_some(); @@ -325,7 +359,7 @@ impl<'source> Parser<'source> { scope: None, match_action: MatchActionExpr::FilterMatchAction( FilterMatchActionExpr { - operator: MatchOperator::Match, + operator, negate, actions, filter_ident, @@ -334,16 +368,87 @@ impl<'source> Parser<'source> { }) } + fn apply_match(&mut self) -> ParseResult { + let operator = self.match_operator()?; + let mut match_arms = Vec::new(); + self.accept_required(Token::CurlyLeft)?; + while self.accept_optional(Token::CurlyRight)?.is_none() { + let variant_id = self.identifier()?; + let data_field = + if self.accept_optional(Token::RoundLeft)?.is_some() { + let id = self.identifier()?; + self.accept_required(Token::RoundRight)?; + Some(id) + } else { + None + }; + + let guard = if self.accept_optional(Token::Pipe)?.is_some() { + let term_id = self.identifier()?; + let args = if self.peek_is(Token::RoundLeft) { + Some(self.arg_expr_list()?) + } else { + None + }; + Some(TermCallExpr { term_id, args }) + } else { + None + }; + + self.accept_required(Token::Arrow)?; + + let mut actions = Vec::new(); + if self.accept_optional(Token::CurlyLeft)?.is_some() { + while self.accept_optional(Token::CurlyRight)?.is_none() { + if let Some(ar) = self.accept_reject()? { + self.accept_required(Token::CurlyRight)?; + actions.push((None, Some(ar))); + break; + } + actions.push((Some(self.action_call_expr()?), None)); + self.accept_required(Token::SemiColon)?; + } + self.accept_optional(Token::Comma)?; + } else { + let expr = self.action_call_expr()?; + self.accept_required(Token::Comma)?; + actions.push((Some(expr), None)); + } + + match_arms.push(PatternMatchActionArm { + variant_id, + data_field, + guard, + actions, + }); + } + Ok(ApplyScope { + scope: None, + match_action: MatchActionExpr::PatternMatchAction( + PatternMatchActionExpr { + operator, + match_arms, + }, + ), + }) + } + + fn action_call_expr(&mut self) -> ParseResult { + let action_id = self.identifier()?; + let args = if self.peek_is(Token::RoundLeft) { + Some(self.arg_expr_list()?) + } else { + None + }; + Ok(ActionCallExpr { action_id, args }) + } + /// Parse a statement returning accept or reject /// /// ```ebnf - /// AcceptReject ::= ('return' ( 'accept' | 'reject' ) ';')? + /// AcceptReject ::= ('return'? ( 'accept' | 'reject' ) ';')? /// ``` fn accept_reject(&mut self) -> ParseResult> { - // Note: this is different from the original parser - // In the original, the return is optional, but all the examples seem to - // require it, so it is now required, - // We could choose to remove it entirely as well. if self.accept_optional(Token::Return)?.is_some() { let value = match self.next()?.0 { Token::Accept => AcceptReject::Accept, @@ -351,10 +456,20 @@ impl<'source> Parser<'source> { _ => return Err(ParseError::Todo(10)), }; self.accept_required(Token::SemiColon)?; - Ok(Some(value)) - } else { - Ok(None) + return Ok(Some(value)); + } + + if self.accept_optional(Token::Accept)?.is_some() { + self.accept_required(Token::SemiColon)?; + return Ok(Some(AcceptReject::Accept)); + } + + if self.accept_optional(Token::Reject)?.is_some() { + self.accept_required(Token::SemiColon)?; + return Ok(Some(AcceptReject::Reject)); } + + Ok(None) } /// Parse a match operator @@ -476,15 +591,18 @@ impl<'source> Parser<'source> { MethodComputeExpr { ident, args }, )) } else { - // TODO: This is technically different from the nom - // parser, because the nom parser will eagerly get - // multiple fields. This is not necessary and the - // Vec in FieldAccessExpr can probably be removed. - access_expr.push(AccessExpr::FieldAccessExpr( - FieldAccessExpr { - field_names: vec![ident], - }, - )) + if let Some(AccessExpr::FieldAccessExpr(FieldAccessExpr { + field_names, + })) = access_expr.last_mut() + { + field_names.push(ident); + } else { + access_expr.push(AccessExpr::FieldAccessExpr( + FieldAccessExpr { + field_names: vec![ident], + }, + )) + } } } @@ -493,13 +611,8 @@ impl<'source> Parser<'source> { /// Parse any literal, including prefixes, ip addresses and communities fn literal(&mut self) -> ParseResult { - // TODO: Implement the following literals: - // - StandardCommunity - // - LargeCommunity - // - ExtendedCommunity - // A prefix length, it requires two tokens - if self.accept_optional(Token::Slash)?.is_some() { + if let Some(Token::PrefixLength(..)) = self.peek() { let PrefixLength(len) = self.prefix_length()?; return Ok(LiteralExpr::PrefixLengthLiteral( PrefixLengthLiteral(len), @@ -510,7 +623,7 @@ impl<'source> Parser<'source> { // slash and is therefore a prefix instead. if matches!(self.peek(), Some(Token::IpV4(_) | Token::IpV6(_))) { let addr = self.ip_address()?; - if self.peek_is(Token::Slash) { + if let Some(Token::PrefixLength(..)) = self.peek() { let len = self.prefix_length()?; return Ok(LiteralExpr::PrefixLiteral(Prefix { addr, len })); } else { @@ -527,7 +640,9 @@ impl<'source> Parser<'source> { let (token, span) = self.next()?; Ok(match token { Token::String(s) => { - LiteralExpr::StringLiteral(StringLiteral(s.into())) + // Trim the quotes from the string literal + let trimmed = &s[1..s.len() - 1]; + LiteralExpr::StringLiteral(StringLiteral(trimmed.into())) } Token::Integer(s) => LiteralExpr::IntegerLiteral(IntegerLiteral( // This parse fails if the literal is too big, @@ -546,11 +661,33 @@ impl<'source> Parser<'source> { } Token::Community(s) => { // We offload the validation of the community to routecore + // but routecore doesn't do all the hex numbers correctly, + // so we transform those first. + // TODO: Change the AST so that it doesn't contain strings, but // routecore communities. - use routecore::bgp::communities::Community; - let c: Community = - s.parse().map_err(|_| ParseError::Todo(12))?; + use routecore::bgp::communities::{self, Community}; + let parts: Vec<_> = s + .split(':') + .map(|p| { + if let Some(hex) = p.strip_prefix("0x") { + u32::from_str_radix(hex, 16).unwrap().to_string() + } else { + p.to_string() + } + }) + .collect(); + + let transformed = parts.join(":"); + + let c: Community = transformed.parse().map_err( + |e: communities::ParseError| ParseError::InvalidLiteral { + description: "community".into(), + token: token.to_string(), + span, + inner_error: e.to_string(), + }, + )?; match c { Community::Standard(x) => { LiteralExpr::StandardCommunityLiteral( @@ -596,25 +733,17 @@ impl<'source> Parser<'source> { Token::CurlyRight, Token::Comma, |parser| { + dbg!(parser.peek()); let key = parser.identifier()?; + dbg!(parser.peek()); parser.accept_required(Token::Colon)?; + dbg!("here?"); let value = parser.value_expr()?; Ok((key, value)) }, ) } - /// Parse a method call: an identifier followed by an argument list - /// - /// ```ebnf - /// MethodCall ::= Identifier ArgExprList - /// ``` - fn method_call(&mut self) -> ParseResult { - let ident = self.identifier()?; - let args = self.arg_expr_list()?; - Ok(MethodComputeExpr { ident, args }) - } - /// Parse a list of arguments to a method /// /// ```ebnf @@ -711,12 +840,16 @@ impl<'source> Parser<'source> { /// PrefixLength ::= '/' Integer /// ``` fn prefix_length(&mut self) -> ParseResult { - self.accept_required(Token::Slash)?; - return match self.next()?.0 { - Token::Integer(s) => Ok(PrefixLength(s.parse().unwrap())), - - _ => Err(ParseError::Todo(14)), + let (token, span) = self.next()?; + let Token::PrefixLength(s) = token else { + return Err(ParseError::InvalidLiteral { + description: "prefix length".into(), + token: token.to_string(), + span, + inner_error: String::new(), + }); }; + Ok(PrefixLength(s[1..].parse().unwrap())) } /// Parse an identifier and a type identifier separated by a colon @@ -738,7 +871,7 @@ impl<'source> Parser<'source> { Ok(match self.next()?.0 { Token::IpV4(s) => IpAddress::Ipv4(Ipv4Addr(s.parse().unwrap())), Token::IpV6(s) => IpAddress::Ipv6(Ipv6Addr(s.parse().unwrap())), - _ => return Err(ParseError::Todo(14)), + _ => return Err(ParseError::Todo(15)), }) } @@ -746,6 +879,13 @@ impl<'source> Parser<'source> { let (token, span) = self.next()?; match token { Token::Ident(s) => Ok(Identifier { ident: s.into() }), + // 'contains' and `type` is already used as both a keyword and an identifier + Token::Contains => Ok(Identifier { + ident: "contains".into(), + }), + Token::Type => Ok(Identifier { + ident: "type".into(), + }), _ => Err(ParseError::Expected { expected: "an identifier".into(), got: token.to_string(), @@ -758,6 +898,13 @@ impl<'source> Parser<'source> { let (token, span) = self.next()?; match token { Token::Ident(s) => Ok(TypeIdentifier { ident: s.into() }), + // 'contains' and `type` already used as both a keyword and an identifier + Token::Contains => Ok(TypeIdentifier { + ident: "contains".into(), + }), + Token::Type => Ok(TypeIdentifier { + ident: "type".into(), + }), _ => Err(ParseError::Expected { expected: "an identifier".into(), got: token.to_string(), diff --git a/src/token.rs b/src/token.rs index 493f81a9..03a10861 100644 --- a/src/token.rs +++ b/src/token.rs @@ -17,6 +17,8 @@ pub enum Token<'s> { BangEq, #[token("&&")] AmpAmp, + #[token("|")] + Pipe, #[token("||")] PipePipe, #[token(">=")] @@ -141,17 +143,21 @@ pub enum Token<'s> { // Integers can contain underscores, but cannot start with them. #[regex(r"[0-9][0-9_]*")] Integer(&'s str), + #[regex(r"0x[0-9A-Fa-f]+")] Hex(&'s str), #[regex(r"AS[0-9]+")] Asn(&'s str), #[regex(r"[0-9]+\.[0-9]*")] Float, + #[regex(r"([0-9]+\.){3}[0-9]+")] IpV4(&'s str), - #[regex(r"([0-9a-zA-Z]+:){6}[0-9a-zA-Z]+")] + #[regex(r"([0-9a-zA-Z]*:){2,6}[0-9a-zA-Z]*")] IpV6(&'s str), + #[regex(r"/[0-9]+")] + PrefixLength(&'s str), // This regex is a super set of all the forms of communities: // standard, large and extended. #[regex(r"([0-9a-zA-Z]+:)?(0x)?[0-9a-fA-F]+:(0x)?[0-9a-fA-F]+")] @@ -165,11 +171,12 @@ pub enum Token<'s> { impl<'source> Display for Token<'source> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let s = match self { - Token::Ident(_) => "identifier", + Token::Ident(s) => s, Token::EqEq => "==", Token::Eq => "=", Token::BangEq => "!=", Token::AmpAmp => "&&", + Token::Pipe => "|", Token::PipePipe => "||", Token::AngleRightEq => ">=", Token::AngleLeftEq => "<=", @@ -225,15 +232,17 @@ impl<'source> Display for Token<'source> { Token::UpTo => "up-to", Token::Use => "use", Token::With => "with", - Token::String(_) => "", - Token::Integer(_) => "", - Token::Hex(_) => "hex literal", - Token::Asn(_) => "ASN", + Token::String(s) => s, + Token::Integer(s) => s, + Token::Hex(s) => s, + Token::Asn(s) => s, Token::Float => "float", - Token::IpV4(_) => "IPv4", - Token::IpV6(_) => "IPv6", - Token::Community(_) => "community", - Token::Bool(_) => "bool", + Token::IpV4(s) => s, + Token::IpV6(s) => s, + Token::Community(s) => s, + Token::Bool(true) => "true", + Token::Bool(false) => "false", + Token::PrefixLength(s) => s, }; write!(f, "{s}") } diff --git a/tests/bgp_filters.rs b/tests/bgp_filters.rs index f5940123..5252f167 100644 --- a/tests/bgp_filters.rs +++ b/tests/bgp_filters.rs @@ -30,7 +30,14 @@ fn test_data( println!("Evaluate filter-map {}...", name); // Compile the source code in this example - let rotolo = Compiler::build(source_code)?; + let compile_res = Compiler::build(source_code); + + // Print a pretty error message for easier debugging + if let Err(e) = &compile_res { + eprintln!("{e}"); + } + + let rotolo = compile_res?; let roto_pack = rotolo.retrieve_pack_as_arcs(&name)?; // Create a BGP packet @@ -192,7 +199,7 @@ fn test_ip_address_literal_3() { assert!(res.is_err()); trace!("res {:?}", res); - assert!(format!("{}", res.err().unwrap()).starts_with(r#"Parse error"#)); + assert!(format!("{}", res.unwrap_err()).contains("Parse error")); } #[test] @@ -772,8 +779,7 @@ fn test_as_path_1() { } "#, annc, - ) - .unwrap(); + ).unwrap(); assert_eq!(res.accept_reject, AcceptReject::Reject); } @@ -1283,7 +1289,7 @@ fn test_ext_comms_2() { } #[test] -#[should_panic = r#"Result::unwrap()` on an `Err` value: "Eval error: Cannot convert literal 'XOTOR:4500:34' into Extended Community: unknown tag"#] +#[should_panic] fn test_ext_comms_3() { common::init(); @@ -1318,7 +1324,7 @@ fn test_ext_comms_3() { } #[test] -#[should_panic = r#"Result::unwrap()` on an `Err` value: "Eval error: Cannot convert literal 'rt:450076876500:34' into Extended Community: invalid rt:AS:AN"#] +#[should_panic] fn test_ext_comms_4() { common::init(); diff --git a/tests/bmp_message.rs b/tests/bmp_message.rs index 19cc2634..7384a696 100644 --- a/tests/bmp_message.rs +++ b/tests/bmp_message.rs @@ -24,7 +24,13 @@ fn test_data( println!("Evaluate filter-map {}...", name); // Compile the source code in this example - let rotolo = Compiler::build(source_code)?; + let compile_res = Compiler::build(source_code); + + if let Err(e) = &compile_res { + eprintln!("{e}") + } + + let rotolo = compile_res?; let roto_pack = rotolo.retrieve_pack_as_arcs(&name)?; let rm_msg = BytesRecord::::new(buf.clone().into()); @@ -83,7 +89,13 @@ fn test_data_2( println!("Evaluate filter-map {}...", name); // Compile the source code in this example - let rotolo = Compiler::build(source_code)?; + let compile_res = Compiler::build(source_code); + + if let Err(e) = &compile_res { + eprintln!("{e}") + } + + let rotolo = compile_res?; let roto_pack = rotolo.retrieve_pack_as_arcs(&name)?; let buf = vec![ @@ -150,7 +162,13 @@ fn test_data_3( println!("Evaluate filter-map {}...", name); // Compile the source code in this example - let rotolo = Compiler::build(source_code)?; + let compile_res = Compiler::build(source_code); + + if let Err(e) = &compile_res { + eprintln!("{e}") + } + + let rotolo = compile_res?; let roto_pack = rotolo.retrieve_pack_as_arcs(&name)?; // BMP PeerDownNotification type 3, containing a BGP NOTIFICATION. @@ -215,7 +233,13 @@ fn test_data_4( println!("Evaluate filter-map {}...", name); // Compile the source code in this example - let rotolo = Compiler::build(source_code)?; + let compile_res = Compiler::build(source_code); + + if let Err(e) = &compile_res { + eprintln!("{e}") + } + + let rotolo = compile_res?; let roto_pack = rotolo.retrieve_pack_as_arcs(&name)?; trace!("Used Arguments"); @@ -272,7 +296,13 @@ fn compile_initiation_payload( println!("Evaluate filter-map {}...", name); // Compile the source code in this example - let rotolo = Compiler::build(source_code)?; + let compile_res = Compiler::build(source_code); + + if let Err(e) = &compile_res { + eprintln!("{e}") + } + + let rotolo = compile_res?; let roto_pack = rotolo.retrieve_pack_as_arcs(&name)?; // assert!(i_msg.is_ok()); diff --git a/tests/compare.rs b/tests/compare.rs index cfd45156..7ad8034d 100644 --- a/tests/compare.rs +++ b/tests/compare.rs @@ -1,5 +1,5 @@ use roto::ast::AcceptReject; -use roto::compiler::Compiler; +use roto::compiler::{compile, Compiler}; use roto::blocks::Scope::{self, FilterMap}; use roto::types::collections::Record; @@ -86,7 +86,13 @@ fn test_data( println!("Evaluate filter-map {}...", name); let c = Compiler::new(); - let roto_packs = c.build_from_compiler(source_code)?; + let compile_res = c.build_from_compiler(source_code); + + if let Err(e) = &compile_res { + eprintln!("{e}"); + } + + let roto_packs = compile_res?; let roto_pack = roto_packs.retrieve_pack_as_refs(&name)?; let asn: TypeValue = Asn::from_u32(211321).into(); diff --git a/tests/data_sources.rs b/tests/data_sources.rs index e02d2132..94e62f87 100644 --- a/tests/data_sources.rs +++ b/tests/data_sources.rs @@ -1,5 +1,5 @@ use log::trace; -use roto::compiler::Compiler; +use roto::compiler::{compile, Compiler}; use roto::blocks::Scope; use roto::types::collections::{ElementTypeValue, List, Record}; @@ -25,7 +25,14 @@ fn test_data( let mut c = Compiler::new(); c.with_arguments(&name, filter_map_arguments)?; - let roto_packs = c.build_from_compiler(source_code)?; + + let compile_res = c.build_from_compiler(source_code); + + if let Err(e) = &compile_res { + eprintln!("{e}"); + } + + let roto_packs = compile_res?; let mut roto_pack = roto_packs.retrieve_pack_as_refs(&name)?; let _count: TypeValue = 1_u32.into(); @@ -144,7 +151,7 @@ fn test_filter_map_1() { tx ext_route: Route; // specify additional external data sets that will be consulted. - use table source_asns; + // use table source_asns; // assignments extra_in_table = source_asns.contains(extra_asn); // 0 @@ -224,15 +231,15 @@ fn test_filter_map_1() { } apply { - use best-path; + // use best-path; filter exactly-one rov-valid matching { set-best; set-rov-invalid-asn-community; return accept; }; - use backup-path; + // use backup-path; filter match on-my-terms matching { set-best; return accept; }; - use backup-path; + // use backup-path; filter match on-my-terms not matching { set-rov-invalid-asn-community; return reject; diff --git a/tests/end_to_end.rs b/tests/end_to_end.rs index c45dee4b..1521ef8c 100644 --- a/tests/end_to_end.rs +++ b/tests/end_to_end.rs @@ -1,7 +1,7 @@ use log::trace; use roto::ast::AcceptReject; -use roto::compiler::Compiler; +use roto::compiler::{compile, Compiler}; use roto::blocks::Scope::{self, Filter, FilterMap}; use roto::types::collections::{ElementTypeValue, List, Record}; use roto::types::datasources::{DataSource, Rib}; @@ -60,7 +60,13 @@ fn test_data( let mut c = Compiler::new(); c.with_arguments(&name, filter_args)?; - let roto_packs = c.build_from_compiler(source_code)?; + let compiler_res = c.build_from_compiler(source_code); + + if let Err(e) = &compiler_res { + eprintln!("{e}"); + } + + let roto_packs = compiler_res?; let mut roto_pack = roto_packs.retrieve_pack_as_refs(&name)?; let _count: TypeValue = 1_u32.into(); @@ -232,8 +238,8 @@ fn test_filter_map_1() { tx ext_route: ExtRoute; // specify additional external data sets that will be consulted. - use table source_asns; - use rib rib-rov; + // use table source_asns; + // use rib rib-rov; // assignments extra_in_table = source_asns.contains(extra_asn); // 0 @@ -326,15 +332,15 @@ fn test_filter_map_1() { } apply { - use best-path; + // use best-path; filter exactly-one rov-valid matching { set-best; set-rov-invalid-asn-community; return accept; }; - use backup-path; + // use backup-path; filter match on-my-terms matching { set-best; return accept; }; - use backup-path; + // use backup-path; filter match on-my-terms not matching { set-rov-invalid-asn-community; return reject; diff --git a/tests/filter_maps.rs b/tests/filter_maps.rs index a4aaec03..68fda662 100644 --- a/tests/filter_maps.rs +++ b/tests/filter_maps.rs @@ -217,7 +217,7 @@ fn test_filter_map_31() { } apply { - use my-filter-map; + // use my-filter-map; filter match peer-asn-matches matching { set-asn; return accept; }; return reject; } @@ -260,7 +260,7 @@ fn test_filter_map_32() { } apply { - use my-filter-map; + // use my-filter-map; filter match peer-asn-matches matching { set-asn; return accept; }; return reject; } @@ -302,7 +302,7 @@ fn test_filter_map_33() { } apply { - use my-filter-map; + // use my-filter-map; filter match peer-asn-matches matching { set-asn; return accept; }; return reject; } @@ -345,7 +345,7 @@ fn test_filter_map_34() { } apply { - use my-filter-map; + // use my-filter-map; filter match peer-asn-matches matching { set-asn; return accept; }; return reject; } diff --git a/tests/helpers.rs b/tests/helpers.rs index c72b8a9d..ef36ac4d 100644 --- a/tests/helpers.rs +++ b/tests/helpers.rs @@ -27,7 +27,7 @@ impl<'a> TestCompiler<'a> { trace!("{} {:#?}", self.name, self.compiler.ast); if let Err(e) = &parse_res { - trace!("{:?}", e); + eprintln!("{:?}", e); } match expect_success { diff --git a/tests/lists.rs b/tests/lists.rs index 712024fe..63da062f 100644 --- a/tests/lists.rs +++ b/tests/lists.rs @@ -91,8 +91,13 @@ fn test_data( trace!("Evaluate filter-map {}...", name); let c = Compiler::new(); - let roto_packs = c.build_from_compiler(source_code)?; + let compiler_res = c.build_from_compiler(source_code); + if let Err(e) = &compiler_res { + eprintln!("{e}"); + } + + let roto_packs = compiler_res?; let roto_pack = roto_packs.retrieve_pack_as_refs(&name)?; let asn: TypeValue = Asn::from_u32(211321).into(); diff --git a/tests/records.rs b/tests/records.rs index 04b33ead..88b91fe9 100644 --- a/tests/records.rs +++ b/tests/records.rs @@ -111,8 +111,13 @@ fn test_data( trace!("Evaluate filter-map {}...", name); let c = Compiler::new(); - let roto_packs = c.build_from_compiler(source_code)?; + let compiler_res = c.build_from_compiler(source_code); + if let Err(e) = &compiler_res { + eprintln!("{e}"); + } + + let roto_packs = compiler_res?; let roto_pack = roto_packs.retrieve_pack_as_refs(&name)?; let asn: TypeValue = Asn::from_u32(211321).into(); @@ -337,7 +342,7 @@ fn test_records_compare_11() { fn test_records_compare_12() { common::init(); let src_line = src_code( - "D { asn: AS99, i: { f: 86400, g: AS24, h: { l:2, k: 100 } }, d: 400 }", + "D { asn: AS99, i: { f: 86400, g: AS24, h: { l: 2, k: 100 } }, d: 400 }", "100 in [a.i.h.k,2,3,4,5]; // Peer Down", "reject", ); From 02fd2251993a5e1dbcf6c696b1dde45b8797c49c Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Thu, 29 Feb 2024 10:24:35 +0100 Subject: [PATCH 11/24] remove old parser and port the last tests --- src/ast.rs | 2631 +---------------- src/lib.rs | 1 - src/parse_string.rs | 163 - src/parser/filter_map.rs | 4 +- src/parser/mod.rs | 24 +- src/parser/test_expressions.rs | 118 + .../parser/test_sections.rs | 18 +- src/types/builtin/tests.rs | 35 +- tests/compare.rs | 2 +- tests/data_sources.rs | 2 +- tests/end_to_end.rs | 2 +- tests/expressions.rs | 184 -- tests/helpers.rs | 1 - 13 files changed, 297 insertions(+), 2888 deletions(-) delete mode 100644 src/parse_string.rs create mode 100644 src/parser/test_expressions.rs rename tests/sections.rs => src/parser/test_sections.rs (52%) delete mode 100644 tests/expressions.rs diff --git a/src/ast.rs b/src/ast.rs index 776e47a6..2a9765d0 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -1,47 +1,18 @@ use std::{borrow, cmp, fmt, hash, ops, str}; -use log::trace; -use nom::branch::{alt, permutation}; -use nom::bytes::complete::{ - take, take_while, take_while1, take_while_m_n, -}; -use nom::character::complete::{ - char, digit1, hex_digit0, multispace0, multispace1, -}; -use nom::combinator::{all_consuming, cut, not, opt, recognize}; -use nom::error::{ - context, ErrorKind, FromExternalError, ParseError as _, VerboseError, -}; -use nom::multi::{ - fold_many0, many0, many1, separated_list0, separated_list1, -}; -use nom::sequence::{ - delimited, pair, preceded, separated_pair, terminated, tuple, -}; -use nom::{ - bytes::complete::{tag, take_until}, - character::complete::char as tag_char, - combinator::map, - IResult, -}; -use nom::AsChar; use serde::{Serialize, Serializer}; use smallvec::SmallVec; -use routecore::bgp::communities::HumanReadableCommunity as Community; -use routecore::bgp::communities::{ExtendedCommunity, LargeCommunity, StandardCommunity}; use routecore::asn::Asn; +use routecore::bgp::communities::HumanReadableCommunity as Community; +use routecore::bgp::communities::{ + ExtendedCommunity, LargeCommunity, StandardCommunity, +}; use crate::compiler::error::CompileError; -use crate::parser::{Parser, ParseError}; +use crate::first_into_compile_err; +use crate::parser::{ParseError, Parser}; use crate::types::typevalue::TypeValue; -use crate::{first_into_compile_err, parse_string}; - -/// ======== Root =========================================================== - -/// The Root of the file. -/// -// Root ::= RootExpr+ #[derive(Clone, Debug, Default)] pub struct SyntaxTree { @@ -49,36 +20,15 @@ pub struct SyntaxTree { } impl SyntaxTree { - pub fn parse_str( - input: &str, - ) -> Result { + pub fn parse_str(input: &str) -> Result { let tree = Parser::parse(input)?; if tree.expressions.is_empty() { return Err(ParseError::EmptyInput); } Ok(tree) } - - pub fn parse_root(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, expressions) = all_consuming(many1(preceded( - skip_opt_ws, - terminated(RootExpr::parse, skip_opt_ws), - )))(input)?; - Ok((input, Self { expressions })) - } } -//------------ RootExpr ----------------------------------------------------- - -// RootExpr ::= FilterMap | -// "filter-map" Identifier ForStatement WithStatement '{' FilterMap '}' | -// "filter" Identifier ForStatement WithStatement '{' Filter '}' | -// "rib" Identifier 'contains' TypeIdentifier '{' RibBody '}' | -// "table" Identifier '{' TableBody '}' | -// "output-stream" Identifier '{' StreamBody '}' | -// RecordTypeAssignment | -// Comment - #[derive(Debug, Clone)] pub enum RootExpr { FilterMap(Box), @@ -90,20 +40,6 @@ pub enum RootExpr { } impl RootExpr { - pub fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, expressions) = context( - "root", - alt(( - map(Rib::parse, Self::Rib), - map(Table::parse, Self::Table), - map(OutputStream::parse, Self::OutputStream), - map(FilterMap::parse, |m| Self::FilterMap(Box::new(m))), - map(RecordTypeAssignment::parse, Self::Ty), - )), - )(input)?; - Ok((input, expressions)) - } - pub fn get_filter_map(&self) -> Result<&FilterMap, CompileError> { match self { Self::FilterMap(m) => Ok(m), @@ -112,168 +48,35 @@ impl RootExpr { } } -//------------ ListValueExpr ------------------------------------------------ - -// ListValueExpr ::= '[' ValueExpr+ ']' - -// A list of values of the same type or a list where all the values can be -// converted to the same type - +/// A list of values of the same type or a list where all the values can be +/// converted to the same type #[derive(Clone, Debug)] pub struct ListValueExpr { pub values: Vec, } -impl ListValueExpr { - fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, values) = context( - "list value", - delimited( - opt_ws(char('[')), - context( - "List Value", - separated_list1(char(','), opt_ws(ValueExpr::parse)), - ), - opt_ws(char(']')), - ), - )(input)?; - - Ok((input, ListValueExpr { values })) - } -} - -//------------ AnonymousRecordValueExpr ------------------------------------- - -// RecordValueExpr ::= '{' (Identifier ':' ValueExpr, )+ '}' - -// The value of a (anonymous) record -// Defined and directly used, mainly as an argument to a method, where the -// actual type can be inferred unambiguously. - +/// The value of a (anonymous) record +/// Defined and directly used, mainly as an argument to a method, where the +/// actual type can be inferred unambiguously. #[derive(Clone, Debug)] pub struct AnonymousRecordValueExpr { pub key_values: Vec<(Identifier, ValueExpr)>, } -impl AnonymousRecordValueExpr { - fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, key_values) = context( - "record value", - delimited( - opt_ws(char('{')), - context( - "Record Value", - separated_list1( - char(','), - opt_ws(tuple(( - terminated( - opt_ws(Identifier::parse), - opt_ws(char(':')), - ), - opt_ws(ValueExpr::parse), - ))), - ), - ), - opt_ws(char('}')), - ), - )(input)?; - - Ok((input, AnonymousRecordValueExpr { key_values })) - } -} - -//------------ TypedRecordValueExpr ----------------------------------------- - -// RecordValueExpr ::= Identifier '{' (Identifier ':' ValueExpr, )+ '}' - -// Used in the 'Define' section to create variables to hold a record. - +/// Used in the 'Define' section to create variables to hold a record. #[derive(Clone, Debug)] pub struct TypedRecordValueExpr { pub type_id: TypeIdentifier, pub key_values: Vec<(Identifier, ValueExpr)>, } -impl TypedRecordValueExpr { - fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, (type_id, key_values)) = context( - "typed record value", - tuple(( - opt_ws(TypeIdentifier::parse), - delimited( - opt_ws(char('{')), - context( - "Record Value", - separated_list1( - char(','), - opt_ws(tuple(( - terminated( - opt_ws(Identifier::parse), - opt_ws(char(':')), - ), - opt_ws(ValueExpr::parse), - ))), - ), - ), - opt_ws(char('}')), - ), - )), - )(input)?; - - Ok(( - input, - TypedRecordValueExpr { - type_id, - key_values, - }, - )) - } -} - -// The value of a typed record - -//------------ RecordTypeAssignment ----------------------------------------- - -// RecordTypeAssignment ::= "type" Identifier '{' RecordTypeIdentifier '}' - +/// The value of a typed record #[derive(Clone, Debug)] pub struct RecordTypeAssignment { pub ident: TypeIdentifier, pub record_type: RecordTypeIdentifier, } -impl RecordTypeAssignment { - fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, (ident, record_type)) = context( - "record type definition", - tuple(( - context( - "record type name", - preceded( - opt_ws(tag("type")), - opt_ws(TypeIdentifier::parse), - ), - ), - context( - "type definition", - delimited( - opt_ws(char('{')), - cut(RecordTypeIdentifier::parse), - opt_ws(char('}')), - ), - ), - )), - )(input)?; - - Ok((input, RecordTypeAssignment { ident, record_type })) - } -} - -//------------ FilterMap ------------------------------------------------------- - -// FilterMap ::= "filter-map" Identifier "for" Identifier WithStatement -// WithStatement '{' FilterMapBody '}' | -// "filter" Identifier "for" Identifier WithStatement '{' FilterBody '}' #[derive(Clone, Copy, Debug)] pub enum FilterType { FilterMap, @@ -298,55 +101,6 @@ pub struct FilterMap { pub body: FilterMapBody, } -impl FilterMap { - pub fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, (ty, ident, for_ident, with_kv, body)) = context( - "filter-map definition", - tuple(( - alt(( - context( - "filter-map type", - map(opt_ws(tag("filter-map")), |_| { - FilterType::FilterMap - }), - ), - context( - "filter-map type", - map(opt_ws(tag("filter")), |_| FilterType::Filter), - ), - )), - opt_ws(Identifier::parse), - for_statement, - with_statement, - context( - "filter-map body", - delimited( - opt_ws(char('{')), - cut(FilterMapBody::parse), - opt_ws(char('}')), - ), - ), - // map(many0(char('\n')), |_| ()), - )), - )(input)?; - - Ok(( - input, - FilterMap { - ty, - ident, - body, - for_ident, - with_kv: with_kv.unwrap_or_default(), - }, - )) - } -} - -//------------ FilterMapBody --------------------------------------------------- - -// FilterMapBody ::= Define FilterMapExpr+ Apply - #[derive(Clone, Debug)] pub struct FilterMapBody { pub define: Define, @@ -354,26 +108,7 @@ pub struct FilterMapBody { pub apply: Option, } -impl FilterMapBody { - pub fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, (define, expressions, apply)) = permutation(( - Define::parse, - context("filter-map expressions", many0(FilterMapExpr::parse)), - opt(ApplySection::parse), - ))(input)?; - - Ok(( - input, - Self { - define, - expressions, - apply, - }, - )) - } -} - -// These are the sections that can appear multiple times in a Filter(Map) +/// These are the sections that can appear multiple times in a Filter(Map) #[derive(Debug, Clone)] pub enum FilterMapExpr { Term(TermSection), @@ -381,28 +116,6 @@ pub enum FilterMapExpr { // Empty, // Import(ImportBody), } -//------------ FilterMapExpr ---------------------------------------------------- -// -// FilterMapExpr ::= Define | -// Term+ | -// Action+ | -// 'import' ForStatement '{' ImportBody '}' - -impl FilterMapExpr { - pub fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, expressions) = context( - "filter-map expression", - alt(( - map(TermSection::parse, Self::Term), - map(ActionSection::parse, Self::Action), - // map(multispace1, |_| Self::Empty), - // map(ImportBody::parse, FilterMapBody::Import), - )), - )(input)?; - Ok((input, expressions)) - } -} - //------------ Define ------------------------------------------------------- #[derive(Clone, Debug)] pub struct Define { @@ -411,38 +124,6 @@ pub struct Define { pub body: DefineBody, } -impl Define { - fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, (for_kv, with_kv, body)) = context( - "define definition", - preceded( - opt_ws(tag("define")), - tuple(( - for_statement, - with_statement, - context( - "define block", - delimited( - opt_ws(char('{')), - DefineBody::parse, - opt_ws(char('}')), - ), - ), - )), - ), - )(input)?; - - Ok(( - input, - Self { - for_kv, - with_kv: with_kv.unwrap_or_default(), - body, - }, - )) - } -} - #[derive(Clone, Debug)] pub enum RxTxType { RxOnly(TypeIdentField), @@ -450,13 +131,6 @@ pub enum RxTxType { PassThrough(TypeIdentField), } -//------------ DefineBody --------------------------------------------------- - -// DefineBody ::= (( 'use' Identifier ';' )? (('rx' Identifier ':' -// TypeIdentifier ';') ('tx' Identifier ':' TypeIdentifier ';')) | ( -// 'rx_tx' Identifier ':' TypeIdentifier ';' ))? ( Identifier '=' -// ComputeExpr ';' )+ )+ - #[derive(Clone, Debug)] pub struct DefineBody { pub rx_tx_type: RxTxType, @@ -464,74 +138,6 @@ pub struct DefineBody { pub assignments: Vec<(Identifier, ValueExpr)>, } -impl DefineBody { - fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, (rx_tx_type, use_ext_data, assignments)) = tuple(( - alt(( - map( - delimited( - opt_ws(tag("rx_tx")), - opt_ws(TypeIdentField::parse), - opt_ws(char(';')), - ), - RxTxType::PassThrough, - ), - map( - permutation(( - delimited( - opt_ws(tag("rx")), - opt_ws(TypeIdentField::parse), - opt_ws(char(';')), - ), - delimited( - opt_ws(tag("tx")), - opt_ws(TypeIdentField::parse), - opt_ws(char(';')), - ), - )), - |t| RxTxType::Split(t.0, t.1), - ), - map( - delimited( - opt_ws(tag("rx")), - opt_ws(TypeIdentField::parse), - opt_ws(char(';')), - ), - RxTxType::RxOnly, - ), - )), - many0(delimited( - opt_ws(tag("use")), - tuple((opt_ws(Identifier::parse), opt_ws(Identifier::parse))), - opt_ws(char(';')), - )), - many0(context( - "assignments", - separated_pair( - opt_ws(Identifier::parse), - preceded(multispace0, char('=')), - terminated(opt_ws(ValueExpr::parse), opt_ws(char(';'))), - ), - )), - ))( - input - )?; - - // let (use_ext_data, assignments) = statements.iter().cloned().unzip(); - - Ok(( - input, - Self { - rx_tx_type, - use_ext_data, - assignments, - }, - )) - } -} - -//------------ Term --------------------------------------------------------- - #[derive(Clone, Debug)] pub struct TermSection { pub ident: Identifier, @@ -540,72 +146,12 @@ pub struct TermSection { pub body: TermBody, } -impl TermSection { - pub fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, (ident, for_kv, with_kv, body)) = context( - "term definition", - tuple(( - preceded( - opt_ws(tag("term")), - cut(context( - "term name", - delimited( - multispace1, - Identifier::parse, - multispace1, - ), - )), - ), - for_statement, - with_statement, - context( - "term block", - cut(delimited( - opt_ws(char('{')), - TermBody::parse, - opt_ws(char('}')), - )), - ), - )), - )(input)?; - - Ok(( - input, - TermSection { - ident, - for_kv, - with_kv: with_kv.unwrap_or_default(), - body, - }, - )) - } -} - -//------------ TermBody ----------------------------------------------------- - -// TermBody ::= TermScope+ - #[derive(Clone, Debug)] pub struct TermBody { pub scopes: Vec, } -impl TermBody { - pub fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, scopes) = - context("term body", many0(TermScope::parse))(input)?; - Ok((input, Self { scopes })) - } -} - -//------------ TermScope ---------------------------------------------------- - -// Everything that can appear inside a named `term` block. - -// TermScope ::= -// ('use' Identifier ';')? -// ( MatchOperator '{' ( ( LogicalExpr ';' ) | VariantMatchExpr ) )+ '}' - +/// Everything that can appear inside a named `term` block. #[derive(Clone, Debug)] pub struct TermScope { pub scope: Option, @@ -613,72 +159,12 @@ pub struct TermScope { pub match_arms: Vec<(Option, Vec)>, } -impl TermScope { - pub fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, (scope, (operator, match_exprs))) = tuple(( - opt(opt_ws(context( - "use scope", - preceded( - opt_ws(tag("use")), - delimited( - multispace1, - Identifier::parse, - opt_ws(char(';')), - ), - ), - ))), - context( - "match expressions", - tuple(( - opt_ws(MatchOperator::parse), - delimited( - opt_ws(char('{')), - many0(context( - "match expression", - alt(( - map(TermMatchExpr::parse, |m_e| { - ( - Some(TermPatternMatchArm { - variant_id: m_e.variant_id, - data_field: m_e.data_field, - }), - m_e.logical_expr, - ) - }), - map( - terminated( - LogicalExpr::parse, - opt_ws(char(';')), - ), - |l_e| (None, vec![l_e]), - ), - )), - )), - opt_ws(char('}')), - ), - )), - ), - ))(input)?; - Ok(( - input, - Self { - scope, - operator, - match_arms: match_exprs, - }, - )) - } -} - -//------------ TermPatternMatchArm ------------------------------------------ - -// A Match arm, with or without a data field. Used to capture a MatchExpr and -// separate it from its logical expression(s), so that the TermScope (down -// below) can capture it as an optional expression for that TermScope. so the -// difference between a Match Expression that represents a match arm (of an -// Enum) and a 'regular' collection of logical expressions, is just this -// optional VariantMatchExpr. - +/// A Match arm, with or without a data field. Used to capture a MatchExpr and +/// separate it from its logical expression(s), so that the TermScope (down +/// below) can capture it as an optional expression for that TermScope. so the +/// difference between a Match Expression that represents a match arm (of an +/// Enum) and a 'regular' collection of logical expressions, is just this +/// optional VariantMatchExpr. #[derive(Clone, Debug)] pub struct TermPatternMatchArm { pub variant_id: Identifier, @@ -698,130 +184,24 @@ pub struct ActionSection { pub body: ActionSectionBody, } -impl ActionSection { - pub fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, (ident, with_kv, body)) = context( - "action definition", - tuple(( - preceded( - opt_ws(tag("action")), - cut(context( - "action name", - delimited( - multispace1, - Identifier::parse, - multispace1, - ), - )), - ), - with_statement, - context( - "action block", - delimited( - opt_ws(char('{')), - ActionSectionBody::parse, - opt_ws(char('}')), - ), - ), - )), - )(input)?; - - Ok(( - input, - ActionSection { - ident, - // for_kv, - with_kv: with_kv.unwrap_or_default(), - body, - }, - )) - } -} - -//------------ ActionBody ----------------------------------------------------- - -// ActionBody ::= (ActionExpr ';')+ - #[derive(Clone, Debug)] pub struct ActionSectionBody { pub expressions: Vec, } -impl ActionSectionBody { - pub fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, expressions) = context( - "action body", - many1(opt_ws(terminated( - OptionalGlobalComputeExpr::parse, - opt_ws(char(';')), - ))), - )(input)?; - Ok((input, Self { expressions })) - } -} - -//------------ OptionalGlobalComputeExpr ------------------------------------ - -// ActionExpr ::= ComputeExpr | GlobalMethodExpr - -// An Optional Global Compute Expressions can be either an ordinary Compute -// Expression, or a global method call, i.e. 'some-global-method(a, b)', so an -// expression without any dots in it ending in a method call. - -// Action Expressions are always turned into regular Compute Expressions at -// parse time (so: here), consequently there's no `eval()` for an Optional -// GlobalComputeExpr. - +/// An Optional Global Compute Expressions can be either an ordinary Compute +/// Expression, or a global method call, i.e. 'some-global-method(a, b)', so an +/// expression without any dots in it ending in a method call. +/// +/// Action Expressions are always turned into regular Compute Expressions at +/// parse time (so: here), consequently there's no `eval()` for an Optional +/// GlobalComputeExpr. #[derive(Clone, Debug)] pub enum OptionalGlobalComputeExpr { GlobalMethodExpr(MethodComputeExpr), ComputeExpr(ComputeExpr), } -impl OptionalGlobalComputeExpr { - pub fn parse( - input: &str, - ) -> IResult<&str, ComputeExpr, VerboseError<&str>> { - map( - context( - "action expression", - alt(( - map( - MethodComputeExpr::parse, - OptionalGlobalComputeExpr::GlobalMethodExpr, - ), - map( - ComputeExpr::parse, - OptionalGlobalComputeExpr::ComputeExpr, - ), - )), - ), - |expr| expr.into_compute_expr(), - )(input) - } - - pub(crate) fn into_compute_expr(self) -> ComputeExpr { - match self { - Self::ComputeExpr(compute_expr) => compute_expr, - Self::GlobalMethodExpr(ref access_expr) => ComputeExpr { - access_expr: vec![AccessExpr::MethodComputeExpr( - access_expr.clone(), - )], - receiver: AccessReceiver::GlobalScope, - }, - } - } -} - -//------------ ImportBody --------------------------------------------------- - -// #[derive(Clone, Debug)] -// pub struct ImportBody {} - -//------------ ApplySection ------------------------------------------------- - -// ApplySection ::= 'apply' ForStatement WithStatement '{' ApplyBody '}' - #[derive(Clone, Debug)] pub struct ApplySection { pub body: ApplyBody, @@ -829,192 +209,30 @@ pub struct ApplySection { pub with_kv: Vec, } -impl ApplySection { - pub fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, (for_kv, with_kv, body)) = context( - "apply definition", - preceded( - opt_ws(tag("apply")), - tuple(( - for_statement, - with_statement, - context( - "apply block", - delimited( - opt_ws(char('{')), - ApplyBody::parse, - opt_ws(char('}')), - ), - ), - )), - ), - )(input)?; - - Ok(( - input, - Self { - for_kv, - with_kv: with_kv.unwrap_or_default(), - body, - }, - )) - } -} - -//------------ ApplyBody ----------------------------------------------------- - -// ApplyBody ::= ApplyScope+ (AcceptReject ';')? - #[derive(Clone, Debug)] pub struct ApplyBody { pub scopes: Vec, pub accept_reject: Option, } -impl ApplyBody { - fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, (scopes, accept_reject)) = context( - "apply body", - tuple(( - many0(ApplyScope::parse), - context("final accept reject", opt(opt_ws(accept_reject))), - )), - )(input)?; - Ok(( - input, - Self { - scopes, - accept_reject, - }, - )) - } -} - -//------------ ApplyScope ----------------------------------------------------- - -// ApplyScope ::= ( 'use' Identifier ';' )? 'filter' MatchOperator ( -// ComputeExpr | Identifier ) 'not'? 'matching' '{' ( ( ComputeExpr | -// Identifier ) ';' ( AcceptReject ';' )? )+ '}' ';' - #[derive(Clone, Debug)] pub struct ApplyScope { pub scope: Option, pub match_action: MatchActionExpr, } -impl ApplyScope { - fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, (scope, apply_scope)) = tuple(( - opt_ws(opt(context( - "use scope", - preceded( - opt_ws(tag("use")), - delimited( - multispace1, - Identifier::parse, - opt_ws(char(';')), - ), - ), - ))), - alt(( - map( - tuple(( - preceded( - opt_ws(tag("filter")), - opt_ws(MatchOperator::parse), - ), - context( - "action expressions", - opt_ws(tuple(( - ValueExpr::parse, - opt(terminated( - opt(opt_ws(tag("not"))), - opt_ws(tag("matching")), - )), - delimited( - opt_ws(char('{')), - alt(( - many1(context( - "Call Expression", - tuple(( - map( - opt_ws(terminated( - ValueExpr::parse, - opt_ws(char(';')), - )), - Some, - ), - opt(opt_ws(accept_reject)), - )), - )), - map(opt_ws(accept_reject), |ar| { - vec![(None, Some(ar))] - }), - )), - terminated( - opt_ws(char('}')), - opt_ws(char(';')), - ), - ), - ))), - ), - )), - |expr| { - ( - input, - MatchActionExpr::FilterMatchAction( - FilterMatchActionExpr { - operator: expr.0, - negate: if let Some(negate) = expr.1 .1 { - negate.is_some() - } else { - false - }, - actions: expr.1 .2, - filter_ident: expr.1 .0, - }, - ), - ) - }, - ), - map( - context( - "pattern match expression", - opt_ws(PatternMatchActionExpr::parse), - ), - |expr| (input, MatchActionExpr::PatternMatchAction(expr)), - ), - )), - ))(input)?; - Ok(( - input, - Self { - scope, - match_action: apply_scope.1, - }, - )) - } -} - -// the Apply section can host regular rules that bind a term to an action -// under specified conditions. It can also host a match expression that -// does the same for enums. - -// MatchAction ::= FilterMatch | PatternMatch +/// the Apply section can host regular rules that bind a term to an action +/// under specified conditions. It can also host a match expression that +/// does the same for enums. #[derive(Clone, Debug)] pub enum MatchActionExpr { FilterMatchAction(FilterMatchActionExpr), PatternMatchAction(PatternMatchActionExpr), } -// A regular 'filter match` expression that binds a term to a (number of) -// action(s). - -// FilterMatchAction ::= MatchOperator Value 'not'? 'matching' '{' -// ActionCallExpr+ ';' ( AcceptReject ';' )? '}' ';'? - +/// A regular 'filter match` expression that binds a term to a (number of) +/// action(s). #[derive(Clone, Debug)] - pub struct FilterMatchActionExpr { pub operator: MatchOperator, pub filter_ident: ValueExpr, @@ -1022,105 +240,40 @@ pub struct FilterMatchActionExpr { pub actions: Vec<(Option, Option)>, } -// A complete pattern match on a variable where every match arm can have -// multiple actions. Similar to TermMatchActionExpr, but a PatternMatchAction -// can only take actions in its body, no Logic Expressions. - -// PatternMatchAction ::= MatchOperator '{' PatternMatchActionArm+ '},' - +/// A complete pattern match on a variable where every match arm can have +/// multiple actions. Similar to TermMatchActionExpr, but a PatternMatchAction +/// can only take actions in its body, no Logic Expressions. #[derive(Clone, Debug)] pub struct PatternMatchActionExpr { - // The data field of the MatchOperator is the identifier of the variable - // to be matched on + /// The data field of the MatchOperator is the identifier of the variable + /// to be matched on pub operator: MatchOperator, - // All the match arms appearing in the source code, with an optional - // guard, i.e. a condition on this variant. + /// All the match arms appearing in the source code, with an optional + /// guard, i.e. a condition on this variant. pub match_arms: Vec, } -impl PatternMatchActionExpr { - pub fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, (variable_id, match_arms)) = context( - "pattern match expression", - context( - "match expressions", - tuple(( - opt_ws(MatchOperator::parse), - delimited( - opt_ws(char('{')), - many1(context( - "match expression", - opt_ws(PatternMatchActionArm::parse), - )), - opt_ws(char('}')), - ), - )), - ), - )(input)?; - Ok(( - input, - Self { - operator: variable_id, - match_arms, - }, - )) - } -} - -//------------ ActionCallExpr ----------------------------------------------- - -// An invocation of an action, used in the Apply section only, it consists of -// the name of the actions plus an optional arguments list of variable names, -// whose values are to be passed in at runtime. The Action definition should -// have all the variables defined in a `with` statement. -// -// The fields of this struct are the same as `MethodComputeExpr`, but it gets -// treated differently at eval time. +/// An invocation of an action, used in the Apply section only, it consists of +/// the name of the actions plus an optional arguments list of variable names, +/// whose values are to be passed in at runtime. The Action definition should +/// have all the variables defined in a `with` statement. +/// +/// The fields of this struct are the same as `MethodComputeExpr`, but it gets +/// treated differently at eval time. #[derive(Clone, Debug)] pub struct ActionCallExpr { pub action_id: Identifier, pub args: Option, } -impl ActionCallExpr { - pub fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, (action_id, args)) = context( - "action call expression", - tuple(( - opt_ws(Identifier::parse), - opt(delimited(char('('), ArgExprList::parse, char(')'))), - )), - )(input)?; - - Ok((input, Self { action_id, args })) - } -} - -//------------ TermActionExpr ----------------------------------------------- - -// The same as the ActionCallExpr and the MethodCallExpr, but for its -// treatment by the evaluator. - +/// The same as the ActionCallExpr and the MethodCallExpr, but for its +/// treatment by the evaluator. #[derive(Clone, Debug)] pub struct TermCallExpr { pub term_id: Identifier, pub args: Option, } -impl TermCallExpr { - pub fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, (term_id, args)) = context( - "term call expression", - tuple(( - opt_ws(Identifier::parse), - opt(delimited(char('('), ArgExprList::parse, char(')'))), - )), - )(input)?; - - Ok((input, Self { term_id, args })) - } -} - #[derive(Clone, Debug)] pub struct PatternMatchActionArm { pub variant_id: Identifier, @@ -1129,92 +282,13 @@ pub struct PatternMatchActionArm { pub actions: Vec<(Option, Option)>, } -impl PatternMatchActionArm { - pub fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - // let (input, (variant_id, data_field, guard, actions)) = - let (input, (variant_id, data_field)) = context( - "pattern match expression", - tuple(( - opt_ws(Identifier::parse), - opt(delimited( - opt_ws(char('(')), - Identifier::parse, - opt_ws(char(')')), - )), - )), - )(input)?; - - let (input, guard) = opt(preceded( - opt_ws(char('|')), - opt_ws(TermCallExpr::parse), - ))(input)?; - - let (input, actions) = preceded( - opt_ws(tag("->")), - // Either a .. - alt(( - // bunch of action expressions, within curly - // braces and semi-colon separated, and - // terminated with an optional comma, or... - delimited( - opt_ws(char('{')), - alt(( - many1(context( - "Action Expression", - tuple(( - map( - opt_ws(terminated( - ActionCallExpr::parse, - opt_ws(char(';')), - )), - Some, - ), - opt(opt_ws(accept_reject)), - )), - )), - map(opt_ws(accept_reject), |ar| { - vec![(None, Some(ar))] - }), - )), - terminated(opt_ws(char('}')), opt_ws(char(','))), - ), - // a single action expression that must have a - // comma at the end. Cannot end with a - // accept_reject - map( - terminated( - opt_ws(ActionCallExpr::parse), - opt_ws(char(',')), - ), - |l_e| vec![(Some(l_e), None)], - ), - )), - )(input)?; - - Ok(( - input, - Self { - variant_id, - guard, - data_field, - actions, - }, - )) - } -} - -//------------ TermMatchExpr ------------------------------------------------ - -// A TermMatchExpr describes a variant of an enum together with its data field -// and one or more logical expressions, that will evaluate to a boolean, it -// may reference the data field. Note that this MatchExpr will be split out in -// (variant_id, data_field) and the logical expressions to be able to store it -// in a TermScope as `VariantMatchExpr`s. Since it only store Logical -// Expressions it is only fit for use in a Term section. In the Apply sections -// the PatternMatchActionExpr is used. - -// MatchExpr := Identifier '(' Identifier ')' '->' (( LogicalExpr ';' ',' ) | -// '{' ( LogicalExpr ';' )+ '}' ','? ) +/// A TermMatchExpr describes a variant of an enum together with its data field +/// and one or more logical expressions, that will evaluate to a boolean, it +/// may reference the data field. Note that this MatchExpr will be split out in +/// (variant_id, data_field) and the logical expressions to be able to store it +/// in a TermScope as `VariantMatchExpr`s. Since it only store Logical +/// Expressions it is only fit for use in a Term section. In the Apply sections +/// the PatternMatchActionExpr is used. #[derive(Clone, Debug)] pub struct TermMatchExpr { pub variant_id: Identifier, @@ -1222,58 +296,6 @@ pub struct TermMatchExpr { pub logical_expr: Vec, } -impl TermMatchExpr { - pub fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, (variant_id, data_field, logical_expr)) = context( - "match expression", - tuple(( - opt_ws(Identifier::parse), - opt(delimited( - opt_ws(char('(')), - Identifier::parse, - opt_ws(char(')')), - )), - preceded( - opt_ws(tag("->")), - // Either a .. - alt(( - // bunch of logical expressions, within curly - // braces and semi-colon separated, and - // terminated with an optional comma, or... - terminated( - delimited( - opt_ws(char('{')), - many1(terminated( - LogicalExpr::parse, - opt_ws(char(';')), - )), - opt_ws(char('}')), - ), - opt(opt_ws(char(','))), - ), - // a single logical expression that must - // have a comma at the end. - map( - terminated(LogicalExpr::parse, opt_ws(char(','))), - |l_e| vec![l_e], - ), - )), - ), - )), - )( - input - )?; - Ok(( - input, - Self { - variant_id, - data_field, - logical_expr, - }, - )) - } -} - //------------ Rib ----------------------------------------------------------- // Rib ::= "rib" Identifier 'contains' TypeIdentifier '{' RibBody '}' @@ -1285,56 +307,6 @@ pub struct Rib { pub body: RibBody, } -impl Rib { - pub fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, (ident, contain_ty, body, _)) = context( - "rib definition", - tuple(( - preceded( - tag("rib"), - context( - "rib name", - delimited( - multispace1, - Identifier::parse, - multispace1, - ), - ), - ), - context( - "contains", - preceded( - opt_ws(tag("contains")), - delimited( - multispace1, - TypeIdentifier::parse, - multispace1, - ), - ), - ), - context( - "rib block", - cut(delimited( - opt_ws(char('{')), - RibBody::parse, - opt_ws(char('}')), - )), - ), - map(skip_opt_ws, |_| ()), - )), - )(input)?; - - Ok(( - input, - Rib { - ident, - contain_ty, - body, - }, - )) - } -} - #[derive(Clone, Debug)] pub struct RibBody { pub key_values: Vec, @@ -1347,64 +319,6 @@ pub enum RibField { ListField(Box<(Identifier, ListTypeIdentifier)>), } -//------------ RibBody ------------------------------------------------------- - -// -// The body of a Rib consists of an (optional) enumeration of (field_name, -// type) pairs. - -// RibBody ::= ( Identifier ':' ( -// TypeIdentifier | -// '{' RecordTypeIdentifier '}' | -// '[' ListTypeIdentifier ']' ) ','? )+ - -impl RibBody { - pub fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, key_values) = context( - "items list", - separated_list1( - char(','), - opt_ws(cut(alt(( - map(TypeIdentField::parse, RibField::PrimitiveField), - map( - tuple(( - terminated( - opt_ws(Identifier::parse), - opt_ws(char(':')), - ), - delimited( - opt_ws(char('{')), - RecordTypeIdentifier::parse, - opt_ws(char('}')), - ), - )), - |r| RibField::RecordField(Box::new(r)), - ), - map( - tuple(( - terminated( - opt_ws(Identifier::parse), - opt_ws(char(':')), - ), - delimited( - opt_ws(char('[')), - ListTypeIdentifier::parse, - opt_ws(char(']')), - ), - )), - |l| RibField::ListField(Box::new(l)), - ), - )))), - ), - )(input)?; - Ok((input, RibBody { key_values })) - } -} - -//------------ Table ----------------------------------------------------- - -// Table ::= "table" Identifier 'contains' TypeIdentifier '{' TableBody '}' - #[derive(Clone, Debug)] pub struct Table { pub ident: Identifier, @@ -1412,60 +326,6 @@ pub struct Table { pub body: RibBody, } -impl Table { - pub fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, (ident, contain_ty, body, _)) = context( - "table definition", - tuple(( - preceded( - opt_ws(tag("table")), - context( - "table name", - delimited( - multispace1, - Identifier::parse, - multispace1, - ), - ), - ), - context( - "contains", - preceded( - opt_ws(tag("contains")), - delimited( - multispace1, - TypeIdentifier::parse, - multispace1, - ), - ), - ), - context( - "table block", - cut(delimited( - opt_ws(char('{')), - RibBody::parse, - opt_ws(char('}')), - )), - ), - map(skip_opt_ws, |_| ()), - )), - )(input)?; - - Ok(( - input, - Table { - ident, - contain_ty, - body, - }, - )) - } -} - -//------------ OutputStream ------------------------------------------------- - -// Table ::= "table" Identifier 'contains' TypeIdentifier '{' TableBody '}' - #[derive(Clone, Debug)] pub struct OutputStream { pub ident: Identifier, @@ -1473,150 +333,16 @@ pub struct OutputStream { pub body: RibBody, } -impl OutputStream { - pub fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, (ident, contain_ty, body, _)) = context( - "output-stream definition", - tuple(( - preceded( - opt_ws(tag("output-stream")), - context( - "output-stream name", - delimited( - multispace1, - Identifier::parse, - multispace1, - ), - ), - ), - context( - "contains", - preceded( - opt_ws(tag("contains")), - delimited( - multispace1, - TypeIdentifier::parse, - multispace1, - ), - ), - ), - context( - "output-stream block", - cut(delimited( - opt_ws(char('{')), - RibBody::parse, - opt_ws(char('}')), - )), - ), - map(skip_opt_ws, |_| ()), - )), - )(input)?; - - Ok(( - input, - OutputStream { - ident, - contain_ty, - body, - }, - )) - } -} - -//============ Separators ==================================================== - -/// Parses something preceded by optional white space. -fn opt_ws<'a, O, F>( - parse: F, -) -> impl FnMut(&'a str) -> IResult<&'a str, O, VerboseError<&str>> -where - F: FnMut(&'a str) -> IResult<&str, O, VerboseError<&str>>, -{ - preceded(skip_opt_ws, parse) -} - -/// Optional white space. -/// -/// White space is all actual white space characters plus comments. -fn skip_opt_ws(input: &str) -> IResult<&str, (), VerboseError<&str>> { - fold_many0(alt((map(multispace1, |_| ()), comment)), || (), |_, _| ())( - input, - ) -} - -/// Comments start with a hash and run to the end of a line. -fn comment(input: &str) -> IResult<&str, (), VerboseError<&str>> { - let (input, _) = tuple((tag("//"), take_until("\n")))(input)?; - map(tag_char('\n'), |_| ())(input) -} - -//------------ Identifier ---------------------------------------------------- - /// An identifier is the name of variables or other things. /// /// It is a word composed of a leading alphabetic Unicode character, followed /// by alphanumeric Unicode characters or underscore or hyphen. -/// -/// Identifier ::= ([a-z]) ([0-9a-z-_])* -/// #[derive(Clone, Debug, Ord, PartialOrd)] pub struct Identifier { /// The actual identifier. pub ident: ShortString, } -impl Identifier { - fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, _) = context( - "no keyword", - not(delimited( - multispace1, - tuple(( - tag("for"), - tag("type"), - tag("with"), - tag("in"), - tag("not"), - tag("define"), - tag("filter-map"), - tag("import"), - tag("term"), - tag("filter"), - tag("match"), - tag("route"), - tag("matches"), - tag("return"), - tag("prefix"), - tag("true"), - tag("false"), - tag("apply"), - tag("use"), - tag("type"), - )), - multispace1, - )), - )(input)?; - let (input, ident) = context( - "identifier", - recognize(preceded( - take_while1(|ch: char| { - ch.is_alphabetic() || ch == '_' || ch == '-' - }), - take_while(|ch: char| { - ch.is_alphanumeric() || ch == '_' || ch == '-' - }), - )), - )(input)?; - - Ok(( - input, - Identifier { - ident: ident.into(), - }, - )) - } -} - impl AsRef for Identifier { fn as_ref(&self) -> &str { self.ident.as_ref() @@ -1649,8 +375,6 @@ impl fmt::Display for Identifier { } } -//------------ TypeIdentifier ----------------------------------------------- - /// An identifier is the unique name of all expressions that we allow to be /// named. /// @@ -1663,25 +387,6 @@ pub struct TypeIdentifier { pub ident: ShortString, } -/// A TypeIdentifier is the unique name of a type. - -/// TypeIdentifier ::= [A-Z] ([0-9a-zA-Z])* - -impl TypeIdentifier { - fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, ident) = recognize(pair( - take_while1(|ch: char| ch.is_alphabetic() && ch.is_uppercase()), - take_while(|ch: char| ch.is_alphanumeric()), - ))(input)?; - Ok(( - input, - TypeIdentifier { - ident: ident.into(), - }, - )) - } -} - impl AsRef for TypeIdentifier { fn as_ref(&self) -> &str { self.ident.as_ref() @@ -1714,10 +419,7 @@ impl fmt::Display for TypeIdentifier { } } -//------------ TypeIdentField ------------------------------------------------ - /// A `field_name: Type` pair. - #[derive(Clone, Debug)] pub struct TypeIdentField { /// The name of the field. @@ -1726,122 +428,32 @@ pub struct TypeIdentField { pub ty: TypeIdentifier, } -impl TypeIdentField { - pub fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, (field_name, ty)) = context( - "key-value pair", - separated_pair( - preceded(multispace0, Identifier::parse), - preceded(multispace0, char(':')), - preceded(multispace0, TypeIdentifier::parse), - ), - )(input)?; - - Ok((input, Self { field_name, ty })) - } -} - -//------------ ListTypeIdentifier ------------------------------------------- - -// ListTypeIdentifier ::= '[' TypeIdentifier ','? ']'+ - #[derive(Clone, Debug)] pub struct ListTypeIdentifier { pub inner_type: TypeIdentifier, } -impl ListTypeIdentifier { - pub fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, inner_type) = - context("List Value", TypeIdentifier::parse)(input)?; - Ok((input, ListTypeIdentifier { inner_type })) - } -} - -//------------ StringLiteral ----------------------------------------------- - -// Our take on a literal string is just a Identifier wrapped in two double -// quotes. We don't do any escaping or anything like that. - -// StringLiteral ::= '"' Identifier '"' +/// Our take on a literal string is just a Identifier wrapped in two double +/// quotes. We don't do any escaping or anything like that. #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct StringLiteral(pub(crate) String); -impl StringLiteral { - fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, string) = parse_string::parse_string(input)?; - Ok((input, Self(string))) - } -} - impl From for String { fn from(literal: StringLiteral) -> Self { literal.0 } } -//------------ RecordTypeIdentifier ----------------------------------------- - -// The user-defined type of a record. It's very similar to a RibBody (in EBNF -// it's the same), but it simplifies creating the SymbolTable, because they're -// semantically different. - -// RecordTypeIdentifier ::= '{' ( Identifier ':' -// RecordTypeIdentifier | '{' RecordBody '}' -// ','? )+ '}' - +/// The user-defined type of a record. It's very similar to a RibBody (in EBNF +/// it's the same), but it simplifies creating the SymbolTable, because they're +/// semantically different. #[derive(Clone, Debug)] pub struct RecordTypeIdentifier { pub key_values: Vec, } -impl RecordTypeIdentifier { - pub fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, key_values) = context( - "Record Value", - separated_list1( - char(','), - opt_ws(cut(alt(( - map(TypeIdentField::parse, RibField::PrimitiveField), - map( - tuple(( - terminated( - opt_ws(Identifier::parse), - opt_ws(char(':')), - ), - delimited( - opt_ws(char('[')), - ListTypeIdentifier::parse, - opt_ws(char(']')), - ), - )), - |r| RibField::ListField(Box::new(r)), - ), - map( - tuple(( - terminated( - opt_ws(Identifier::parse), - opt_ws(char(':')), - ), - delimited( - opt_ws(char('{')), - RecordTypeIdentifier::parse, - opt_ws(char('}')), - ), - )), - |r| RibField::RecordField(Box::new(r)), - ), - )))), - ), - )(input)?; - Ok((input, RecordTypeIdentifier { key_values })) - } -} - //============= Literals ==================================================== -//------------ IntegerLiteral ----------------------------------------------- - /// An integer literal is a sequence of digits. /// IntegerLiteral ::= [0-9]+ /// @@ -1849,27 +461,6 @@ impl RecordTypeIdentifier { #[derive(Clone, Debug, PartialEq, Eq)] pub struct IntegerLiteral(pub i64); -impl IntegerLiteral { - pub fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, digits) = context( - "integer literal", - recognize(pair( - opt(char('-')), - take_while1(|ch: char| ch.is_ascii_digit()), - )), - )(input)?; - - let value = digits.parse::().map_err(|e| { - nom::Err::Failure(VerboseError::from_external_error( - input, - nom::error::ErrorKind::Char, - e, - )) - })?; - Ok((input, Self(value))) - } -} - impl From<&'_ IntegerLiteral> for ShortString { fn from(literal: &IntegerLiteral) -> Self { ShortString::from(literal.0.to_string().as_str()) @@ -1891,24 +482,6 @@ impl From<&'_ IntegerLiteral> for i64 { #[derive(Clone, Debug, PartialEq, Eq)] pub struct PrefixLengthLiteral(pub u8); -impl PrefixLengthLiteral { - pub fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, digits) = context( - "prefix length literal", - preceded(char('/'), take_while1(|ch: char| ch.is_ascii_digit())), - )(input)?; - - let value = digits.parse::().map_err(|e| { - nom::Err::Failure(VerboseError::from_external_error( - input, - nom::error::ErrorKind::Char, - e, - )) - })?; - Ok((input, Self(value))) - } -} - impl From<&'_ PrefixLengthLiteral> for ShortString { fn from(literal: &PrefixLengthLiteral) -> Self { ShortString::from(literal.0.to_string().as_str()) @@ -1929,27 +502,6 @@ impl From<&'_ PrefixLengthLiteral> for u8 { pub struct HexLiteral(pub u64); -impl HexLiteral { - pub fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, digits) = context( - "hex literal", - recognize(pair( - tag("0x"), - take_while1(|ch: char| ch.is_ascii_hexdigit()), - )), - )(input)?; - - let value = u64::from_str_radix(&digits[2..], 16).map_err(|e| { - nom::Err::Failure(VerboseError::from_external_error( - input, - nom::error::ErrorKind::HexDigit, - e, - )) - })?; - Ok((input, Self(value))) - } -} - impl From<&'_ HexLiteral> for ShortString { fn from(literal: &HexLiteral) -> Self { ShortString::from(literal.0.to_string().as_str()) @@ -1965,38 +517,10 @@ impl From<&'_ HexLiteral> for u64 { //------------ AsnLiteral --------------------------------------------------- /// An ASN literal is a sequence of hex digits, prefixed by 'AS' -/// -/// #[derive(Clone, Debug)] pub struct AsnLiteral(pub u32); -// To avoid a parse collision with an extended community literal i.e -// 'AS{}:{}:{}', we are checking that the literal is not followed by a ':'. -impl AsnLiteral { - pub fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, digits) = context( - "ASN literal", - tuple(( - recognize(pair( - tag("AS"), - take_while1(|ch: char| ch.is_ascii_digit()), - )), - not(char(':')), - )), - )(input)?; - - let value = digits.0[2..].parse::().map_err(|e| { - nom::Err::Failure(VerboseError::from_external_error( - input, - nom::error::ErrorKind::AlphaNumeric, - e, - )) - })?; - Ok((input, Self(value))) - } -} - impl From<&'_ AsnLiteral> for ShortString { fn from(literal: &AsnLiteral) -> Self { ShortString::from(literal.0.to_string().as_str()) @@ -2014,77 +538,6 @@ impl From<&'_ AsnLiteral> for Asn { #[derive(Clone, Debug)] pub struct StandardCommunityLiteral(pub String); -impl StandardCommunityLiteral { - pub fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, std_comm) = context( - "Standard Community Literal", - tuple(( - opt(tag("0x")), - take_while_m_n(1, 5, |ch: char| ch.is_hex_digit()), - char(':'), - opt(tag("0x")), - take_while_m_n(1, 5, |ch: char| ch.is_hex_digit()), - not(char(':')), - )), - )(input)?; - - // Let routecore do all the heavy lifting here: Not erroring out here - // on an invalid tag, or too big a AS or AN, because the eval phase of - // the compilation (that uses routecore's `from_str` will error out - // with a way more useful error. Also syntactically it might not be - // wrong to have unknown tags, or too big a AS or AN here. - - // See tests in bgp_filters.rs for the kind of errors the eval() phase - // returns. - - // What we are doing here is catering for both decimal and hexadecimal - // literals provided. Hexadecimals will be converted into decimal, so - // that the from_str method from routecore can process them. Again, we - // are not trying to parse here as tightly fitted as possible (that - // would be a u16 here), we're parsing the largest possible type, so - // that the eval phase can optionally error out. - let value_1 = if let Some(_hex_tag) = std_comm.0 { - u64::from_str_radix(std_comm.1, 16).map_err(|e| { - nom::Err::Failure(VerboseError::from_external_error( - input, - nom::error::ErrorKind::HexDigit, - e, - )) - })? - } else { - std_comm.1.parse::().map_err(|e| { - nom::Err::Failure(VerboseError::from_external_error( - input, - nom::error::ErrorKind::Digit, - e, - )) - })? - }; - - let value_2 = if let Some(_hex_tag) = std_comm.3 { - u64::from_str_radix(std_comm.4, 16).map_err(|e| { - nom::Err::Failure(VerboseError::from_external_error( - input, - nom::error::ErrorKind::HexDigit, - e, - )) - })? - } else { - std_comm.4.parse::().map_err(|e| { - nom::Err::Failure(VerboseError::from_external_error( - input, - nom::error::ErrorKind::Digit, - e, - )) - })? - }; - - // See tests in bgp_filters.rs for the kind of errors the eval() phase - // returns. - Ok((input, Self(format!("{}:{}", value_1, value_2)))) - } -} - impl From<&'_ StandardCommunityLiteral> for ShortString { fn from(literal: &StandardCommunityLiteral) -> Self { ShortString::from(literal.0.to_string().as_str()) @@ -2105,9 +558,7 @@ impl TryFrom<&'_ StandardCommunityLiteral> for Community { "Cannot convert literal '{}' into Extended Community: {e}", literal.0, )))?; - Ok( - comm.into(), - ) + Ok(comm.into()) } } @@ -2116,80 +567,6 @@ impl TryFrom<&'_ StandardCommunityLiteral> for Community { #[derive(Clone, Debug)] pub struct ExtendedCommunityLiteral(pub String); -// To avoid a collision with a IPv6 address, which can never be three hex -// blocks, we make sure that the ext. community here is never followed by a -// ':' -impl ExtendedCommunityLiteral { - pub fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, ext_comm) = context( - "Extended Community Literal", - tuple(( - take_while1(|ch: char| ch.is_alpha()), - tag(":"), - opt(tag("0x")), - take_while1(|ch: char| ch.is_hex_digit()), - tag(":"), - opt(tag("0x")), - take_while_m_n(1, 5, |ch: char| ch.is_hex_digit()), - not(char(':')), - )), - )(input)?; - - // Let routecore do all the heavy lifting here: Not erroring out here - // on an invalid tag, or too big a AS or AN, because the eval phase of - // the compilation (that uses routecore's `from_str` will error out - // with a way more useful error. Also syntactically it might not be - // wrong to have unknown tags, or too big a AS or AN here. - - // See tests in bgp_filters.rs for the kind of errors the eval() phase - // returns. - - // What we are doing here is catering for both decimal and hexadecimal - // literals provided. Hexadecimals will be converted into decimal, so - // that the from_str method from routecore can process them. Again, we - // are not trying to parse here as tightly fitted as possible (that - // would be a u16 here), we're parsing the largest possible type, so - // that the eval phase can optionally error out. - let value_1 = if let Some(_hex_tag) = ext_comm.2 { - u64::from_str_radix(ext_comm.3, 16).map_err(|e| { - nom::Err::Failure(VerboseError::from_external_error( - input, - nom::error::ErrorKind::HexDigit, - e, - )) - })? - } else { - ext_comm.3.parse::().map_err(|e| { - nom::Err::Failure(VerboseError::from_external_error( - input, - nom::error::ErrorKind::Digit, - e, - )) - })? - }; - - let value_2 = if let Some(_hex_tag) = ext_comm.5 { - u32::from_str_radix(ext_comm.6, 16).map_err(|e| { - nom::Err::Failure(VerboseError::from_external_error( - input, - nom::error::ErrorKind::HexDigit, - e, - )) - })? - } else { - ext_comm.6.parse::().map_err(|e| { - nom::Err::Failure(VerboseError::from_external_error( - input, - nom::error::ErrorKind::Digit, - e, - )) - })? - }; - - Ok((input, Self(format!("{}:{value_1}:{value_2}", ext_comm.0)))) - } -} - impl From<&'_ ExtendedCommunityLiteral> for ShortString { fn from(literal: &ExtendedCommunityLiteral) -> Self { ShortString::from(literal.0.to_string().as_str()) @@ -2210,9 +587,7 @@ impl<'a> TryFrom<&'a ExtendedCommunityLiteral> for Community { "Cannot convert literal '{}' into Extended Community: {e}", literal.0, )))?; - Ok( - comm.into() - ) + Ok(comm.into()) } } @@ -2221,101 +596,6 @@ impl<'a> TryFrom<&'a ExtendedCommunityLiteral> for Community { #[derive(Clone, Debug)] pub struct LargeCommunityLiteral(pub String); -impl LargeCommunityLiteral { - pub fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, l_comm) = context( - "Large Community Literal", - tuple(( - opt(alt((tag("AS"), tag("0x")))), - take_while1(|ch: char| ch.is_hex_digit()), - char(':'), - opt(tag("0x")), - take_while1(|ch: char| ch.is_hex_digit()), - char(':'), - opt(tag("0x")), - take_while1(|ch: char| ch.is_hex_digit()), - not(char(':')) - )), - )(input)?; - - // Let routecore do all the heavy lifting here: Not erroring out here - // on an invalid tag, or too big a AS or AN, because the eval phase of - // the compilation (that uses routecore's `from_str` will error out - // with a way more useful error. Also syntactically it might not be - // wrong to have unknown tags, or too big a AS or AN here. - - // See tests in bgp_filters.rs for the kind of errors the eval() phase - // returns. - - // What we are doing here is catering for both decimal and hexadecimal - // literals provided. Hexadecimals will be converted into decimal, so - // that the from_str method from routecore can process them. Again, we - // are not trying to parse here as tightly fitted as possible (that - // would be a u16 here), we're parsing the largest possible type, so - // that the eval phase can optionally error out. - let value_1 = match l_comm.0 { - Some("0x") => u64::from_str_radix(l_comm.1, 16).map_err(|e| { - nom::Err::Failure(VerboseError::from_external_error( - input, - nom::error::ErrorKind::HexDigit, - e, - )) - })?, - _ => l_comm.1.parse::().map_err(|e| { - nom::Err::Failure(VerboseError::from_external_error( - input, - nom::error::ErrorKind::Digit, - e, - )) - })?, - }; - - let value_2 = if let Some(_hex_tag) = l_comm.3 { - u32::from_str_radix(l_comm.4, 16).map_err(|e| { - nom::Err::Failure(VerboseError::from_external_error( - input, - nom::error::ErrorKind::HexDigit, - e, - )) - })? - } else { - l_comm.4.parse::().map_err(|e| { - nom::Err::Failure(VerboseError::from_external_error( - input, - nom::error::ErrorKind::Digit, - e, - )) - })? - }; - - let value_3 = if let Some(_hex_tag) = l_comm.6 { - u32::from_str_radix(l_comm.7, 16).map_err(|e| { - nom::Err::Failure(VerboseError::from_external_error( - input, - nom::error::ErrorKind::HexDigit, - e, - )) - })? - } else { - l_comm.7.parse::().map_err(|e| { - nom::Err::Failure(VerboseError::from_external_error( - input, - nom::error::ErrorKind::Digit, - e, - )) - })? - }; - - Ok(( - input, - Self(format!( - "{}{value_1}:{value_2}:{value_3}", - if let Some("AS") = l_comm.0 { "AS" } else { "" } - )), - )) - } -} - impl From<&'_ LargeCommunityLiteral> for ShortString { fn from(literal: &LargeCommunityLiteral) -> Self { ShortString::from(literal.0.to_string().as_str()) @@ -2329,12 +609,13 @@ impl TryFrom<&'_ LargeCommunityLiteral> for Community { fn try_from( literal: &LargeCommunityLiteral, ) -> Result { - let comm = ::from_str( - &literal.0 - ).map_err( - |e| CompileError::from(format!( - "Cannot convert literal '{}' into Large Community: {e}", literal.0, - )))?; + let comm = ::from_str(&literal.0) + .map_err(|e| { + CompileError::from(format!( + "Cannot convert literal '{}' into Large Community: {e}", + literal.0, + )) + })?; Ok(comm.into()) } @@ -2348,47 +629,11 @@ impl TryFrom<&'_ LargeCommunityLiteral> for Community { #[derive(Clone, Debug)] pub struct FloatLiteral(pub f64); -impl FloatLiteral { - pub fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, digits) = context( - "float literal", - recognize(tuple(( - opt(char('-')), - take_while1(|ch: char| ch.is_ascii_digit()), - char('.'), - take_while1(|ch: char| ch.is_ascii_digit()), - ))), - )(input)?; - - let value = digits.parse::().map_err(|e| { - nom::Err::Failure(VerboseError::from_external_error( - input, - nom::error::ErrorKind::AlphaNumeric, - e, - )) - })?; - Ok((input, Self(value))) - } -} - -//------------ BooleanLiteral ----------------------------------------------- /// A boolean literal is either `true` or `false`. -/// BooleanLiteral ::= 'true' | 'false' #[derive(Clone, Debug)] pub struct BooleanLiteral(pub bool); -impl BooleanLiteral { - pub fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, value) = alt(( - map(tag("true"), |_| Self(true)), - map(tag("false"), |_| Self(false)), - ))(input)?; - - Ok((input, value)) - } -} - impl From<&'_ BooleanLiteral> for ShortString { fn from(literal: &BooleanLiteral) -> Self { ShortString::from(literal.0.to_string().as_str()) @@ -2401,46 +646,12 @@ impl From<&'_ BooleanLiteral> for bool { } } -//------------ ByteStringLiteral -------------------------------------------- /// A byte string literal is a sequence of bytes, preceded by '0x' -/// ByteStringLiteral ::= 0x[0-9a-fA-F]+ - #[derive(Clone, Debug)] pub struct ByteStringLiteral(pub SmallVec<[u8; 24]>); -impl<'a> ByteStringLiteral { - pub fn parse(input: &'a str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, value) = (preceded( - tag("0x"), - many1(terminated(take(2_usize), opt(char('_')))), - ))(input)?; - - let mut result_value: SmallVec<[u8; 24]> = SmallVec::new(); - - for v in &value { - let v = u8::from_str_radix(v, 16); - - if let Ok(v) = v { - result_value.push(v); - } else { - return Err(nom::Err::Error(VerboseError::from_error_kind( - input, - ErrorKind::Digit, - ))); - } - } - - Ok((input, Self(result_value))) - } -} - -//------------ AcceptReject ------------------------------------------------- - -// Every filter needs to return either a 'accept' or 'reject' statement. -// failing to set it properly ends in the whole thing being cancelled. - -// AcceptReject ::= 'return'? ( 'accept' | 'reject' ) ';' - +/// Every filter needs to return either a 'accept' or 'reject' statement. +/// failing to set it properly ends in the whole thing being cancelled. #[derive( Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, )] @@ -2450,27 +661,6 @@ pub enum AcceptReject { NoReturn, } -fn accept_reject( - input: &str, -) -> IResult<&str, AcceptReject, VerboseError<&str>> { - context( - "accept or reject", - preceded( - opt(opt_ws(tag("return"))), - alt(( - map( - terminated(opt_ws(tag("accept")), opt_ws(char(';'))), - |_| AcceptReject::Accept, - ), - map( - terminated(opt_ws(tag("reject")), opt_ws(char(';'))), - |_| AcceptReject::Reject, - ), - )), - ), - )(input) -} - impl std::fmt::Display for AcceptReject { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { @@ -2481,8 +671,6 @@ impl std::fmt::Display for AcceptReject { } } -//------------ LiteralExpr --------------------------------------------------- - #[derive(Clone, Debug)] pub enum LiteralExpr { StringLiteral(StringLiteral), @@ -2495,37 +683,7 @@ pub enum LiteralExpr { LargeCommunityLiteral(LargeCommunityLiteral), IntegerLiteral(IntegerLiteral), HexLiteral(HexLiteral), - BooleanLiteral(BooleanLiteral) -} - -impl LiteralExpr { - pub fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - alt(( - map(StringLiteral::parse, LiteralExpr::StringLiteral), - map( - ExtendedCommunityLiteral::parse, - LiteralExpr::ExtendedCommunityLiteral, - ), - map( - LargeCommunityLiteral::parse, - LiteralExpr::LargeCommunityLiteral, - ), - map( - StandardCommunityLiteral::parse, - LiteralExpr::StandardCommunityLiteral, - ), - map(Prefix::parse, LiteralExpr::PrefixLiteral), - map(IpAddress::parse, LiteralExpr::IpAddressLiteral), - map(AsnLiteral::parse, LiteralExpr::AsnLiteral), - map(HexLiteral::parse, LiteralExpr::HexLiteral), - map(IntegerLiteral::parse, LiteralExpr::IntegerLiteral), - map(PrefixLengthLiteral::parse, LiteralExpr::PrefixLengthLiteral), - map(tag("true"), |_| LiteralExpr::BooleanLiteral(BooleanLiteral(true))), - map(tag("false"), |_| { - LiteralExpr::BooleanLiteral(BooleanLiteral(false)) - }) - ))(input) - } + BooleanLiteral(BooleanLiteral), } impl TryFrom<&'_ LiteralExpr> for TypeValue { @@ -2548,133 +706,53 @@ impl TryFrom<&'_ LiteralExpr> for TypeValue { } } -//------------ LiteralAccessExpr --------------------------------------------- #[derive(Clone, Debug)] pub struct LiteralAccessExpr { pub literal: LiteralExpr, - pub access_expr: Vec -} - -impl LiteralAccessExpr { - fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, (literal, access_expr)) = tuple(( - LiteralExpr::parse, - many0(preceded(char('.'), AccessExpr::parse)), - ))(input)?; - - Ok((input, Self { literal, access_expr })) - } + pub access_expr: Vec, } -//------------ ValueExpr ----------------------------------------------------- - -// ValueExpr ::= LiteralAccessExpr | PrefixMatchExpr | ComputeExpr | -// RootMethodCallExpr | TypedRecordExpr | ListExpr - -// An expression that ultimately will resolve into a TypeValue, e.g. the -// right-hand of an assignment, or an argument to a method, etc. - +/// An expression that ultimately will resolve into a TypeValue, e.g. the +/// right-hand of an assignment, or an argument to a method, etc. #[derive(Clone, Debug)] pub enum ValueExpr { - // a literal, or a chain of field accesses and/or methods on a literal, - // e.g. `10.0.0.0/8.covers(..)` + /// a literal, or a chain of field accesses and/or methods on a literal, + /// e.g. `10.0.0.0/8.covers(..)` LiteralAccessExpr(LiteralAccessExpr), - // a JunOS style prefix match expression, e.g. `0.0.0.0/0 - // prefix-length-range /12-/16` + /// a JunOS style prefix match expression, e.g. `0.0.0.0/0 + /// prefix-length-range /12-/16` PrefixMatchExpr(PrefixMatchExpr), - // an access receiver (an expression named with a single identifier, e.g. - // `my_var`), or a chain of field accesses and/or methods on an access - // receiver. + /// an access receiver (an expression named with a single identifier, e.g. + /// `my_var`), or a chain of field accesses and/or methods on an access + /// receiver. ComputeExpr(ComputeExpr), - // an expression of the form `word(argument)`, so nothing in front of - // `word`, this would be something like a builtin method call, or an - // action or term with an argument. + /// an expression of the form `word(argument)`, so nothing in front of + /// `word`, this would be something like a builtin method call, or an + /// action or term with an argument. RootMethodCallExpr(MethodComputeExpr), - // a record that doesn't have a type mentioned in the assignment of it, - // e.g `{ value_1: 100, value_2: "bla" }`. This can also be a sub-record - // of a record that does have an explicit type. + /// a record that doesn't have a type mentioned in the assignment of it, + /// e.g `{ value_1: 100, value_2: "bla" }`. This can also be a sub-record + /// of a record that does have an explicit type. AnonymousRecordExpr(AnonymousRecordValueExpr), - // an expression of a record that does have a type, e.g. `MyType { - // value_1: 100, value_2: "bla" }`, where MyType is a user-defined Record - // Type. + /// an expression of a record that does have a type, e.g. `MyType { + /// value_1: 100, value_2: "bla" }`, where MyType is a user-defined Record + /// Type. TypedRecordExpr(TypedRecordValueExpr), - // An expression that yields a list of values, e.g. `[100, 200, 300]` + /// An expression that yields a list of values, e.g. `[100, 200, 300]` ListExpr(ListValueExpr), } -impl ValueExpr { - pub fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, value_expr) = alt(( - map(ListValueExpr::parse, ValueExpr::ListExpr), - map( - AnonymousRecordValueExpr::parse, - ValueExpr::AnonymousRecordExpr, - ), - map(TypedRecordValueExpr::parse, ValueExpr::TypedRecordExpr), - map(PrefixMatchExpr::parse, ValueExpr::PrefixMatchExpr), - map(LiteralAccessExpr::parse, ValueExpr::LiteralAccessExpr), - map(MethodComputeExpr::parse, ValueExpr::RootMethodCallExpr), - map(ComputeExpr::parse, ValueExpr::ComputeExpr), - ))(input)?; - - Ok((input, value_expr)) - } -} - -//------------ ArgExprList ------------------------------------------------- - -// ArgExprList ::= ( ValueExpr ','? )* - #[derive(Clone, Debug)] pub struct ArgExprList { pub args: Vec, } impl ArgExprList { - fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, args) = context( - "argument expressions", - separated_list0( - preceded(multispace0, char(',')), - opt_ws(ValueExpr::parse), - ), - )(input)?; - - Ok((input, Self { args })) - } - pub fn is_empty(&self) -> bool { self.args.is_empty() } } -//------------ for & with statements ---------------------------------------- - -// ForStatement ::= ('for' Identifier':' TypeIdentifier)? - -fn for_statement( - input: &str, -) -> IResult<&str, Option, VerboseError<&str>> { - context( - "for", - opt(preceded(opt_ws(tag("for")), TypeIdentField::parse)), - )(input) -} - -// WithStatement ::= ('with' (Identifier':' TypeIdentifier)+)? - -fn with_statement( - input: &str, -) -> IResult<&str, Option>, VerboseError<&str>> { - context( - "with", - opt(preceded( - opt_ws(tag("with")), - separated_list1(char(','), TypeIdentField::parse), - )), - )(input) -} - // ============ Compound Expressions ======================================== // Compound expressions consist of field access of data structures or method @@ -2693,13 +771,6 @@ pub enum AccessExpr { } impl AccessExpr { - pub fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - alt(( - map(MethodComputeExpr::parse, AccessExpr::MethodComputeExpr), - map(FieldAccessExpr::parse, AccessExpr::FieldAccessExpr), - ))(input) - } - pub fn get_ident(&self) -> Result<&ShortString, CompileError> { match self { AccessExpr::MethodComputeExpr(expr) => Ok(&expr.ident.ident), @@ -2716,18 +787,12 @@ impl AccessExpr { } } -//------------- ComputeExpr ---------------------------------------------------- - -// It's complete EBNF would be: -// CompoundExpr ::= AccessReceiver(.MethodComputeExpr)? | AccessReceiver - -// A ComputeExpr is an expression that starts with an access receiver, -// optionally followed by one or more method calls, and/or access receivers, -// e.g. 'rib-rov.longest_match(route.prefix).prefix.len()`. -// -// Note that an expression ending in a field access, i.e. not a method call, -// is also parsed as a ComputeExpr, but with an empty method call list. - +/// A ComputeExpr is an expression that starts with an access receiver, +/// optionally followed by one or more method calls, and/or access receivers, +/// e.g. 'rib-rov.longest_match(route.prefix).prefix.len()`. +/// +/// Note that an expression ending in a field access, i.e. not a method call, +/// is also parsed as a ComputeExpr, but with an empty method call list. #[derive(Clone, Debug)] pub struct ComputeExpr { pub receiver: AccessReceiver, @@ -2735,20 +800,6 @@ pub struct ComputeExpr { } impl ComputeExpr { - pub fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, (receiver, access_expr)) = tuple(( - AccessReceiver::parse, - many0(preceded(char('.'), AccessExpr::parse)), - ))(input)?; - Ok(( - input, - Self { - receiver, - access_expr, - }, - )) - } - pub fn get_receiver(&self) -> &AccessReceiver { &self.receiver } @@ -2766,32 +817,18 @@ impl ComputeExpr { } } -//------------- AccessReceiver ------------------------------------------------ - -// The AccessReceiver is the specifier of a data structure that is being called -// (used as part of a ComputeExpr) or used to retrieve one of its fields. Can -// also be a stand-alone specifier. - -// AccessReceiver ::= Identifier | GlobalScope - +/// The AccessReceiver is the specifier of a data structure that is being called +/// (used as part of a ComputeExpr) or used to retrieve one of its fields. Can +/// also be a stand-alone specifier. #[derive(Clone, Debug)] pub enum AccessReceiver { - // The identifier of the data structure. + /// The identifier of the data structure. Ident(Identifier), - // or it can only be in the Global Scope (for global methods), it doesn't - // have a string as identifier then. + /// or it can only be in the Global Scope (for global methods), it doesn't + /// have a string as identifier then. GlobalScope, } -impl AccessReceiver { - fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, receiver) = - context("access receiver", Identifier::parse)(input)?; - - Ok((input, Self::Ident(receiver))) - } -} - impl AccessReceiver { pub fn get_ident(&self) -> Option<&Identifier> { if let Self::Ident(ident) = &self { @@ -2825,51 +862,16 @@ pub struct FieldAccessExpr { pub field_names: Vec, } -impl FieldAccessExpr { - fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, field_names) = context( - "field expression", - separated_list1( - char('.'), - terminated(Identifier::parse, not(char('('))), - ), - )(input)?; - Ok((input, Self { field_names })) - } -} - -//------------- MethodComputeExpr ------------------------------------------ - -// The method that is being called on the data structure (directly or on one -// of its fields). - -// MethodComputeExpr ::= Identifier '(' ArgExprList ')' - +/// The method that is being called on the data structure (directly or on one +/// of its fields). #[derive(Clone, Debug)] pub struct MethodComputeExpr { - // The name of the method. + /// The name of the method. pub ident: Identifier, - // The list with arguments + /// The list with arguments pub args: ArgExprList, } -impl MethodComputeExpr { - pub fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, (ident, args)) = context( - "method call expression", - tuple(( - Identifier::parse, - delimited( - opt_ws(char('(')), - ArgExprList::parse, - opt_ws(char(')')), - ), - )), - )(input)?; - Ok((input, Self { ident, args })) - } -} - //============ First-Order Logic ============================================ // "No, no, you're not thinking. You're just being logical." -- Niels Bohr @@ -2932,15 +934,10 @@ impl MethodComputeExpr { // // ▲────────────────────LF::Or──────────────────▲ -//------------ LogicalExpr -------------------------------------------------- - -// The Logical expression evaluates to a logical formula, that is a tuple of -// (optional boolean expression, logical operator, boolean expression). The -// first boolean expression is optional, only in the case of a negation (not) -// operator. The second boolean expression is always present. - -// LogicalExpr ::= OrExpr | AndExpr | NotExpr | BooleanExpr - +/// The Logical expression evaluates to a logical formula, that is a tuple of +/// (optional boolean expression, logical operator, boolean expression). The +/// first boolean expression is optional, only in the case of a negation (not) +/// operator. The second boolean expression is always present. #[derive(Clone, Debug)] pub enum LogicalExpr { OrExpr(OrExpr), @@ -2949,97 +946,49 @@ pub enum LogicalExpr { BooleanExpr(BooleanExpr), } -impl LogicalExpr { - pub fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - opt_ws(alt(( - map(AndExpr::parse, LogicalExpr::AndExpr), - map(OrExpr::parse, LogicalExpr::OrExpr), - map(NotExpr::parse, LogicalExpr::NotExpr), - map(BooleanExpr::parse, LogicalExpr::BooleanExpr), - )))(input) - } -} - #[derive(Clone, Debug)] pub enum CompareArg { - // A "stand-alone" left|right-hand side argument of a comparison + /// A "stand-alone" left|right-hand side argument of a comparison ValueExpr(ValueExpr), - // A nested logical formula, e.g. (A && B) || (C && D) used as a left| - // right-hand side argument of a comparison. Note that this can only - // have the opposite hand be a boolean expression. + /// A nested logical formula, e.g. (A && B) || (C && D) used as a left| + /// right-hand side argument of a comparison. Note that this can only + /// have the opposite hand be a boolean expression. GroupedLogicalExpr(GroupedLogicalExpr), } -impl CompareArg { - pub fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - alt(( - map(GroupedLogicalExpr::parse, CompareArg::GroupedLogicalExpr), - map(ValueExpr::parse, CompareArg::ValueExpr), - ))(input) - } -} - -//------------ BooleanExpr -------------------------------------------------- - -// A Boolean expression is an expression that *may* evaluate to one of: -// - a Boolean-valued function, which is a fn : X → B, where X is an arbitrary -// set and B is a boolean value. For example, an Integer expression can -// never evaluate to a boolean value, but a method call expression may -// evaluate to a method that returns a Boolean value. -// - a Literal Boolean value, "true" or "false" -// - a Boolean-typed variable, including boolean-typed record fields -// - an Expression containing a boolean-valued operator, such as '==', '!=', -// ">=", "<=" - -// BooleanExpr ::= BooleanLiteral | ComputeExpr | CompareExpr -// | ListCompareExpr | AccessReceiver | PrefixMatchExpr | Identifier - +/// A Boolean expression is an expression that *may* evaluate to one of: +/// - a Boolean-valued function, which is a fn : X → B, where X is an arbitrary +/// set and B is a boolean value. For example, an Integer expression can +/// never evaluate to a boolean value, but a method call expression may +/// evaluate to a method that returns a Boolean value. +/// - a Literal Boolean value, "true" or "false" +/// - a Boolean-typed variable, including boolean-typed record fields +/// - an Expression containing a boolean-valued operator, such as '==', '!=', +/// ">=", "<=" #[derive(Clone, Debug)] pub enum BooleanExpr { - // A complete formula that is wrapped in parentheses is a Boolean-Valued - // Function, since it will always return a Boolean value. + /// A complete formula that is wrapped in parentheses is a Boolean-Valued + /// Function, since it will always return a Boolean value. GroupedLogicalExpr(GroupedLogicalExpr), - // "true" | "false" literals + /// "true" | "false" literals BooleanLiteral(BooleanLiteral), - // A syntactically correct comparison always evaluates to a - // Boolean-Valued Function, since it will always return a Boolean value. + /// A syntactically correct comparison always evaluates to a + /// Boolean-Valued Function, since it will always return a Boolean value. CompareExpr(Box), - // A ComputeExpression *may* evaluate to a function that returns a boolean + /// A ComputeExpression *may* evaluate to a function that returns a boolean ComputeExpr(ComputeExpr), - // Just like a ComputeExpr, a Literal, or a Literal access, e.g. - // `10.0.0.0/16.covers()`, may return a boolean + /// Just like a ComputeExpr, a Literal, or a Literal access, e.g. + /// `10.0.0.0/16.covers()`, may return a boolean LiteralAccessExpr(LiteralAccessExpr), - // Set Compare expression, will *always* result in a boolean-valued - // function. Syntactic sugar for a truth-function that performs - // fn : a -> {a} ∩ B + /// Set Compare expression, will *always* result in a boolean-valued + /// function. Syntactic sugar for a truth-function that performs + /// fn : a -> {a} ∩ B ListCompareExpr(Box), - // syntactic sugar for a method on a prefix function that returns a - // boolean. + /// syntactic sugar for a method on a prefix function that returns a + /// boolean. PrefixMatchExpr(PrefixMatchExpr), } -impl BooleanExpr { - pub fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - alt(( - map(GroupedLogicalExpr::parse, BooleanExpr::GroupedLogicalExpr), - map(ListCompareExpr::parse, |e| { - BooleanExpr::ListCompareExpr(Box::new(e)) - }), - map(CompareExpr::parse, |e| { - BooleanExpr::CompareExpr(Box::new(e)) - }), - map(OptionalGlobalComputeExpr::parse, BooleanExpr::ComputeExpr), - map(PrefixMatchExpr::parse, BooleanExpr::PrefixMatchExpr), - map(LiteralAccessExpr::parse, BooleanExpr::LiteralAccessExpr), - map(BooleanLiteral::parse, BooleanExpr::BooleanLiteral), - ))(input) - } -} - -//------------ CompareExpr -------------------------------------------------- - -// CompareExpr ::= CompareArg CompareOp CompareArg - #[derive(Clone, Debug)] pub struct CompareExpr { pub left: CompareArg, @@ -3047,32 +996,6 @@ pub struct CompareExpr { pub right: CompareArg, } -impl CompareExpr { - pub fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, (left, op, right)) = context( - "Compare Expression", - tuple(( - opt_ws(CompareArg::parse), - opt_ws(alt(( - map(tag("=="), |_| CompareOp::Eq), - map(tag("!="), |_| CompareOp::Ne), - map(tag("<="), |_| CompareOp::Le), - map(tag("<"), |_| CompareOp::Lt), - map(tag(">="), |_| CompareOp::Ge), - map(tag(">"), |_| CompareOp::Gt), - ))), - opt_ws(CompareArg::parse), - )), - )(input)?; - - Ok((input, Self { left, op, right })) - } -} - -//------------ CompareOp ---------------------------------------------------- - -// CompareOp ::= '==' | '!=' | '<' | '<=' | '>' | '>=' - #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] pub enum CompareOp { Eq, @@ -3087,68 +1010,23 @@ pub enum CompareOp { NotIn, } -//------------ AndExpr ------------------------------------------------------ - -// AndExpr ::= MatchExpr '&&' MatchExpr - #[derive(Clone, Debug)] pub struct AndExpr { pub left: BooleanExpr, pub right: BooleanExpr, } -impl AndExpr { - fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, (left, right)) = tuple(( - opt_ws(BooleanExpr::parse), - preceded(opt_ws(tag("&&")), opt_ws(BooleanExpr::parse)), - ))(input)?; - - Ok((input, Self { left, right })) - } -} - -//------------ OrExpr ------------------------------------------------------- - -// OrExpr ::= MatchExpr '||' MatchExpr - #[derive(Clone, Debug)] pub struct OrExpr { pub left: BooleanExpr, pub right: BooleanExpr, } -impl OrExpr { - pub fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, (left, right)) = context( - "or expr", - tuple(( - opt_ws(BooleanExpr::parse), - preceded(opt_ws(tag("||")), opt_ws(BooleanExpr::parse)), - )), - )(input)?; - - Ok((input, Self { left, right })) - } -} - #[derive(Clone, Debug)] pub struct NotExpr { pub expr: BooleanExpr, } -impl NotExpr { - pub fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, expr) = - preceded(opt_ws(tag("!")), opt_ws(BooleanExpr::parse))(input)?; - Ok((input, Self { expr })) - } -} - -//------------ ListCompareExpr ----------------------------------------------- - -// ListCompareExpr ::= ( ValueExpr ( 'in' | 'not in' ) ValueExpr )+ - #[derive(Clone, Debug)] pub struct ListCompareExpr { pub left: ValueExpr, @@ -3156,47 +1034,11 @@ pub struct ListCompareExpr { pub right: ValueExpr, } -impl ListCompareExpr { - fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, (left, op, right)) = tuple(( - opt_ws(ValueExpr::parse), - alt(( - map(opt_ws(tag("in")), |_| CompareOp::In), - map(opt_ws(tag("not in")), |_| CompareOp::NotIn), - )), - opt_ws(ValueExpr::parse), - ))(input)?; - - trace!("ListCompareExpr {:?} {:?} {:?}", left, op, right); - Ok((input, Self { left, op, right })) - } -} - -//------------- GroupedFormulaExpr ------------------------------------------ - -// GroupedFormulaExpr ::= '(' LogicalExpr ')' - #[derive(Clone, Debug)] pub struct GroupedLogicalExpr { pub expr: Box, } -impl GroupedLogicalExpr { - fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, expr) = delimited( - opt_ws(char('(')), - LogicalExpr::parse, - opt_ws(char(')')), - )(input)?; - Ok(( - input, - Self { - expr: Box::new(expr), - }, - )) - } -} - #[derive(Clone, Debug)] pub enum MatchOperator { // 'match' followed by a block containing truth expressions @@ -3212,23 +1054,6 @@ pub enum MatchOperator { } impl MatchOperator { - fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - alt(( - map( - tuple(( - tag("match"), - opt_ws(Identifier::parse), - opt_ws(tag("with")), - )), - |t| MatchOperator::MatchValueWith(t.1), - ), - map(tag("match"), |_| MatchOperator::Match), - map(tag("some"), |_| MatchOperator::Some), - map(tag("exactly-one"), |_| MatchOperator::ExactlyOne), - map(tag("all"), |_| MatchOperator::All), - ))(input) - } - pub(crate) fn get_ident(&self) -> Result { if let MatchOperator::MatchValueWith(id) = self { Ok(id.clone()) @@ -3241,12 +1066,6 @@ impl MatchOperator { } } -//------------ PrefixMatchType ---------------------------------------------- - -// PrefixMatchType ::= ( 'exact' | 'longer' | 'orlonger' | -// 'prefix-length-range' | 'upto' | 'through' | 'netmask' ) -// ( PrefixLength | PrefixLengthRange | IpAddress ) - #[derive(Clone, Debug)] pub enum PrefixMatchType { Exact, @@ -3258,78 +1077,18 @@ pub enum PrefixMatchType { NetMask(IpAddress), } -impl PrefixMatchType { - fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, res) = alt(( - map(tag("exact"), |_| PrefixMatchType::Exact), - map(tag("longer"), |_| PrefixMatchType::Longer), - map(tag("orlonger"), |_| PrefixMatchType::OrLonger), - map( - preceded( - tag("prefix-length-range"), - opt_ws(PrefixLengthRange::parse), - ), - PrefixMatchType::PrefixLengthRange, - ), - map( - preceded(tag("upto"), opt_ws(PrefixLength::parse)), - PrefixMatchType::UpTo, - ), - map( - preceded(tag("through"), opt_ws(PrefixLength::parse)), - PrefixMatchType::Through, - ), - map( - preceded(tag("netmask"), opt_ws(IpAddress::parse)), - PrefixMatchType::NetMask, - ), - ))(input)?; - - Ok((input, res)) - } -} - -//------------ PrefixMatchExpr ---------------------------------------------- - -// PrefixMatchExpr ::= Prefix PrefixMatchType - #[derive(Clone, Debug)] pub struct PrefixMatchExpr { pub prefix: Prefix, pub ty: PrefixMatchType, } -impl PrefixMatchExpr { - pub fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, (prefix, ty)) = tuple(( - opt_ws(Prefix::parse), - opt_ws(PrefixMatchType::parse), - ))(input)?; - - Ok((input, Self { prefix, ty })) - } -} - -//------------ IpAddress ---------------------------------------------------- - -// IpAddress ::= IpV4Address | IpV6Address - #[derive(Clone, Debug)] pub enum IpAddress { Ipv4(Ipv4Addr), Ipv6(Ipv6Addr), } -impl IpAddress { - pub fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, res) = alt(( - map(Ipv4Addr::parse, IpAddress::Ipv4), - map(Ipv6Addr::parse, IpAddress::Ipv6), - ))(input)?; - Ok((input, res)) - } -} - impl std::fmt::Display for IpAddress { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { @@ -3339,137 +1098,27 @@ impl std::fmt::Display for IpAddress { } } -//------------ Ipv4Addr ----------------------------------------------------- - -// Ipv4Addr ::= #[derive(Clone, Debug)] pub struct Ipv4Addr(pub std::net::Ipv4Addr); -impl Ipv4Addr { - fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, ip_addr_str) = context( - "IPv4 Literal", - recognize(tuple(( - terminated(digit1, char('.')), - terminated(digit1, char('.')), - terminated(digit1, char('.')), - take_while1(|ch: char| ch.is_dec_digit()), - not(char(':')), - ))), - )(input)?; - - let addr = - ip_addr_str.parse::().map_err(|_| { - nom::Err::Error(VerboseError::from_error_kind( - input, - ErrorKind::Digit, - )) - })?; - - Ok((input, Self(addr))) - } -} - -//------------ Ipv6Addr ----------------------------------------------------- - -// Ipv6Addr ::= - #[derive(Clone, Debug)] pub struct Ipv6Addr(pub std::net::Ipv6Addr); -impl Ipv6Addr { - fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, ip_addr_str) = context( - "IPv6 Literal", - recognize(tuple(( - terminated(hex_digit0, char(':')), - opt(terminated(hex_digit0, char(':'))), - opt(terminated(hex_digit0, char(':'))), - opt(terminated(hex_digit0, char(':'))), - opt(terminated(hex_digit0, char(':'))), - opt(terminated(hex_digit0, char(':'))), - opt(terminated(hex_digit0, char(':'))), - hex_digit0, - not(char('.')), - ))), - )(input)?; - - let addr = - ip_addr_str.parse::().map_err(|_| { - nom::Err::Error(VerboseError::from_error_kind( - input, - ErrorKind::Digit, - )) - })?; - - Ok((input, Self(addr))) - } -} - -//------------ Prefix ------------------------------------------------------- - -// Prefix ::= IpAddress '/' PrefixLength - #[derive(Clone, Debug)] pub struct Prefix { pub addr: IpAddress, pub len: PrefixLength, } -impl Prefix { - fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, (addr, len)) = tuple(( - opt_ws(IpAddress::parse), - opt_ws(PrefixLength::parse), - ))(input)?; - - Ok((input, Prefix { addr, len })) - } -} - -//------------ PrefixLength ------------------------------------------------- - -// PrefixLength ::= '/' - #[derive(Clone, Debug)] pub struct PrefixLength(pub u8); -impl PrefixLength { - fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, length) = preceded(char('/'), opt_ws(digit1))(input)?; - - let length = length.parse::().map_err(|_| { - nom::Err::Error(VerboseError::from_error_kind( - input, - ErrorKind::Digit, - )) - })?; - - Ok((input, Self(length))) - } -} - -//------------ PrefixLengthRange -------------------------------------------- - -// PrefixLengthRange ::= PrefixLength '-' PrefixLength - #[derive(Clone, Debug)] pub struct PrefixLengthRange { pub start: PrefixLength, pub end: PrefixLength, } -impl PrefixLengthRange { - fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> { - let (input, (start, end)) = tuple(( - opt_ws(PrefixLength::parse), - preceded(char('-'), opt_ws(PrefixLength::parse)), - ))(input)?; - - Ok((input, Self { start, end })) - } -} - //------------ ShortString --------------------------------------------------- #[derive(Clone)] diff --git a/src/lib.rs b/src/lib.rs index f42d11c4..0421dce0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,6 @@ mod attr_change_set; pub mod blocks; pub mod compiler; pub mod eval; -mod parse_string; pub mod parser; mod symbols; pub mod token; diff --git a/src/parse_string.rs b/src/parse_string.rs deleted file mode 100644 index 2ffa0e06..00000000 --- a/src/parse_string.rs +++ /dev/null @@ -1,163 +0,0 @@ -use nom::branch::alt; -use nom::bytes::streaming::{is_not, take_while_m_n}; -use nom::character::streaming::{char, multispace1}; -use nom::combinator::{map, map_opt, map_res, value, verify}; -use nom::error::{FromExternalError, ParseError}; -use nom::multi::fold_many0; -// use nom::multi::fold; -use nom::sequence::{delimited, preceded}; -use nom::IResult; - -// Taken from: -// https://github.com/rust-bakery/nom/blob/main/examples/string.rs - -// parser combinators are constructed from the bottom up: -// first we write parsers for the smallest elements (escaped characters), -// then combine them into larger parsers. - -/// Parse a unicode sequence, of the form u{XXXX}, where XXXX is 1 to 6 -/// hexadecimal numerals. We will combine this later with parse_escaped_char -/// to parse sequences like \u{00AC}. -fn parse_unicode<'a, E>(input: &'a str) -> IResult<&'a str, char, E> -where - E: ParseError<&'a str> - + FromExternalError<&'a str, std::num::ParseIntError>, -{ - // `take_while_m_n` parses between `m` and `n` bytes (inclusive) that match - // a predicate. `parse_hex` here parses between 1 and 6 hexadecimal numerals. - let parse_hex = take_while_m_n(1, 6, |c: char| c.is_ascii_hexdigit()); - - // `preceded` takes a prefix parser, and if it succeeds, returns the result - // of the body parser. In this case, it parses u{XXXX}. - let parse_delimited_hex = preceded( - char('u'), - // `delimited` is like `preceded`, but it parses both a prefix and a suffix. - // It returns the result of the middle parser. In this case, it parses - // {XXXX}, where XXXX is 1 to 6 hex numerals, and returns XXXX - delimited(char('{'), parse_hex, char('}')), - ); - - // `map_res` takes the result of a parser and applies a function that returns - // a Result. In this case we take the hex bytes from parse_hex and attempt to - // convert them to a u32. - let parse_u32 = - map_res(parse_delimited_hex, move |hex| u32::from_str_radix(hex, 16)); - - // map_opt is like map_res, but it takes an Option instead of a Result. If - // the function returns None, map_opt returns an error. In this case, because - // not all u32 values are valid unicode code points, we have to fallibly - // convert to char with from_u32. - map_opt(parse_u32, std::char::from_u32)(input) -} - -/// Parse an escaped character: \n, \t, \r, \u{00AC}, etc. -fn parse_escaped_char<'a, E>(input: &'a str) -> IResult<&'a str, char, E> -where - E: ParseError<&'a str> - + FromExternalError<&'a str, std::num::ParseIntError>, -{ - preceded( - char('\\'), - // `alt` tries each parser in sequence, returning the result of - // the first successful match - alt(( - parse_unicode, - // The `value` parser returns a fixed value (the first argument) if its - // parser (the second argument) succeeds. In these cases, it looks for - // the marker characters (n, r, t, etc) and returns the matching - // character (\n, \r, \t, etc). - value('\n', char('n')), - value('\r', char('r')), - value('\t', char('t')), - value('\u{08}', char('b')), - value('\u{0C}', char('f')), - value('\\', char('\\')), - value('/', char('/')), - value('"', char('"')), - )), - )(input) -} - -/// Parse a backslash, followed by any amount of whitespace. This is used later -/// to discard any escaped whitespace. -fn parse_escaped_whitespace<'a, E: ParseError<&'a str>>( - input: &'a str, -) -> IResult<&'a str, &'a str, E> { - preceded(char('\\'), multispace1)(input) -} - -/// Parse a non-empty block of text that doesn't include \ or " -fn parse_literal<'a, E: ParseError<&'a str>>( - input: &'a str, -) -> IResult<&'a str, &'a str, E> { - // `is_not` parses a string of 0 or more characters that aren't one of the - // given characters. - let not_quote_slash = is_not("\"\\"); - - // `verify` runs a parser, then runs a verification function on the output of - // the parser. The verification function accepts out output only if it - // returns true. In this case, we want to ensure that the output of is_not - // is non-empty. - verify(not_quote_slash, |s: &str| !s.is_empty())(input) -} - -/// A string fragment contains a fragment of a string being parsed: either -/// a non-empty Literal (a series of non-escaped characters), a single -/// parsed escaped character, or a block of escaped whitespace. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum StringFragment<'a> { - Literal(&'a str), - EscapedChar(char), - EscapedWS, -} - -/// Combine parse_literal, parse_escaped_whitespace, and parse_escaped_char -/// into a StringFragment. -fn parse_fragment<'a, E>( - input: &'a str, -) -> IResult<&'a str, StringFragment<'a>, E> -where - E: ParseError<&'a str> - + FromExternalError<&'a str, std::num::ParseIntError>, -{ - alt(( - // The `map` combinator runs a parser, then applies a function to the output - // of that parser. - map(parse_literal, StringFragment::Literal), - map(parse_escaped_char, StringFragment::EscapedChar), - value(StringFragment::EscapedWS, parse_escaped_whitespace), - ))(input) -} - -/// Parse a string. Use a loop of parse_fragment and push all of the fragments -/// into an output string. -pub fn parse_string<'a, E>(input: &'a str) -> IResult<&'a str, String, E> -where - E: ParseError<&'a str> - + FromExternalError<&'a str, std::num::ParseIntError>, -{ - // fold is the equivalent of iterator::fold. It runs a parser in a loop, - // and for each output value, calls a folding function on each output value. - let build_string = fold_many0( - // Our parser function– parses a single string fragment - parse_fragment, - // Our init value, an empty string - String::new, - // Our folding function. For each fragment, append the fragment to the - // string. - |mut string, fragment| { - match fragment { - StringFragment::Literal(s) => string.push_str(s), - StringFragment::EscapedChar(c) => string.push(c), - StringFragment::EscapedWS => {} - } - string - }, - ); - - // Finally, parse the string. Note that, if `build_string` could accept a raw - // " character, the closing delimiter " would never match. When using - // `delimited` with a looping parser (like fold), be sure that the - // loop won't accidentally match your closing delimiter! - delimited(char('"'), build_string, char('"'))(input) -} diff --git a/src/parser/filter_map.rs b/src/parser/filter_map.rs index 39732d9f..09aaa586 100644 --- a/src/parser/filter_map.rs +++ b/src/parser/filter_map.rs @@ -213,7 +213,7 @@ impl<'source> Parser<'source> { /// | BooleanExpr '&&' BooleanExpr /// | BooleanExpr /// ``` - fn logical_expr(&mut self) -> ParseResult { + pub(super) fn logical_expr(&mut self) -> ParseResult { if self.accept_optional(Token::Bang)?.is_some() { let expr = self.boolean_expr()?; return Ok(LogicalExpr::NotExpr(NotExpr { expr })); @@ -342,7 +342,7 @@ impl<'source> Parser<'source> { }) } - fn action(&mut self) -> ParseResult { + pub(super) fn action(&mut self) -> ParseResult { self.accept_required(Token::Action)?; let ident = self.identifier()?; let with_kv = self.with_statement()?; diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 10c94692..703ba215 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -19,12 +19,18 @@ use std::iter::Peekable; mod filter_map; mod rib_like; +#[cfg(test)] +mod test_expressions; +// #[cfg(test)] +// mod test_sections; + type ParseResult = Result; #[derive(Clone, Debug, Diagnostic)] pub enum ParseError { EmptyInput, EndOfInput, + FailedToParseEntireInput, InvalidToken(#[label("invalid token")] Span), /// Dummy variant where more precise messages should be made /// @@ -51,6 +57,9 @@ impl std::fmt::Display for ParseError { match self { Self::EmptyInput => write!(f, "input was empty"), Self::EndOfInput => write!(f, "unexpected end of input"), + Self::FailedToParseEntireInput => { + write!(f, "failed to parse entire input") + } Self::InvalidToken(_) => write!(f, "invalid token"), Self::Todo(n) => write!(f, "add a nice message here {n}"), Self::Expected { expected, got, .. } => { @@ -176,10 +185,21 @@ impl<'source> Parser<'source> { /// # Parsing complex expressions impl<'source> Parser<'source> { pub fn parse(input: &'source str) -> ParseResult { - Self { + Self::run_parser(Self::tree, input) + } + + fn run_parser( + mut parser: impl FnMut(&mut Self) -> ParseResult, + input: &'source str, + ) -> ParseResult { + let mut p = Self { lexer: Lexer::new(input).spanned().peekable(), + }; + let out = parser(&mut p)?; + if p.lexer.next().is_some() { + return Err(ParseError::FailedToParseEntireInput); } - .tree() + Ok(out) } fn tree(&mut self) -> ParseResult { diff --git a/src/parser/test_expressions.rs b/src/parser/test_expressions.rs new file mode 100644 index 00000000..c6ac5976 --- /dev/null +++ b/src/parser/test_expressions.rs @@ -0,0 +1,118 @@ +use crate::parser::Parser; + +//------------ Logical Expressions parsing ---------------------------------- + +#[test] +fn test_logical_expr_1() { + let r = Parser::run_parser( + Parser::logical_expr, + "( blaffer.waf().contains(my_set) ) || ( blaffer.blaf() < bop() )", + ); + assert!(r.is_ok()); +} + +#[test] +fn test_logical_expr_2() { + let r = Parser::run_parser( + Parser::logical_expr, + r#"(0.0.0.0/0 prefix-length-range /12-/16)"#, + ); + assert!(r.is_ok()); +} + +#[test] +fn test_logical_expr_3() { + let r = Parser::run_parser( + Parser::logical_expr, + r#"blaffer.blaf.contains(something,"somewhat") > blaf()"#, + ); + assert!(r.is_ok()); +} + +#[test] +fn test_logical_expr_4() { + let r = Parser::run_parser( + Parser::logical_expr, + r#"( my_set.contains(bla.bla()) ) || ( my_other_set.contains(bla.bla()) )"#, + ); + assert!(r.is_ok()); +} + +#[test] +fn test_logical_expr_5() { + let r = Parser::run_parser( + Parser::logical_expr, + "(found_prefix.prefix.exists() && found_prefix.prefix.exists()) || route_in_table" + ); + assert!(r.is_ok()); +} + +//------------ Compute Expressions parsing ---------------------------------- + +#[test] +fn test_compute_expr_1() { + let cm = Parser::run_parser( + Parser::value_expr, + r#"source_asns.contains("asn", route.as_path.origin)"#, + ); + assert!(cm.is_ok()); +} + +#[test] +fn test_compute_expr_2() { + let r = + Parser::run_parser(Parser::value_expr, "a.b.c.d(x,y,z).e.f(o.p()).g"); + assert!(r.is_ok()); +} + +#[test] +fn test_compute_expr_3() { + let r = Parser::run_parser(Parser::value_expr, "send-to(a, b)"); + assert!(r.is_ok()); +} + +#[test] +fn test_compute_expr_4() { + let r = Parser::run_parser(Parser::value_expr, "global_record.field"); + assert!(r.is_ok()); +} + +#[test] +fn test_compute_expr_5() { + let r = Parser::run_parser(Parser::value_expr, "pph_asn.asn.set(AS200)"); + assert!(r.is_ok()); +} + +//------------ Other Expressions -------------------------------------------- + +#[test] +fn test_value_expr() { + let mm = Parser::run_parser(Parser::value_expr, r###"globlaf(bla)"###); + assert!(mm.is_ok()); +} + +//------------ Prefix Match Expressions ------------------------------------- + +// This SHOULD be syntactic sugar for, but they're not correct right now. +// They're supposed to be boolean expressions, not expressions that yield +// prefixes. TODO + +#[test] +fn test_prefix_expr_1() { + let s = Parser::run_parser(Parser::value_expr, r###"129.23.0.0/16 upto /18"###); + assert!(s.is_ok()); +} + +#[test] +fn test_prefix_expr_2() { + let s = Parser::run_parser(Parser::value_expr, r###"2001::1/48 orlonger"###); + assert!(s.is_ok()); +} + +#[test] +fn test_prefix_expr_3() { + let s = Parser::run_parser(Parser::value_expr, + r###"0.0.0.0/0 prefix-length-range /24-/32"###, + ); + assert!(s.is_ok()); +} diff --git a/tests/sections.rs b/src/parser/test_sections.rs similarity index 52% rename from tests/sections.rs rename to src/parser/test_sections.rs index 2a7b200d..f843672d 100644 --- a/tests/sections.rs +++ b/src/parser/test_sections.rs @@ -10,13 +10,17 @@ fn test_logical_expr_1() { send-to(a,b); }"###, ); + assert!(r.is_ok()); +} - if r.is_err() { - println!("{:?}", r); - } +#[test] +fn test_logical_expr_1() { + let r = ActionSection::parse( + r###" + action my-action { + send_to(a,b); + pph_asn.asn.set(AS200); + }"###, + ); assert!(r.is_ok()); - if let Ok(expr) = r { - println!("{:?}", expr.1); - assert_eq!(expr.0, ""); - } } diff --git a/src/types/builtin/tests.rs b/src/types/builtin/tests.rs index 3ac5f27f..3e6af3cd 100644 --- a/src/types/builtin/tests.rs +++ b/src/types/builtin/tests.rs @@ -1,11 +1,9 @@ #[cfg(test)] mod route { - use std::net::IpAddr; - use super::super::{ IntegerLiteral, PrefixLength, StringLiteral, }; - use crate::ast::{AsnLiteral, IpAddress}; + use crate::ast::AsnLiteral; use crate::types::builtin::BuiltinTypeValue; use crate::types::typedef::TypeDef; use crate::types::typevalue::TypeValue; @@ -1080,37 +1078,6 @@ src_ty.clone().test_type_conversion(arg_ty)"] test_consume_method_on_type_value(test_value, "set", res).unwrap(); } - - //------------ Test: IpAddr ---------------------------------------------- - - #[test] - fn test_ip_address_literal_1() -> Result<(), CompileError> { - init(); - - let test_value = IpAddr::from(&IpAddress::parse("24.0.2.0").unwrap().1); - let res = std::net::IpAddr::from([24,0,2,0]); - mk_converted_type_value(test_value, res) - } - - #[test] - fn test_ip_address_literal_2() -> Result<(), CompileError> { - init(); - - let test_value = IpAddr::from(&IpAddress::parse("24.0.2.0").unwrap().1); - let res = StringLiteral("24.0.2.0".into()); - mk_converted_type_value(test_value, res) - } - - #[test] - fn test_ip_address_literal_3() -> Result<(), CompileError> { - init(); - - let test_value = IpAddr::from(&IpAddress::parse("2001::ffff").unwrap().1); - let res = std::net::IpAddr::from([0x2001,0x0,0x0,0x0,0x0,0x0,0x0,0xffff]); - - assert_eq!(TypeValue::from(test_value).into_builtin()?, BuiltinTypeValue::IpAddr(res)); - mk_converted_type_value(test_value, res) - } //-------- Test: Asn ----------------------------------------------------- diff --git a/tests/compare.rs b/tests/compare.rs index 7ad8034d..f22cddea 100644 --- a/tests/compare.rs +++ b/tests/compare.rs @@ -1,5 +1,5 @@ use roto::ast::AcceptReject; -use roto::compiler::{compile, Compiler}; +use roto::compiler::Compiler; use roto::blocks::Scope::{self, FilterMap}; use roto::types::collections::Record; diff --git a/tests/data_sources.rs b/tests/data_sources.rs index 94e62f87..2db2a22c 100644 --- a/tests/data_sources.rs +++ b/tests/data_sources.rs @@ -1,5 +1,5 @@ use log::trace; -use roto::compiler::{compile, Compiler}; +use roto::compiler::Compiler; use roto::blocks::Scope; use roto::types::collections::{ElementTypeValue, List, Record}; diff --git a/tests/end_to_end.rs b/tests/end_to_end.rs index 1521ef8c..6d1a8cff 100644 --- a/tests/end_to_end.rs +++ b/tests/end_to_end.rs @@ -1,7 +1,7 @@ use log::trace; use roto::ast::AcceptReject; -use roto::compiler::{compile, Compiler}; +use roto::compiler::Compiler; use roto::blocks::Scope::{self, Filter, FilterMap}; use roto::types::collections::{ElementTypeValue, List, Record}; use roto::types::datasources::{DataSource, Rib}; diff --git a/tests/expressions.rs b/tests/expressions.rs deleted file mode 100644 index 7080b6a1..00000000 --- a/tests/expressions.rs +++ /dev/null @@ -1,184 +0,0 @@ -use roto::ast::*; - -//------------ Logical Expressions parsing ---------------------------------- - -#[test] -fn test_logical_expr_1() { - let r = LogicalExpr::parse( - "( blaffer.waf().contains(my_set) ) || ( blaffer.blaf() < bop() )", - ); - assert!(r.is_ok()); - if let Ok(expr) = r { - assert_eq!(expr.0, ""); - } -} - -#[test] -fn test_logical_expr_2() { - let r = LogicalExpr::parse(r#"(0.0.0.0/0 prefix-length-range /12-/16)"#); - println!("{:#?}", r); - assert!(r.is_ok()); - if let Ok(expr) = r { - assert_eq!(expr.0, ""); - } -} - -#[test] -fn test_logical_expr_3() { - let r = LogicalExpr::parse( - r#"blaffer.blaf.contains(something,"somewhat") > blaf()"#, - ); - println!("{:#?}", r); - assert!(r.is_ok()); - if let Ok(expr) = r { - assert_eq!(expr.0, ""); - } -} - -#[test] -fn test_logical_expr_4() { - let r = LogicalExpr::parse( - r#"( my_set.contains(bla.bla()) ) || ( my_other_set.contains(bla.bla()) )"#, - ); - println!("{:#?}", r); - assert!(r.is_ok()); - if let Ok(expr) = r { - assert_eq!(expr.0, ""); - } -} - -#[test] -fn test_logical_expr_5() { - let r = LogicalExpr::parse( - "(found_prefix.prefix.exists() && found_prefix.prefix.exists()) || route_in_table" - ); - println!("{:#?}", r); - assert!(r.is_ok()); - if let Ok(expr) = r { - assert_eq!(expr.0, ""); - } -} - -//------------ Compute Expressions parsing ---------------------------------- - -#[test] -fn test_compute_expr_1() { - let cm = ComputeExpr::parse( - r#"source_asns.contains("asn", route.as_path.origin)"#, - ); - - println!("{}, {:#?}", cm.is_ok(), cm); - assert!(cm.is_ok()); - if let Ok(expr) = cm { - assert_eq!(expr.0, ""); - } -} - -#[test] -fn test_compute_expr_2() { - let r = ComputeExpr::parse("a.b.c.d(x,y,z).e.f(o.p()).g"); - assert!(r.is_ok()); - if let Ok(expr) = r { - assert_eq!(expr.0, ""); - } -} - -#[test] -fn test_compute_expr_3() { - let r = OptionalGlobalComputeExpr::parse("send-to(a, b)"); - assert!(r.is_ok()); - if let Ok(expr) = r { - assert_eq!(expr.0, ""); - } -} - -#[test] -fn test_compute_expr_4() { - let r = OptionalGlobalComputeExpr::parse("global_record.field"); - assert!(r.is_ok()); - if let Ok(expr) = r { - assert_eq!(expr.0, ""); - } -} - -#[test] -fn test_compute_expr_5() { - let r = OptionalGlobalComputeExpr::parse("pph_asn.asn.set(AS200)"); - assert!(r.is_ok()); - if let Ok(expr) = r { - assert_eq!(expr.0, ""); - } -} - -#[test] -fn test_action_body() { - let r = ActionSectionBody::parse( - r###" - send_to(a,b); - pph_asn.asn.set(AS200);"###, - ); - assert!(r.is_ok()); - if let Ok(expr) = r { - assert_eq!(expr.0, ""); - } -} - -//------------ Other Expressions -------------------------------------------- - -#[test] -fn test_value_expr() { - let mm = ValueExpr::parse(r###"globlaf(bla)"###); - println!("{}, {:#?}", mm.is_ok(), mm); - assert!(mm.is_ok()); - if let Ok(expr) = mm { - assert_eq!(expr.0, ""); - } -} - -#[test] -fn test_bytestring_literal_expr() { - let r = ByteStringLiteral::parse("0xZZZZ_AE9"); - println!("{:#?}", r); - assert!(r.is_err()); - if let Ok(expr) = r { - assert_eq!(expr.0, ""); - } -} - -//------------ Prefix Match Expressions ------------------------------------- - -// This SHOULD be syntactic sugar for, but they're not correct right now. -// They're supposed to be boolean expressions, not expressions that yield -// prefixes. TODO - -#[test] -fn test_prefix_expr_1() { - let s = PrefixMatchExpr::parse(r###"129.23.0.0/16 upto /18"###); - println!("{:#?}", s); - assert!(s.is_ok()); - if let Ok(expr) = s { - assert_eq!(expr.0, ""); - } -} - -#[test] -fn test_prefix_expr_2() { - let s = PrefixMatchExpr::parse(r###"2001::1/48 orlonger"###); - println!("{:#?}", s); - assert!(s.is_ok()); - if let Ok(expr) = s { - assert_eq!(expr.0, ""); - } -} - -#[test] -fn test_prefix_expr_3() { - let s = PrefixMatchExpr::parse( - r###"0.0.0.0/0 prefix-length-range /24-/32"###, - ); - println!("{:#?}", s); - assert!(s.is_ok()); - if let Ok(expr) = s { - assert_eq!(expr.0, ""); - } -} diff --git a/tests/helpers.rs b/tests/helpers.rs index ef36ac4d..bd55959e 100644 --- a/tests/helpers.rs +++ b/tests/helpers.rs @@ -1,5 +1,4 @@ use log::trace; -use nom::error::convert_error; use roto::{ blocks::Scope, compiler::{CompileError, Compiler}, From 7812aa4c35e1d9bc9bef221644982f88e8938a54 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Thu, 29 Feb 2024 10:29:04 +0100 Subject: [PATCH 12/24] remove the temporary main.rs --- src/main.rs | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 src/main.rs diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 238667b4..00000000 --- a/src/main.rs +++ /dev/null @@ -1,22 +0,0 @@ -fn main() { - let mut args = std::env::args(); - args.next(); - - let cmd = args.next().expect("missing command"); - - match cmd.as_ref() { - "parse" => { - let file = args.next().expect("need a file to parse"); - parse(&file); - } - _ => panic!("unrecognized command: {cmd}"), - }; -} - -fn parse(file: &str) { - let contents = std::fs::read_to_string(file).unwrap(); - match roto::parser::Parser::parse(&contents) { - Ok(contents) => println!("{contents:?}"), - Err(err) => println!("{err}"), - }; -} From d79c40df6e7f9b75cd8de2beab13696c9e8d27b8 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Thu, 29 Feb 2024 11:29:58 +0100 Subject: [PATCH 13/24] fixup! remove old parser and port the last tests --- src/parser/mod.rs | 4 ++-- src/parser/test_sections.rs | 19 +++++++++++-------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 703ba215..7bd855ba 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -21,8 +21,8 @@ mod rib_like; #[cfg(test)] mod test_expressions; -// #[cfg(test)] -// mod test_sections; +#[cfg(test)] +mod test_sections; type ParseResult = Result; diff --git a/src/parser/test_sections.rs b/src/parser/test_sections.rs index f843672d..779126af 100644 --- a/src/parser/test_sections.rs +++ b/src/parser/test_sections.rs @@ -1,10 +1,11 @@ -use roto::ast::*; +use crate::parser::Parser; //------------ Logical Expressions parsing ---------------------------------- #[test] fn test_logical_expr_1() { - let r = ActionSection::parse( + let r = Parser::run_parser( + Parser::action, r###" action my-action { send-to(a,b); @@ -14,13 +15,15 @@ fn test_logical_expr_1() { } #[test] -fn test_logical_expr_1() { - let r = ActionSection::parse( +fn test_logical_expr_2() { + let r = Parser::run_parser( + Parser::action, r###" - action my-action { - send_to(a,b); - pph_asn.asn.set(AS200); - }"###, + action my-action { + send_to(a,b); + pph_asn.asn.set(AS200); + } + "###, ); assert!(r.is_ok()); } From b900c63307b6ebcd1f9282968b722e0d35798a21 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Thu, 29 Feb 2024 12:10:23 +0100 Subject: [PATCH 14/24] restructure parser files --- src/parser/filter_map.rs | 330 ++++++++++++++++++- src/parser/mod.rs | 666 +-------------------------------------- src/parser/value.rs | 340 ++++++++++++++++++++ 3 files changed, 667 insertions(+), 669 deletions(-) create mode 100644 src/parser/value.rs diff --git a/src/parser/filter_map.rs b/src/parser/filter_map.rs index 09aaa586..f58c36f4 100644 --- a/src/parser/filter_map.rs +++ b/src/parser/filter_map.rs @@ -1,11 +1,6 @@ use crate::{ ast::{ - AccessExpr, AccessReceiver, ActionSection, ActionSectionBody, - AndExpr, ApplySection, BooleanExpr, CompareArg, CompareExpr, - CompareOp, ComputeExpr, Define, FilterMap, FilterMapBody, - FilterMapExpr, FilterType, GroupedLogicalExpr, ListCompareExpr, - LogicalExpr, MatchOperator, NotExpr, OrExpr, TermBody, - TermPatternMatchArm, TermScope, TermSection, ValueExpr, + AcceptReject, AccessExpr, AccessReceiver, ActionCallExpr, ActionSection, ActionSectionBody, AndExpr, ApplyBody, ApplyScope, ApplySection, BooleanExpr, CompareArg, CompareExpr, CompareOp, ComputeExpr, Define, DefineBody, FilterMap, FilterMapBody, FilterMapExpr, FilterMatchActionExpr, FilterType, GroupedLogicalExpr, ListCompareExpr, LogicalExpr, MatchActionExpr, MatchOperator, NotExpr, OrExpr, PatternMatchActionArm, PatternMatchActionExpr, RxTxType, TermBody, TermCallExpr, TermPatternMatchArm, TermScope, TermSection, TypeIdentField, ValueExpr }, token::Token, }; @@ -97,6 +92,282 @@ impl<'source> Parser<'source> { }) } + /// Parse the body of a define section + /// + /// ```ebnf + /// DefineBody ::= '{' RxTxType Use? Assignment* '}' + /// + /// RxTxType ::= ( 'rx_tx' TypeIdentField ';' + /// | 'rx' TypeIdentField ';' 'tx' TypeIdentField ';' + /// | 'rx' TypeIdentField ) + /// + /// Use ::= 'use' Identifier Identifier + /// + /// Assignment ::= Identifier '=' ValueExpr ';' + /// ``` + fn define_body(&mut self) -> ParseResult { + self.accept_required(Token::CurlyLeft)?; + + let rx_tx_type = match self.next()?.0 { + Token::RxTx => { + let field = self.type_ident_field()?; + self.accept_required(Token::SemiColon)?; + RxTxType::PassThrough(field) + } + Token::Rx => { + let rx_field = self.type_ident_field()?; + self.accept_required(Token::SemiColon)?; + if self.accept_optional(Token::Tx)?.is_some() { + let tx_field = self.type_ident_field()?; + self.accept_required(Token::SemiColon)?; + RxTxType::Split(rx_field, tx_field) + } else { + RxTxType::RxOnly(rx_field) + } + } + _ => return Err(ParseError::Todo(9)), + }; + + let mut use_ext_data = Vec::new(); + while self.accept_optional(Token::Use)?.is_some() { + use_ext_data.push((self.identifier()?, self.identifier()?)); + self.accept_required(Token::SemiColon)?; + } + + let mut assignments = Vec::new(); + while self.accept_optional(Token::CurlyRight)?.is_none() { + let id = self.identifier()?; + self.accept_required(Token::Eq)?; + let value = self.value_expr()?; + self.accept_required(Token::SemiColon)?; + assignments.push((id, value)); + } + + Ok(DefineBody { + rx_tx_type, + use_ext_data, + assignments, + }) + } + + /// Parse the body of an apply section + /// + /// ```ebnf + /// ApplyBody ::= ApplyScope* (AcceptReject ';')? + /// ``` + fn apply_body(&mut self) -> ParseResult { + self.accept_required(Token::CurlyLeft)?; + let mut scopes = Vec::new(); + + while !(self.peek_is(Token::Return) + || self.peek_is(Token::Accept) + || self.peek_is(Token::Reject) + || self.peek_is(Token::CurlyRight)) + { + scopes.push(self.apply_scope()?); + } + + let accept_reject = self.accept_reject()?; + self.accept_required(Token::CurlyRight)?; + Ok(ApplyBody { + scopes, + accept_reject, + }) + } + + /// Parse a scope of the body of apply + /// + /// ```ebnf + /// ApplyScope ::= 'filter' 'match' ValueExpr + /// 'not'? 'matching' + /// Actions ';' + /// + /// Actions ::= '{' (ValueExpr ';')* ( AcceptReject ';' )? '}' + /// ``` + fn apply_scope(&mut self) -> ParseResult { + if self.peek_is(Token::Match) { + return self.apply_match(); + } + + self.accept_required(Token::Filter)?; + + // This is not exactly self.match_operator because match ... with is + // not allowed. + let operator = if self.accept_optional(Token::Match)?.is_some() { + MatchOperator::Match + } else if self.accept_optional(Token::ExactlyOne)?.is_some() { + MatchOperator::ExactlyOne + } else if self.accept_optional(Token::Some)?.is_some() { + MatchOperator::Some + } else if self.accept_optional(Token::All)?.is_some() { + MatchOperator::All + } else { + return Err(ParseError::Todo(20)); + }; + + let filter_ident = self.value_expr()?; + let negate = self.accept_optional(Token::Not)?.is_some(); + self.accept_required(Token::Matching)?; + self.accept_required(Token::CurlyLeft)?; + + let mut actions = Vec::new(); + while self.accept_optional(Token::CurlyRight)?.is_none() { + if let Some(accept_reject) = self.accept_reject()? { + self.accept_required(Token::CurlyRight)?; + actions.push((None, Some(accept_reject))); + break; + } + + let val = self.value_expr()?; + self.accept_required(Token::SemiColon)?; + actions.push((Some(val), None)); + } + + self.accept_required(Token::SemiColon)?; + + Ok(ApplyScope { + scope: None, + match_action: MatchActionExpr::FilterMatchAction( + FilterMatchActionExpr { + operator, + negate, + actions, + filter_ident, + }, + ), + }) + } + + fn apply_match(&mut self) -> ParseResult { + let operator = self.match_operator()?; + let mut match_arms = Vec::new(); + self.accept_required(Token::CurlyLeft)?; + while self.accept_optional(Token::CurlyRight)?.is_none() { + let variant_id = self.identifier()?; + let data_field = + if self.accept_optional(Token::RoundLeft)?.is_some() { + let id = self.identifier()?; + self.accept_required(Token::RoundRight)?; + Some(id) + } else { + None + }; + + let guard = if self.accept_optional(Token::Pipe)?.is_some() { + let term_id = self.identifier()?; + let args = if self.peek_is(Token::RoundLeft) { + Some(self.arg_expr_list()?) + } else { + None + }; + Some(TermCallExpr { term_id, args }) + } else { + None + }; + + self.accept_required(Token::Arrow)?; + + let mut actions = Vec::new(); + if self.accept_optional(Token::CurlyLeft)?.is_some() { + while self.accept_optional(Token::CurlyRight)?.is_none() { + if let Some(ar) = self.accept_reject()? { + self.accept_required(Token::CurlyRight)?; + actions.push((None, Some(ar))); + break; + } + actions.push((Some(self.action_call_expr()?), None)); + self.accept_required(Token::SemiColon)?; + } + self.accept_optional(Token::Comma)?; + } else { + let expr = self.action_call_expr()?; + self.accept_required(Token::Comma)?; + actions.push((Some(expr), None)); + } + + match_arms.push(PatternMatchActionArm { + variant_id, + data_field, + guard, + actions, + }); + } + Ok(ApplyScope { + scope: None, + match_action: MatchActionExpr::PatternMatchAction( + PatternMatchActionExpr { + operator, + match_arms, + }, + ), + }) + } + + fn action_call_expr(&mut self) -> ParseResult { + let action_id = self.identifier()?; + let args = if self.peek_is(Token::RoundLeft) { + Some(self.arg_expr_list()?) + } else { + None + }; + Ok(ActionCallExpr { action_id, args }) + } + + /// Parse a statement returning accept or reject + /// + /// ```ebnf + /// AcceptReject ::= ('return'? ( 'accept' | 'reject' ) ';')? + /// ``` + fn accept_reject(&mut self) -> ParseResult> { + if self.accept_optional(Token::Return)?.is_some() { + let value = match self.next()?.0 { + Token::Accept => AcceptReject::Accept, + Token::Reject => AcceptReject::Reject, + _ => return Err(ParseError::Todo(10)), + }; + self.accept_required(Token::SemiColon)?; + return Ok(Some(value)); + } + + if self.accept_optional(Token::Accept)?.is_some() { + self.accept_required(Token::SemiColon)?; + return Ok(Some(AcceptReject::Accept)); + } + + if self.accept_optional(Token::Reject)?.is_some() { + self.accept_required(Token::SemiColon)?; + return Ok(Some(AcceptReject::Reject)); + } + + Ok(None) + } + + /// Parse a match operator + /// + /// ```ebnf + /// MatchOperator ::= 'match' ( Identifier 'with' )? + /// | 'some' | 'exactly-one' | 'all' + /// ``` + fn match_operator(&mut self) -> ParseResult { + let op = match self.next()?.0 { + Token::Match => { + if matches!(self.peek(), Some(Token::Ident(_))) { + let ident = self.identifier()?; + self.accept_required(Token::With)?; + MatchOperator::MatchValueWith(ident) + } else { + MatchOperator::Match + } + } + Token::Some => MatchOperator::Some, + Token::ExactlyOne => MatchOperator::ExactlyOne, + Token::All => MatchOperator::All, + _ => return Err(ParseError::Todo(11)), + }; + + Ok(op) + } + /// Parse a filter map expression, which is a term or an action /// /// ```ebnf @@ -302,7 +573,7 @@ impl<'source> Parser<'source> { /// Optionally parse a compare operator /// - /// This method returns option, because we are never sure that there is + /// This method returns [`Option`], because we are never sure that there is /// going to be a comparison operator. /// /// ```ebnf @@ -370,4 +641,49 @@ impl<'source> Parser<'source> { body: ActionSectionBody { expressions }, }) } + + /// Parse an optional for clause for filter-map, define and apply + /// + /// ```ebnf + /// For ::= ( 'for' TypeIdentField)? + /// ``` + fn for_statement(&mut self) -> ParseResult> { + if self.accept_optional(Token::For)?.is_some() { + Ok(Some(self.type_ident_field()?)) + } else { + Ok(None) + } + } + + /// Parse an optional with clause for filter-map, define and apply + /// + /// ```ebnf + /// With ::= ( 'with' TypeIdentField (',' TypeIdentField)*)? + /// ``` + fn with_statement(&mut self) -> ParseResult> { + let mut key_values = Vec::new(); + + if self.accept_optional(Token::With)?.is_none() { + return Ok(key_values); + } + + key_values.push(self.type_ident_field()?); + while self.accept_optional(Token::Comma)?.is_some() { + key_values.push(self.type_ident_field()?); + } + + Ok(key_values) + } + + /// Parse an identifier and a type identifier separated by a colon + /// + /// ```ebnf + /// TypeIdentField ::= Identifier ':' TypeIdentifier + /// ``` + fn type_ident_field(&mut self) -> ParseResult { + let field_name = self.identifier()?; + self.accept_required(Token::Colon)?; + let ty = self.type_identifier()?; + Ok(TypeIdentField { field_name, ty }) + } } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 7bd855ba..f49152c5 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -1,15 +1,6 @@ use crate::ast::{ - AcceptReject, AccessExpr, AccessReceiver, ActionCallExpr, - AnonymousRecordValueExpr, ApplyBody, ApplyScope, ArgExprList, AsnLiteral, - BooleanLiteral, ComputeExpr, DefineBody, ExtendedCommunityLiteral, - FieldAccessExpr, FilterMatchActionExpr, HexLiteral, Identifier, - IntegerLiteral, IpAddress, Ipv4Addr, Ipv6Addr, LargeCommunityLiteral, - ListValueExpr, LiteralAccessExpr, LiteralExpr, MatchActionExpr, - MatchOperator, MethodComputeExpr, PatternMatchActionArm, - PatternMatchActionExpr, Prefix, PrefixLength, PrefixLengthLiteral, - PrefixLengthRange, PrefixMatchExpr, PrefixMatchType, RootExpr, RxTxType, - StandardCommunityLiteral, StringLiteral, SyntaxTree, TermCallExpr, - TypeIdentField, TypeIdentifier, TypedRecordValueExpr, ValueExpr, + Identifier, + RootExpr, SyntaxTree, TypeIdentifier, }; use crate::token::Token; use logos::{Lexer, Span, SpannedIter}; @@ -18,6 +9,7 @@ use std::iter::Peekable; mod filter_map; mod rib_like; +mod value; #[cfg(test)] mod test_expressions; @@ -241,660 +233,10 @@ impl<'source> Parser<'source> { }; Ok(expr) } - - /// Parse the body of a define section - /// - /// ```ebnf - /// DefineBody ::= '{' RxTxType Use? Assignment* '}' - /// - /// RxTxType ::= ( 'rx_tx' TypeIdentField ';' - /// | 'rx' TypeIdentField ';' 'tx' TypeIdentField ';' - /// | 'rx' TypeIdentField ) - /// - /// Use ::= 'use' Identifier Identifier - /// - /// Assignment ::= Identifier '=' ValueExpr ';' - /// ``` - fn define_body(&mut self) -> ParseResult { - self.accept_required(Token::CurlyLeft)?; - - let rx_tx_type = match self.next()?.0 { - Token::RxTx => { - let field = self.type_ident_field()?; - self.accept_required(Token::SemiColon)?; - RxTxType::PassThrough(field) - } - Token::Rx => { - let rx_field = self.type_ident_field()?; - self.accept_required(Token::SemiColon)?; - if self.accept_optional(Token::Tx)?.is_some() { - let tx_field = self.type_ident_field()?; - self.accept_required(Token::SemiColon)?; - RxTxType::Split(rx_field, tx_field) - } else { - RxTxType::RxOnly(rx_field) - } - } - _ => return Err(ParseError::Todo(9)), - }; - - let mut use_ext_data = Vec::new(); - while self.accept_optional(Token::Use)?.is_some() { - use_ext_data.push((self.identifier()?, self.identifier()?)); - self.accept_required(Token::SemiColon)?; - } - - let mut assignments = Vec::new(); - while self.accept_optional(Token::CurlyRight)?.is_none() { - let id = self.identifier()?; - self.accept_required(Token::Eq)?; - let value = self.value_expr()?; - self.accept_required(Token::SemiColon)?; - assignments.push((id, value)); - } - - Ok(DefineBody { - rx_tx_type, - use_ext_data, - assignments, - }) - } - - /// Parse the body of an apply section - /// - /// ```ebnf - /// ApplyBody ::= ApplyScope* (AcceptReject ';')? - /// ``` - fn apply_body(&mut self) -> ParseResult { - self.accept_required(Token::CurlyLeft)?; - let mut scopes = Vec::new(); - - while !(self.peek_is(Token::Return) - || self.peek_is(Token::Accept) - || self.peek_is(Token::Reject) - || self.peek_is(Token::CurlyRight)) - { - scopes.push(self.apply_scope()?); - } - - let accept_reject = self.accept_reject()?; - self.accept_required(Token::CurlyRight)?; - Ok(ApplyBody { - scopes, - accept_reject, - }) - } - - /// Parse a scope of the body of apply - /// - /// ```ebnf - /// ApplyScope ::= 'filter' 'match' ValueExpr - /// 'not'? 'matching' - /// Actions ';' - /// - /// Actions ::= '{' (ValueExpr ';')* ( AcceptReject ';' )? '}' - /// ``` - fn apply_scope(&mut self) -> ParseResult { - if self.peek_is(Token::Match) { - return self.apply_match(); - } - - self.accept_required(Token::Filter)?; - - // This is not exactly self.match_operator because match ... with is - // not allowed. - let operator = if self.accept_optional(Token::Match)?.is_some() { - MatchOperator::Match - } else if self.accept_optional(Token::ExactlyOne)?.is_some() { - MatchOperator::ExactlyOne - } else if self.accept_optional(Token::Some)?.is_some() { - MatchOperator::Some - } else if self.accept_optional(Token::All)?.is_some() { - MatchOperator::All - } else { - return Err(ParseError::Todo(20)); - }; - - let filter_ident = self.value_expr()?; - let negate = self.accept_optional(Token::Not)?.is_some(); - self.accept_required(Token::Matching)?; - self.accept_required(Token::CurlyLeft)?; - - let mut actions = Vec::new(); - while self.accept_optional(Token::CurlyRight)?.is_none() { - if let Some(accept_reject) = self.accept_reject()? { - self.accept_required(Token::CurlyRight)?; - actions.push((None, Some(accept_reject))); - break; - } - - let val = self.value_expr()?; - self.accept_required(Token::SemiColon)?; - actions.push((Some(val), None)); - } - - self.accept_required(Token::SemiColon)?; - - Ok(ApplyScope { - scope: None, - match_action: MatchActionExpr::FilterMatchAction( - FilterMatchActionExpr { - operator, - negate, - actions, - filter_ident, - }, - ), - }) - } - - fn apply_match(&mut self) -> ParseResult { - let operator = self.match_operator()?; - let mut match_arms = Vec::new(); - self.accept_required(Token::CurlyLeft)?; - while self.accept_optional(Token::CurlyRight)?.is_none() { - let variant_id = self.identifier()?; - let data_field = - if self.accept_optional(Token::RoundLeft)?.is_some() { - let id = self.identifier()?; - self.accept_required(Token::RoundRight)?; - Some(id) - } else { - None - }; - - let guard = if self.accept_optional(Token::Pipe)?.is_some() { - let term_id = self.identifier()?; - let args = if self.peek_is(Token::RoundLeft) { - Some(self.arg_expr_list()?) - } else { - None - }; - Some(TermCallExpr { term_id, args }) - } else { - None - }; - - self.accept_required(Token::Arrow)?; - - let mut actions = Vec::new(); - if self.accept_optional(Token::CurlyLeft)?.is_some() { - while self.accept_optional(Token::CurlyRight)?.is_none() { - if let Some(ar) = self.accept_reject()? { - self.accept_required(Token::CurlyRight)?; - actions.push((None, Some(ar))); - break; - } - actions.push((Some(self.action_call_expr()?), None)); - self.accept_required(Token::SemiColon)?; - } - self.accept_optional(Token::Comma)?; - } else { - let expr = self.action_call_expr()?; - self.accept_required(Token::Comma)?; - actions.push((Some(expr), None)); - } - - match_arms.push(PatternMatchActionArm { - variant_id, - data_field, - guard, - actions, - }); - } - Ok(ApplyScope { - scope: None, - match_action: MatchActionExpr::PatternMatchAction( - PatternMatchActionExpr { - operator, - match_arms, - }, - ), - }) - } - - fn action_call_expr(&mut self) -> ParseResult { - let action_id = self.identifier()?; - let args = if self.peek_is(Token::RoundLeft) { - Some(self.arg_expr_list()?) - } else { - None - }; - Ok(ActionCallExpr { action_id, args }) - } - - /// Parse a statement returning accept or reject - /// - /// ```ebnf - /// AcceptReject ::= ('return'? ( 'accept' | 'reject' ) ';')? - /// ``` - fn accept_reject(&mut self) -> ParseResult> { - if self.accept_optional(Token::Return)?.is_some() { - let value = match self.next()?.0 { - Token::Accept => AcceptReject::Accept, - Token::Reject => AcceptReject::Reject, - _ => return Err(ParseError::Todo(10)), - }; - self.accept_required(Token::SemiColon)?; - return Ok(Some(value)); - } - - if self.accept_optional(Token::Accept)?.is_some() { - self.accept_required(Token::SemiColon)?; - return Ok(Some(AcceptReject::Accept)); - } - - if self.accept_optional(Token::Reject)?.is_some() { - self.accept_required(Token::SemiColon)?; - return Ok(Some(AcceptReject::Reject)); - } - - Ok(None) - } - - /// Parse a match operator - /// - /// ```ebnf - /// MatchOperator ::= 'match' ( Identifier 'with' )? - /// | 'some' | 'exactly-one' | 'all' - /// ``` - fn match_operator(&mut self) -> ParseResult { - let op = match self.next()?.0 { - Token::Match => { - if matches!(self.peek(), Some(Token::Ident(_))) { - let ident = self.identifier()?; - self.accept_required(Token::With)?; - MatchOperator::MatchValueWith(ident) - } else { - MatchOperator::Match - } - } - Token::Some => MatchOperator::Some, - Token::ExactlyOne => MatchOperator::ExactlyOne, - Token::All => MatchOperator::All, - _ => return Err(ParseError::Todo(11)), - }; - - Ok(op) - } - - /// Parse a value expr - /// - /// ```ebnf - /// ValueExpr ::= '[' ValueExpr* '] - /// | Identifier? Record - /// | MethodCall - /// | Identifier AccessExpr - /// | PrefixMatchExpr - /// | Literal AccessExpr - /// ``` - fn value_expr(&mut self) -> ParseResult { - if self.peek_is(Token::SquareLeft) { - let values = self.separated( - Token::SquareLeft, - Token::SquareRight, - Token::Comma, - Self::value_expr, - )?; - return Ok(ValueExpr::ListExpr(ListValueExpr { values })); - } - - if self.peek_is(Token::CurlyLeft) { - return Ok(ValueExpr::AnonymousRecordExpr( - AnonymousRecordValueExpr { - key_values: self.record()?, - }, - )); - } - - if let Some(Token::Ident(_)) = self.peek() { - let id = self.identifier()?; - if self.peek_is(Token::CurlyLeft) { - let Identifier { ident: s } = id; - return Ok(ValueExpr::TypedRecordExpr( - TypedRecordValueExpr { - type_id: TypeIdentifier { ident: s }, - key_values: self.record()?, - }, - )); - } - - if self.peek_is(Token::RoundLeft) { - let args = self.arg_expr_list()?; - return Ok(ValueExpr::RootMethodCallExpr( - MethodComputeExpr { ident: id, args }, - )); - } - - let receiver = AccessReceiver::Ident(id); - let access_expr = self.access_expr()?; - - return Ok(ValueExpr::ComputeExpr(ComputeExpr { - receiver, - access_expr, - })); - } - - let literal = self.literal()?; - - // If we parsed a prefix, it may be followed by a prefix match - // If not, it can be an access expression - if let LiteralExpr::PrefixLiteral(prefix) = &literal { - if let Some(ty) = self.prefix_match_type()? { - return Ok(ValueExpr::PrefixMatchExpr(PrefixMatchExpr { - prefix: prefix.clone(), - ty, - })); - } - } - - let access_expr = self.access_expr()?; - Ok(ValueExpr::LiteralAccessExpr(LiteralAccessExpr { - literal, - access_expr, - })) - } - - /// Parse an access expresion - /// - /// ```ebnf - /// AccessExpr ::= ( '.' ( MethodCallExpr | FieldAccessExpr ) )* - /// ``` - fn access_expr(&mut self) -> ParseResult> { - let mut access_expr = Vec::new(); - - while self.accept_optional(Token::Period)?.is_some() { - let ident = self.identifier()?; - if self.peek_is(Token::RoundLeft) { - let args = self.arg_expr_list()?; - access_expr.push(AccessExpr::MethodComputeExpr( - MethodComputeExpr { ident, args }, - )) - } else { - if let Some(AccessExpr::FieldAccessExpr(FieldAccessExpr { - field_names, - })) = access_expr.last_mut() - { - field_names.push(ident); - } else { - access_expr.push(AccessExpr::FieldAccessExpr( - FieldAccessExpr { - field_names: vec![ident], - }, - )) - } - } - } - - Ok(access_expr) - } - - /// Parse any literal, including prefixes, ip addresses and communities - fn literal(&mut self) -> ParseResult { - // A prefix length, it requires two tokens - if let Some(Token::PrefixLength(..)) = self.peek() { - let PrefixLength(len) = self.prefix_length()?; - return Ok(LiteralExpr::PrefixLengthLiteral( - PrefixLengthLiteral(len), - )); - } - - // If we see an IpAddress, we need to check whether it is followed by a - // slash and is therefore a prefix instead. - if matches!(self.peek(), Some(Token::IpV4(_) | Token::IpV6(_))) { - let addr = self.ip_address()?; - if let Some(Token::PrefixLength(..)) = self.peek() { - let len = self.prefix_length()?; - return Ok(LiteralExpr::PrefixLiteral(Prefix { addr, len })); - } else { - return Ok(LiteralExpr::IpAddressLiteral(addr)); - } - } - - self.simple_literal() - } - - /// Parse literals that need no complex parsing, just one token - fn simple_literal(&mut self) -> ParseResult { - // TODO: Make proper errors using the spans - let (token, span) = self.next()?; - Ok(match token { - Token::String(s) => { - // Trim the quotes from the string literal - let trimmed = &s[1..s.len() - 1]; - LiteralExpr::StringLiteral(StringLiteral(trimmed.into())) - } - Token::Integer(s) => LiteralExpr::IntegerLiteral(IntegerLiteral( - // This parse fails if the literal is too big, - // it should be handled properly - s.parse().unwrap(), - )), - Token::Hex(s) => LiteralExpr::HexLiteral(HexLiteral( - u64::from_str_radix(&s[2..], 16).unwrap(), - )), - Token::Asn(s) => LiteralExpr::AsnLiteral(AsnLiteral( - s[2..].parse::().unwrap(), - )), - Token::Bool(b) => LiteralExpr::BooleanLiteral(BooleanLiteral(b)), - Token::Float => { - unimplemented!("Floating point numbers are not supported yet") - } - Token::Community(s) => { - // We offload the validation of the community to routecore - // but routecore doesn't do all the hex numbers correctly, - // so we transform those first. - - // TODO: Change the AST so that it doesn't contain strings, but - // routecore communities. - use routecore::bgp::communities::{self, Community}; - let parts: Vec<_> = s - .split(':') - .map(|p| { - if let Some(hex) = p.strip_prefix("0x") { - u32::from_str_radix(hex, 16).unwrap().to_string() - } else { - p.to_string() - } - }) - .collect(); - - let transformed = parts.join(":"); - - let c: Community = transformed.parse().map_err( - |e: communities::ParseError| ParseError::InvalidLiteral { - description: "community".into(), - token: token.to_string(), - span, - inner_error: e.to_string(), - }, - )?; - match c { - Community::Standard(x) => { - LiteralExpr::StandardCommunityLiteral( - StandardCommunityLiteral(x.to_string()), - ) - } - Community::Extended(x) => { - LiteralExpr::ExtendedCommunityLiteral( - ExtendedCommunityLiteral(x.to_string()), - ) - } - Community::Large(x) => { - LiteralExpr::LargeCommunityLiteral( - LargeCommunityLiteral(x.to_string()), - ) - } - Community::Ipv6Extended(_) => { - unimplemented!( - "IPv6 extended communities are not supported yet" - ) - } - } - } - t => { - return Err(ParseError::Expected { - expected: "a literal".into(), - got: t.to_string(), - span, - }) - } - }) - } - - /// Parse an (anonymous) record - /// - /// ```ebnf - /// Record ::= '{' (RecordField (',' RecordField)* ','? )? '}' - /// RecordField ::= Identifier ':' ValueExpr - /// ``` - fn record(&mut self) -> ParseResult> { - self.separated( - Token::CurlyLeft, - Token::CurlyRight, - Token::Comma, - |parser| { - dbg!(parser.peek()); - let key = parser.identifier()?; - dbg!(parser.peek()); - parser.accept_required(Token::Colon)?; - dbg!("here?"); - let value = parser.value_expr()?; - Ok((key, value)) - }, - ) - } - - /// Parse a list of arguments to a method - /// - /// ```ebnf - /// ArgExprList ::= '(' ( ValueExpr (',' ValueExpr)* ','? )? ')' - /// ``` - fn arg_expr_list(&mut self) -> ParseResult { - let args = self.separated( - Token::RoundLeft, - Token::RoundRight, - Token::Comma, - Self::value_expr, - )?; - - Ok(ArgExprList { args }) - } - - /// Parse an optional for clause for filter-map, define and apply - /// - /// ```ebnf - /// For ::= ( 'for' TypeIdentField)? - /// ``` - fn for_statement(&mut self) -> ParseResult> { - if self.accept_optional(Token::For)?.is_some() { - Ok(Some(self.type_ident_field()?)) - } else { - Ok(None) - } - } - - /// Parase an optional with clause for filter-map, define and apply - /// - /// ```ebnf - /// With ::= ( 'with' TypeIdentField (',' TypeIdentField)*)? - /// ``` - fn with_statement(&mut self) -> ParseResult> { - let mut key_values = Vec::new(); - - if self.accept_optional(Token::With)?.is_none() { - return Ok(key_values); - } - - key_values.push(self.type_ident_field()?); - while self.accept_optional(Token::Comma)?.is_some() { - key_values.push(self.type_ident_field()?); - } - - Ok(key_values) - } - - /// Parse a prefix match type, which can follow a prefix in some contexts - /// - /// ```ebnf - /// PrefixMatchType ::= 'longer' - /// | 'orlonger' - /// | 'prefix-length-range' PrefixLengthRange - /// | 'upto' PrefixLength - /// | 'netmask' IpAddress - /// ``` - fn prefix_match_type(&mut self) -> ParseResult> { - let match_type = if self.accept_optional(Token::Exact)?.is_some() { - PrefixMatchType::Exact - } else if self.accept_optional(Token::Longer)?.is_some() { - PrefixMatchType::Longer - } else if self.accept_optional(Token::OrLonger)?.is_some() { - PrefixMatchType::OrLonger - } else if self.accept_optional(Token::PrefixLengthRange)?.is_some() { - PrefixMatchType::PrefixLengthRange(self.prefix_length_range()?) - } else if self.accept_optional(Token::UpTo)?.is_some() { - PrefixMatchType::UpTo(self.prefix_length()?) - } else if self.accept_optional(Token::NetMask)?.is_some() { - PrefixMatchType::NetMask(self.ip_address()?) - } else { - return Ok(None); - }; - - Ok(Some(match_type)) - } - - /// Parse a prefix length range - /// - /// ```ebnf - /// PrefixLengthRange ::= PrefixLength '-' PrefixLength - /// ``` - fn prefix_length_range(&mut self) -> ParseResult { - let start = self.prefix_length()?; - self.accept_required(Token::Hyphen)?; - let end = self.prefix_length()?; - Ok(PrefixLengthRange { start, end }) - } - - /// Parse a prefix length - /// - /// ```ebnf - /// PrefixLength ::= '/' Integer - /// ``` - fn prefix_length(&mut self) -> ParseResult { - let (token, span) = self.next()?; - let Token::PrefixLength(s) = token else { - return Err(ParseError::InvalidLiteral { - description: "prefix length".into(), - token: token.to_string(), - span, - inner_error: String::new(), - }); - }; - Ok(PrefixLength(s[1..].parse().unwrap())) - } - - /// Parse an identifier and a type identifier separated by a colon - /// - /// ```ebnf - /// TypeIdentField ::= Identifier ':' TypeIdentifier - /// ``` - fn type_ident_field(&mut self) -> ParseResult { - let field_name = self.identifier()?; - self.accept_required(Token::Colon)?; - let ty = self.type_identifier()?; - Ok(TypeIdentField { field_name, ty }) - } } -/// # Parsing single items +/// # Parsing identifiers impl<'source> Parser<'source> { - fn ip_address(&mut self) -> ParseResult { - Ok(match self.next()?.0 { - Token::IpV4(s) => IpAddress::Ipv4(Ipv4Addr(s.parse().unwrap())), - Token::IpV6(s) => IpAddress::Ipv6(Ipv6Addr(s.parse().unwrap())), - _ => return Err(ParseError::Todo(15)), - }) - } - fn identifier(&mut self) -> ParseResult { let (token, span) = self.next()?; match token { diff --git a/src/parser/value.rs b/src/parser/value.rs new file mode 100644 index 00000000..3efc2485 --- /dev/null +++ b/src/parser/value.rs @@ -0,0 +1,340 @@ +use crate::{ + ast::{ + AccessExpr, AccessReceiver, AnonymousRecordValueExpr, ArgExprList, AsnLiteral, BooleanLiteral, ComputeExpr, ExtendedCommunityLiteral, FieldAccessExpr, HexLiteral, Identifier, IntegerLiteral, IpAddress, Ipv4Addr, Ipv6Addr, LargeCommunityLiteral, ListValueExpr, LiteralAccessExpr, LiteralExpr, MethodComputeExpr, Prefix, PrefixLength, PrefixLengthLiteral, PrefixLengthRange, PrefixMatchExpr, PrefixMatchType, StandardCommunityLiteral, StringLiteral, TypeIdentifier, TypedRecordValueExpr, ValueExpr + }, + parser::ParseError, + token::Token, +}; + +use super::{ParseResult, Parser}; + +impl<'source> Parser<'source> { + /// Parse a value expr + /// + /// ```ebnf + /// ValueExpr ::= '[' ValueExpr* '] + /// | Identifier? Record + /// | MethodCall + /// | Identifier AccessExpr + /// | PrefixMatchExpr + /// | Literal AccessExpr + /// ``` + pub(super) fn value_expr(&mut self) -> ParseResult { + if self.peek_is(Token::SquareLeft) { + let values = self.separated( + Token::SquareLeft, + Token::SquareRight, + Token::Comma, + Self::value_expr, + )?; + return Ok(ValueExpr::ListExpr(ListValueExpr { values })); + } + + if self.peek_is(Token::CurlyLeft) { + return Ok(ValueExpr::AnonymousRecordExpr( + AnonymousRecordValueExpr { + key_values: self.record()?, + }, + )); + } + + if let Some(Token::Ident(_)) = self.peek() { + let id = self.identifier()?; + if self.peek_is(Token::CurlyLeft) { + let Identifier { ident: s } = id; + return Ok(ValueExpr::TypedRecordExpr( + TypedRecordValueExpr { + type_id: TypeIdentifier { ident: s }, + key_values: self.record()?, + }, + )); + } + + if self.peek_is(Token::RoundLeft) { + let args = self.arg_expr_list()?; + return Ok(ValueExpr::RootMethodCallExpr( + MethodComputeExpr { ident: id, args }, + )); + } + + let receiver = AccessReceiver::Ident(id); + let access_expr = self.access_expr()?; + + return Ok(ValueExpr::ComputeExpr(ComputeExpr { + receiver, + access_expr, + })); + } + + let literal = self.literal()?; + + // If we parsed a prefix, it may be followed by a prefix match + // If not, it can be an access expression + if let LiteralExpr::PrefixLiteral(prefix) = &literal { + if let Some(ty) = self.prefix_match_type()? { + return Ok(ValueExpr::PrefixMatchExpr(PrefixMatchExpr { + prefix: prefix.clone(), + ty, + })); + } + } + + let access_expr = self.access_expr()?; + Ok(ValueExpr::LiteralAccessExpr(LiteralAccessExpr { + literal, + access_expr, + })) + } + + /// Parse an access expresion + /// + /// ```ebnf + /// AccessExpr ::= ( '.' ( MethodCallExpr | FieldAccessExpr ) )* + /// ``` + fn access_expr(&mut self) -> ParseResult> { + let mut access_expr = Vec::new(); + + while self.accept_optional(Token::Period)?.is_some() { + let ident = self.identifier()?; + if self.peek_is(Token::RoundLeft) { + let args = self.arg_expr_list()?; + access_expr.push(AccessExpr::MethodComputeExpr( + MethodComputeExpr { ident, args }, + )) + } else { + if let Some(AccessExpr::FieldAccessExpr(FieldAccessExpr { + field_names, + })) = access_expr.last_mut() + { + field_names.push(ident); + } else { + access_expr.push(AccessExpr::FieldAccessExpr( + FieldAccessExpr { + field_names: vec![ident], + }, + )) + } + } + } + + Ok(access_expr) + } + + /// Parse any literal, including prefixes, ip addresses and communities + fn literal(&mut self) -> ParseResult { + // A prefix length, it requires two tokens + if let Some(Token::PrefixLength(..)) = self.peek() { + let PrefixLength(len) = self.prefix_length()?; + return Ok(LiteralExpr::PrefixLengthLiteral( + PrefixLengthLiteral(len), + )); + } + + // If we see an IpAddress, we need to check whether it is followed by a + // slash and is therefore a prefix instead. + if matches!(self.peek(), Some(Token::IpV4(_) | Token::IpV6(_))) { + let addr = self.ip_address()?; + if let Some(Token::PrefixLength(..)) = self.peek() { + let len = self.prefix_length()?; + return Ok(LiteralExpr::PrefixLiteral(Prefix { addr, len })); + } else { + return Ok(LiteralExpr::IpAddressLiteral(addr)); + } + } + + self.simple_literal() + } + + fn ip_address(&mut self) -> ParseResult { + Ok(match self.next()?.0 { + Token::IpV4(s) => IpAddress::Ipv4(Ipv4Addr(s.parse().unwrap())), + Token::IpV6(s) => IpAddress::Ipv6(Ipv6Addr(s.parse().unwrap())), + _ => return Err(ParseError::Todo(15)), + }) + } + + /// Parse literals that need no complex parsing, just one token + fn simple_literal(&mut self) -> ParseResult { + // TODO: Make proper errors using the spans + let (token, span) = self.next()?; + Ok(match token { + Token::String(s) => { + // Trim the quotes from the string literal + let trimmed = &s[1..s.len() - 1]; + LiteralExpr::StringLiteral(StringLiteral(trimmed.into())) + } + Token::Integer(s) => LiteralExpr::IntegerLiteral(IntegerLiteral( + // This parse fails if the literal is too big, + // it should be handled properly + s.parse().unwrap(), + )), + Token::Hex(s) => LiteralExpr::HexLiteral(HexLiteral( + u64::from_str_radix(&s[2..], 16).unwrap(), + )), + Token::Asn(s) => LiteralExpr::AsnLiteral(AsnLiteral( + s[2..].parse::().unwrap(), + )), + Token::Bool(b) => LiteralExpr::BooleanLiteral(BooleanLiteral(b)), + Token::Float => { + unimplemented!("Floating point numbers are not supported yet") + } + Token::Community(s) => { + // We offload the validation of the community to routecore + // but routecore doesn't do all the hex numbers correctly, + // so we transform those first. + + // TODO: Change the AST so that it doesn't contain strings, but + // routecore communities. + use routecore::bgp::communities::{self, Community}; + let parts: Vec<_> = s + .split(':') + .map(|p| { + if let Some(hex) = p.strip_prefix("0x") { + u32::from_str_radix(hex, 16).unwrap().to_string() + } else { + p.to_string() + } + }) + .collect(); + + let transformed = parts.join(":"); + + let c: Community = transformed.parse().map_err( + |e: communities::ParseError| ParseError::InvalidLiteral { + description: "community".into(), + token: token.to_string(), + span, + inner_error: e.to_string(), + }, + )?; + match c { + Community::Standard(x) => { + LiteralExpr::StandardCommunityLiteral( + StandardCommunityLiteral(x.to_string()), + ) + } + Community::Extended(x) => { + LiteralExpr::ExtendedCommunityLiteral( + ExtendedCommunityLiteral(x.to_string()), + ) + } + Community::Large(x) => { + LiteralExpr::LargeCommunityLiteral( + LargeCommunityLiteral(x.to_string()), + ) + } + Community::Ipv6Extended(_) => { + unimplemented!( + "IPv6 extended communities are not supported yet" + ) + } + } + } + t => { + return Err(ParseError::Expected { + expected: "a literal".into(), + got: t.to_string(), + span, + }) + } + }) + } + + /// Parse an (anonymous) record + /// + /// ```ebnf + /// Record ::= '{' (RecordField (',' RecordField)* ','? )? '}' + /// RecordField ::= Identifier ':' ValueExpr + /// ``` + fn record(&mut self) -> ParseResult> { + self.separated( + Token::CurlyLeft, + Token::CurlyRight, + Token::Comma, + |parser| { + dbg!(parser.peek()); + let key = parser.identifier()?; + dbg!(parser.peek()); + parser.accept_required(Token::Colon)?; + dbg!("here?"); + let value = parser.value_expr()?; + Ok((key, value)) + }, + ) + } + + /// Parse a list of arguments to a method + /// + /// ```ebnf + /// ArgExprList ::= '(' ( ValueExpr (',' ValueExpr)* ','? )? ')' + /// ``` + pub(super) fn arg_expr_list(&mut self) -> ParseResult { + let args = self.separated( + Token::RoundLeft, + Token::RoundRight, + Token::Comma, + Self::value_expr, + )?; + + Ok(ArgExprList { args }) + } + + /// Parse a prefix match type, which can follow a prefix in some contexts + /// + /// ```ebnf + /// PrefixMatchType ::= 'longer' + /// | 'orlonger' + /// | 'prefix-length-range' PrefixLengthRange + /// | 'upto' PrefixLength + /// | 'netmask' IpAddress + /// ``` + fn prefix_match_type(&mut self) -> ParseResult> { + let match_type = if self.accept_optional(Token::Exact)?.is_some() { + PrefixMatchType::Exact + } else if self.accept_optional(Token::Longer)?.is_some() { + PrefixMatchType::Longer + } else if self.accept_optional(Token::OrLonger)?.is_some() { + PrefixMatchType::OrLonger + } else if self.accept_optional(Token::PrefixLengthRange)?.is_some() { + PrefixMatchType::PrefixLengthRange(self.prefix_length_range()?) + } else if self.accept_optional(Token::UpTo)?.is_some() { + PrefixMatchType::UpTo(self.prefix_length()?) + } else if self.accept_optional(Token::NetMask)?.is_some() { + PrefixMatchType::NetMask(self.ip_address()?) + } else { + return Ok(None); + }; + + Ok(Some(match_type)) + } + + /// Parse a prefix length range + /// + /// ```ebnf + /// PrefixLengthRange ::= PrefixLength '-' PrefixLength + /// ``` + fn prefix_length_range(&mut self) -> ParseResult { + let start = self.prefix_length()?; + self.accept_required(Token::Hyphen)?; + let end = self.prefix_length()?; + Ok(PrefixLengthRange { start, end }) + } + + /// Parse a prefix length + /// + /// ```ebnf + /// PrefixLength ::= '/' Integer + /// ``` + fn prefix_length(&mut self) -> ParseResult { + let (token, span) = self.next()?; + let Token::PrefixLength(s) = token else { + return Err(ParseError::InvalidLiteral { + description: "prefix length".into(), + token: token.to_string(), + span, + inner_error: String::new(), + }); + }; + Ok(PrefixLength(s[1..].parse().unwrap())) + } +} From f2251330c62544003c5dda01f30c2afc5ecbd3c1 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Thu, 29 Feb 2024 12:14:21 +0100 Subject: [PATCH 15/24] rename functions to be more consistent --- src/parser/filter_map.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/parser/filter_map.rs b/src/parser/filter_map.rs index f58c36f4..e7599057 100644 --- a/src/parser/filter_map.rs +++ b/src/parser/filter_map.rs @@ -23,8 +23,8 @@ impl<'source> Parser<'source> { }; let ident = self.identifier()?; - let for_ident = self.for_statement()?; - let with_kv = self.with_statement()?; + let for_ident = self.for_clause()?; + let with_kv = self.with_clause()?; let body = self.filter_map_body()?; Ok(FilterMap { @@ -59,8 +59,8 @@ impl<'source> Parser<'source> { // Cannot have multiple define sections return Err(ParseError::Todo(2)); } - let for_kv = self.for_statement()?; - let with_kv = self.with_statement()?; + let for_kv = self.for_clause()?; + let with_kv = self.with_clause()?; let body = self.define_body()?; define = Some(Define { for_kv, @@ -72,8 +72,8 @@ impl<'source> Parser<'source> { // Cannot have multiple apply sections return Err(ParseError::Todo(3)); } - let for_kv = self.for_statement()?; - let with_kv = self.with_statement()?; + let for_kv = self.for_clause()?; + let with_kv = self.with_clause()?; let body = self.apply_body()?; apply = Some(ApplySection { for_kv, @@ -386,8 +386,8 @@ impl<'source> Parser<'source> { fn term(&mut self) -> ParseResult { self.accept_required(Token::Term)?; let ident = self.identifier()?; - let for_kv = self.for_statement()?; - let with_kv = self.with_statement()?; + let for_kv = self.for_clause()?; + let with_kv = self.with_clause()?; let mut scopes = Vec::new(); self.accept_required(Token::CurlyLeft)?; @@ -616,7 +616,7 @@ impl<'source> Parser<'source> { pub(super) fn action(&mut self) -> ParseResult { self.accept_required(Token::Action)?; let ident = self.identifier()?; - let with_kv = self.with_statement()?; + let with_kv = self.with_clause()?; let mut expressions = Vec::new(); self.accept_required(Token::CurlyLeft)?; @@ -647,7 +647,7 @@ impl<'source> Parser<'source> { /// ```ebnf /// For ::= ( 'for' TypeIdentField)? /// ``` - fn for_statement(&mut self) -> ParseResult> { + fn for_clause(&mut self) -> ParseResult> { if self.accept_optional(Token::For)?.is_some() { Ok(Some(self.type_ident_field()?)) } else { @@ -660,7 +660,7 @@ impl<'source> Parser<'source> { /// ```ebnf /// With ::= ( 'with' TypeIdentField (',' TypeIdentField)*)? /// ``` - fn with_statement(&mut self) -> ParseResult> { + fn with_clause(&mut self) -> ParseResult> { let mut key_values = Vec::new(); if self.accept_optional(Token::With)?.is_none() { From f3860c48b567bf76b37899b22c274e2683852126 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Thu, 29 Feb 2024 12:40:45 +0100 Subject: [PATCH 16/24] rename basic parser methods --- src/parser/filter_map.rs | 175 +++++++++++++++++++-------------------- src/parser/mod.rs | 30 +++---- src/parser/rib_like.rs | 20 ++--- src/parser/value.rs | 22 ++--- 4 files changed, 123 insertions(+), 124 deletions(-) diff --git a/src/parser/filter_map.rs b/src/parser/filter_map.rs index e7599057..208a33d7 100644 --- a/src/parser/filter_map.rs +++ b/src/parser/filter_map.rs @@ -51,10 +51,10 @@ impl<'source> Parser<'source> { let mut expressions: Vec = Vec::new(); let mut apply = None; - self.accept_required(Token::CurlyLeft)?; + self.take(Token::CurlyLeft)?; - while self.accept_optional(Token::CurlyRight)?.is_none() { - if self.accept_optional(Token::Define)?.is_some() { + while !self.next_is(Token::CurlyRight) { + if self.next_is(Token::Define) { if define.is_some() { // Cannot have multiple define sections return Err(ParseError::Todo(2)); @@ -67,7 +67,7 @@ impl<'source> Parser<'source> { with_kv, body, }); - } else if self.accept_optional(Token::Apply)?.is_some() { + } else if self.next_is(Token::Apply) { if apply.is_some() { // Cannot have multiple apply sections return Err(ParseError::Todo(3)); @@ -106,20 +106,20 @@ impl<'source> Parser<'source> { /// Assignment ::= Identifier '=' ValueExpr ';' /// ``` fn define_body(&mut self) -> ParseResult { - self.accept_required(Token::CurlyLeft)?; + self.take(Token::CurlyLeft)?; let rx_tx_type = match self.next()?.0 { Token::RxTx => { let field = self.type_ident_field()?; - self.accept_required(Token::SemiColon)?; + self.take(Token::SemiColon)?; RxTxType::PassThrough(field) } Token::Rx => { let rx_field = self.type_ident_field()?; - self.accept_required(Token::SemiColon)?; - if self.accept_optional(Token::Tx)?.is_some() { + self.take(Token::SemiColon)?; + if self.next_is(Token::Tx) { let tx_field = self.type_ident_field()?; - self.accept_required(Token::SemiColon)?; + self.take(Token::SemiColon)?; RxTxType::Split(rx_field, tx_field) } else { RxTxType::RxOnly(rx_field) @@ -129,17 +129,17 @@ impl<'source> Parser<'source> { }; let mut use_ext_data = Vec::new(); - while self.accept_optional(Token::Use)?.is_some() { + while self.next_is(Token::Use) { use_ext_data.push((self.identifier()?, self.identifier()?)); - self.accept_required(Token::SemiColon)?; + self.take(Token::SemiColon)?; } let mut assignments = Vec::new(); - while self.accept_optional(Token::CurlyRight)?.is_none() { + while !self.next_is(Token::CurlyRight) { let id = self.identifier()?; - self.accept_required(Token::Eq)?; + self.take(Token::Eq)?; let value = self.value_expr()?; - self.accept_required(Token::SemiColon)?; + self.take(Token::SemiColon)?; assignments.push((id, value)); } @@ -156,7 +156,7 @@ impl<'source> Parser<'source> { /// ApplyBody ::= ApplyScope* (AcceptReject ';')? /// ``` fn apply_body(&mut self) -> ParseResult { - self.accept_required(Token::CurlyLeft)?; + self.take(Token::CurlyLeft)?; let mut scopes = Vec::new(); while !(self.peek_is(Token::Return) @@ -167,8 +167,8 @@ impl<'source> Parser<'source> { scopes.push(self.apply_scope()?); } - let accept_reject = self.accept_reject()?; - self.accept_required(Token::CurlyRight)?; + let accept_reject = self.try_accept_reject()?; + self.take(Token::CurlyRight)?; Ok(ApplyBody { scopes, accept_reject, @@ -189,41 +189,41 @@ impl<'source> Parser<'source> { return self.apply_match(); } - self.accept_required(Token::Filter)?; + self.take(Token::Filter)?; // This is not exactly self.match_operator because match ... with is // not allowed. - let operator = if self.accept_optional(Token::Match)?.is_some() { + let operator = if self.next_is(Token::Match) { MatchOperator::Match - } else if self.accept_optional(Token::ExactlyOne)?.is_some() { + } else if self.next_is(Token::ExactlyOne) { MatchOperator::ExactlyOne - } else if self.accept_optional(Token::Some)?.is_some() { + } else if self.next_is(Token::Some) { MatchOperator::Some - } else if self.accept_optional(Token::All)?.is_some() { + } else if self.next_is(Token::All) { MatchOperator::All } else { return Err(ParseError::Todo(20)); }; let filter_ident = self.value_expr()?; - let negate = self.accept_optional(Token::Not)?.is_some(); - self.accept_required(Token::Matching)?; - self.accept_required(Token::CurlyLeft)?; + let negate = self.next_is(Token::Not); + self.take(Token::Matching)?; + self.take(Token::CurlyLeft)?; let mut actions = Vec::new(); - while self.accept_optional(Token::CurlyRight)?.is_none() { - if let Some(accept_reject) = self.accept_reject()? { - self.accept_required(Token::CurlyRight)?; + while !self.next_is(Token::CurlyRight) { + if let Some(accept_reject) = self.try_accept_reject()? { + self.take(Token::CurlyRight)?; actions.push((None, Some(accept_reject))); break; } let val = self.value_expr()?; - self.accept_required(Token::SemiColon)?; + self.take(Token::SemiColon)?; actions.push((Some(val), None)); } - self.accept_required(Token::SemiColon)?; + self.take(Token::SemiColon)?; Ok(ApplyScope { scope: None, @@ -241,19 +241,19 @@ impl<'source> Parser<'source> { fn apply_match(&mut self) -> ParseResult { let operator = self.match_operator()?; let mut match_arms = Vec::new(); - self.accept_required(Token::CurlyLeft)?; - while self.accept_optional(Token::CurlyRight)?.is_none() { + self.take(Token::CurlyLeft)?; + while !self.next_is(Token::CurlyRight) { let variant_id = self.identifier()?; let data_field = - if self.accept_optional(Token::RoundLeft)?.is_some() { + if self.next_is(Token::RoundLeft) { let id = self.identifier()?; - self.accept_required(Token::RoundRight)?; + self.take(Token::RoundRight)?; Some(id) } else { None }; - let guard = if self.accept_optional(Token::Pipe)?.is_some() { + let guard = if self.next_is(Token::Pipe) { let term_id = self.identifier()?; let args = if self.peek_is(Token::RoundLeft) { Some(self.arg_expr_list()?) @@ -265,23 +265,23 @@ impl<'source> Parser<'source> { None }; - self.accept_required(Token::Arrow)?; + self.take(Token::Arrow)?; let mut actions = Vec::new(); - if self.accept_optional(Token::CurlyLeft)?.is_some() { - while self.accept_optional(Token::CurlyRight)?.is_none() { - if let Some(ar) = self.accept_reject()? { - self.accept_required(Token::CurlyRight)?; + if self.next_is(Token::CurlyLeft) { + while !self.next_is(Token::CurlyRight) { + if let Some(ar) = self.try_accept_reject()? { + self.take(Token::CurlyRight)?; actions.push((None, Some(ar))); break; } actions.push((Some(self.action_call_expr()?), None)); - self.accept_required(Token::SemiColon)?; + self.take(Token::SemiColon)?; } - self.accept_optional(Token::Comma)?; + self.next_is(Token::Comma); } else { let expr = self.action_call_expr()?; - self.accept_required(Token::Comma)?; + self.take(Token::Comma)?; actions.push((Some(expr), None)); } @@ -318,24 +318,24 @@ impl<'source> Parser<'source> { /// ```ebnf /// AcceptReject ::= ('return'? ( 'accept' | 'reject' ) ';')? /// ``` - fn accept_reject(&mut self) -> ParseResult> { - if self.accept_optional(Token::Return)?.is_some() { + fn try_accept_reject(&mut self) -> ParseResult> { + if self.next_is(Token::Return) { let value = match self.next()?.0 { Token::Accept => AcceptReject::Accept, Token::Reject => AcceptReject::Reject, _ => return Err(ParseError::Todo(10)), }; - self.accept_required(Token::SemiColon)?; + self.take(Token::SemiColon)?; return Ok(Some(value)); } - if self.accept_optional(Token::Accept)?.is_some() { - self.accept_required(Token::SemiColon)?; + if self.next_is(Token::Accept) { + self.take(Token::SemiColon)?; return Ok(Some(AcceptReject::Accept)); } - if self.accept_optional(Token::Reject)?.is_some() { - self.accept_required(Token::SemiColon)?; + if self.next_is(Token::Reject) { + self.take(Token::SemiColon)?; return Ok(Some(AcceptReject::Reject)); } @@ -353,7 +353,7 @@ impl<'source> Parser<'source> { Token::Match => { if matches!(self.peek(), Some(Token::Ident(_))) { let ident = self.identifier()?; - self.accept_required(Token::With)?; + self.take(Token::With)?; MatchOperator::MatchValueWith(ident) } else { MatchOperator::Match @@ -384,14 +384,14 @@ impl<'source> Parser<'source> { } fn term(&mut self) -> ParseResult { - self.accept_required(Token::Term)?; + self.take(Token::Term)?; let ident = self.identifier()?; let for_kv = self.for_clause()?; let with_kv = self.with_clause()?; let mut scopes = Vec::new(); - self.accept_required(Token::CurlyLeft)?; - while self.accept_optional(Token::CurlyRight)?.is_none() { + self.take(Token::CurlyLeft)?; + while !self.next_is(Token::CurlyRight) { scopes.push(self.term_scope()?); } @@ -410,8 +410,8 @@ impl<'source> Parser<'source> { // the match will contain logical expressions. if let MatchOperator::MatchValueWith(_) = operator { let mut match_arms = Vec::new(); - self.accept_required(Token::CurlyLeft)?; - while self.accept_optional(Token::CurlyRight)?.is_none() { + self.take(Token::CurlyLeft)?; + while !self.next_is(Token::CurlyRight) { let (pattern, expr) = self.match_arm()?; match_arms.push((Some(pattern), expr)); } @@ -423,11 +423,11 @@ impl<'source> Parser<'source> { match_arms, }) } else { - self.accept_required(Token::CurlyLeft)?; + self.take(Token::CurlyLeft)?; let mut match_arms = Vec::new(); - while self.accept_optional(Token::CurlyRight)?.is_none() { + while !self.next_is(Token::CurlyRight) { match_arms.push((None, vec![self.logical_expr()?])); - self.accept_required(Token::SemiColon)?; + self.take(Token::SemiColon)?; } Ok(TermScope { scope: None, @@ -442,29 +442,28 @@ impl<'source> Parser<'source> { ) -> ParseResult<(TermPatternMatchArm, Vec)> { let variant_id = self.identifier()?; - let data_field = self - .accept_optional(Token::RoundLeft)? - .map(|_| { - let field = self.identifier()?; - self.accept_required(Token::RoundRight)?; - Ok(field) - }) - .transpose()?; + let data_field = if self.next_is(Token::RoundLeft) { + let field = self.identifier()?; + self.take(Token::RoundRight)?; + Some(field) + } else { + None + }; - self.accept_required(Token::Arrow)?; + self.take(Token::Arrow)?; let mut expr = Vec::new(); - if self.accept_optional(Token::CurlyLeft)?.is_some() { - while self.accept_optional(Token::CurlyRight)?.is_none() { + if self.next_is(Token::CurlyLeft) { + while !self.next_is(Token::CurlyRight) { expr.push(self.logical_expr()?); - self.accept_required(Token::SemiColon)?; + self.take(Token::SemiColon)?; } - self.accept_optional(Token::Comma)?; + self.next_is(Token::Comma); } else { expr.push(self.logical_expr()?); // This comma might need to be optional, but it's probably good // practice to require it. - self.accept_required(Token::Comma)?; + self.take(Token::Comma)?; } Ok(( @@ -485,17 +484,17 @@ impl<'source> Parser<'source> { /// | BooleanExpr /// ``` pub(super) fn logical_expr(&mut self) -> ParseResult { - if self.accept_optional(Token::Bang)?.is_some() { + if self.next_is(Token::Bang) { let expr = self.boolean_expr()?; return Ok(LogicalExpr::NotExpr(NotExpr { expr })); } let left = self.boolean_expr()?; - Ok(if self.accept_optional(Token::PipePipe)?.is_some() { + Ok(if self.next_is(Token::PipePipe) { let right = self.boolean_expr()?; LogicalExpr::OrExpr(OrExpr { left, right }) - } else if self.accept_optional(Token::AmpAmp)?.is_some() { + } else if self.next_is(Token::AmpAmp) { let right = self.boolean_expr()?; LogicalExpr::AndExpr(AndExpr { left, right }) } else { @@ -593,8 +592,8 @@ impl<'source> Parser<'source> { Token::AngleRightEq => CompareOp::Ge, Token::In => CompareOp::In, Token::Not => { - self.accept_required(Token::Not)?; - self.accept_required(Token::In)?; + self.take(Token::Not)?; + self.take(Token::In)?; return Ok(Some(CompareOp::NotIn)); } _ => return Ok(None), @@ -605,24 +604,24 @@ impl<'source> Parser<'source> { } fn grouped_logical_expr(&mut self) -> ParseResult { - self.accept_required(Token::RoundLeft)?; + self.take(Token::RoundLeft)?; let expr = self.logical_expr()?; - self.accept_required(Token::RoundRight)?; + self.take(Token::RoundRight)?; Ok(GroupedLogicalExpr { expr: Box::new(expr), }) } pub(super) fn action(&mut self) -> ParseResult { - self.accept_required(Token::Action)?; + self.take(Token::Action)?; let ident = self.identifier()?; let with_kv = self.with_clause()?; let mut expressions = Vec::new(); - self.accept_required(Token::CurlyLeft)?; - while self.accept_optional(Token::CurlyRight)?.is_none() { + self.take(Token::CurlyLeft)?; + while !self.next_is(Token::CurlyRight) { let value_expr = self.value_expr()?; - self.accept_required(Token::SemiColon)?; + self.take(Token::SemiColon)?; match value_expr { ValueExpr::ComputeExpr(x) => expressions.push(x), ValueExpr::RootMethodCallExpr(x) => { @@ -648,7 +647,7 @@ impl<'source> Parser<'source> { /// For ::= ( 'for' TypeIdentField)? /// ``` fn for_clause(&mut self) -> ParseResult> { - if self.accept_optional(Token::For)?.is_some() { + if self.next_is(Token::For) { Ok(Some(self.type_ident_field()?)) } else { Ok(None) @@ -663,12 +662,12 @@ impl<'source> Parser<'source> { fn with_clause(&mut self) -> ParseResult> { let mut key_values = Vec::new(); - if self.accept_optional(Token::With)?.is_none() { + if !self.next_is(Token::With) { return Ok(key_values); } key_values.push(self.type_ident_field()?); - while self.accept_optional(Token::Comma)?.is_some() { + while self.next_is(Token::Comma) { key_values.push(self.type_ident_field()?); } @@ -682,7 +681,7 @@ impl<'source> Parser<'source> { /// ``` fn type_ident_field(&mut self) -> ParseResult { let field_name = self.identifier()?; - self.accept_required(Token::Colon)?; + self.take(Token::Colon)?; let ty = self.type_identifier()?; Ok(TypeIdentField { field_name, ty }) } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index f49152c5..f5981ba1 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -86,6 +86,16 @@ impl<'source> Parser<'source> { } } + /// Move the lexer forward if the next token matches the given token + fn next_is(&mut self, token: Token) -> bool { + if self.peek_is(token) { + self.next().unwrap(); + true + } else { + false + } + } + /// Peek the next token fn peek(&mut self) -> Option<&Token<'source>> { match self.lexer.peek() { @@ -103,18 +113,8 @@ impl<'source> Parser<'source> { &token == lexed_token } - /// Check for an optional token - fn accept_optional(&mut self, token: Token) -> ParseResult> { - if self.peek_is(token) { - // TODO: this should probably be an unwrap? - Ok(Some(self.next()?.1)) - } else { - Ok(None) - } - } - /// Move the lexer forward and assert that it matches the token - fn accept_required(&mut self, token: Token) -> ParseResult<()> { + fn take(&mut self, token: Token) -> ParseResult<()> { let (next, span) = self.next()?; if next == token { Ok(()) @@ -145,12 +145,12 @@ impl<'source> Parser<'source> { sep: Token, mut parser: impl FnMut(&mut Self) -> ParseResult, ) -> ParseResult> { - self.accept_required(open)?; + self.take(open)?; let mut items = Vec::new(); // If there are no fields, return the empty vec. - if self.accept_optional(close.clone())?.is_some() { + if self.next_is(close.clone()) { return Ok(items); } @@ -158,7 +158,7 @@ impl<'source> Parser<'source> { items.push(parser(self)?); // Now each field must be separated by a comma - while self.accept_optional(sep.clone())?.is_some() { + while self.next_is(sep.clone()) { // If we have found the curly right, we have just // parsed the trailing comma. if self.peek_is(close.clone()) { @@ -168,7 +168,7 @@ impl<'source> Parser<'source> { items.push(parser(self)?); } - self.accept_required(close)?; + self.take(close)?; Ok(items) } diff --git a/src/parser/rib_like.rs b/src/parser/rib_like.rs index fa4495c5..7403f333 100644 --- a/src/parser/rib_like.rs +++ b/src/parser/rib_like.rs @@ -16,9 +16,9 @@ impl<'source> Parser<'source> { /// RibBody /// ``` pub(super) fn rib(&mut self) -> ParseResult { - self.accept_required(Token::Rib)?; + self.take(Token::Rib)?; let ident = self.identifier()?; - self.accept_required(Token::Contains)?; + self.take(Token::Contains)?; let contain_ty = self.type_identifier()?; let body = self.rib_body()?; @@ -37,9 +37,9 @@ impl<'source> Parser<'source> { /// RibBody /// ``` pub(super) fn table(&mut self) -> ParseResult
{ - self.accept_required(Token::Table)?; + self.take(Token::Table)?; let ident = self.identifier()?; - self.accept_required(Token::Contains)?; + self.take(Token::Contains)?; let contain_ty = self.type_identifier()?; let body = self.rib_body()?; @@ -58,9 +58,9 @@ impl<'source> Parser<'source> { /// RibBody /// ``` pub(super) fn output_stream(&mut self) -> ParseResult { - self.accept_required(Token::OutputStream)?; + self.take(Token::OutputStream)?; let ident = self.identifier()?; - self.accept_required(Token::Contains)?; + self.take(Token::Contains)?; let contain_ty = self.type_identifier()?; let body = self.rib_body()?; @@ -74,7 +74,7 @@ impl<'source> Parser<'source> { pub(super) fn record_type_assignment( &mut self, ) -> ParseResult { - self.accept_required(Token::Type)?; + self.take(Token::Type)?; let ident = self.type_identifier()?; let body = self.rib_body()?; let record_type = RecordTypeIdentifier { @@ -111,7 +111,7 @@ impl<'source> Parser<'source> { /// ``` fn rib_field(&mut self) -> ParseResult { let key = self.identifier()?; - self.accept_required(Token::Colon)?; + self.take(Token::Colon)?; let field = if self.peek_is(Token::CurlyLeft) { // TODO: This recursion seems to be the right thing to do, maybe @@ -121,9 +121,9 @@ impl<'source> Parser<'source> { key, RecordTypeIdentifier { key_values }, ))) - } else if self.accept_optional(Token::SquareLeft)?.is_some() { + } else if self.next_is(Token::SquareLeft) { let inner_type = self.type_identifier()?; - self.accept_required(Token::SquareRight)?; + self.take(Token::SquareRight)?; RibField::ListField(Box::new(( key, ListTypeIdentifier { inner_type }, diff --git a/src/parser/value.rs b/src/parser/value.rs index 3efc2485..5046dfba 100644 --- a/src/parser/value.rs +++ b/src/parser/value.rs @@ -71,7 +71,7 @@ impl<'source> Parser<'source> { // If we parsed a prefix, it may be followed by a prefix match // If not, it can be an access expression if let LiteralExpr::PrefixLiteral(prefix) = &literal { - if let Some(ty) = self.prefix_match_type()? { + if let Some(ty) = self.try_prefix_match_type()? { return Ok(ValueExpr::PrefixMatchExpr(PrefixMatchExpr { prefix: prefix.clone(), ty, @@ -94,7 +94,7 @@ impl<'source> Parser<'source> { fn access_expr(&mut self) -> ParseResult> { let mut access_expr = Vec::new(); - while self.accept_optional(Token::Period)?.is_some() { + while self.next_is(Token::Period) { let ident = self.identifier()?; if self.peek_is(Token::RoundLeft) { let args = self.arg_expr_list()?; @@ -255,7 +255,7 @@ impl<'source> Parser<'source> { dbg!(parser.peek()); let key = parser.identifier()?; dbg!(parser.peek()); - parser.accept_required(Token::Colon)?; + parser.take(Token::Colon)?; dbg!("here?"); let value = parser.value_expr()?; Ok((key, value)) @@ -288,18 +288,18 @@ impl<'source> Parser<'source> { /// | 'upto' PrefixLength /// | 'netmask' IpAddress /// ``` - fn prefix_match_type(&mut self) -> ParseResult> { - let match_type = if self.accept_optional(Token::Exact)?.is_some() { + fn try_prefix_match_type(&mut self) -> ParseResult> { + let match_type = if self.next_is(Token::Exact) { PrefixMatchType::Exact - } else if self.accept_optional(Token::Longer)?.is_some() { + } else if self.next_is(Token::Longer) { PrefixMatchType::Longer - } else if self.accept_optional(Token::OrLonger)?.is_some() { + } else if self.next_is(Token::OrLonger) { PrefixMatchType::OrLonger - } else if self.accept_optional(Token::PrefixLengthRange)?.is_some() { + } else if self.next_is(Token::PrefixLengthRange) { PrefixMatchType::PrefixLengthRange(self.prefix_length_range()?) - } else if self.accept_optional(Token::UpTo)?.is_some() { + } else if self.next_is(Token::UpTo) { PrefixMatchType::UpTo(self.prefix_length()?) - } else if self.accept_optional(Token::NetMask)?.is_some() { + } else if self.next_is(Token::NetMask) { PrefixMatchType::NetMask(self.ip_address()?) } else { return Ok(None); @@ -315,7 +315,7 @@ impl<'source> Parser<'source> { /// ``` fn prefix_length_range(&mut self) -> ParseResult { let start = self.prefix_length()?; - self.accept_required(Token::Hyphen)?; + self.take(Token::Hyphen)?; let end = self.prefix_length()?; Ok(PrefixLengthRange { start, end }) } From 1dac4c402d8ac4311755a931fffd7108be31e34e Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Thu, 29 Feb 2024 14:47:07 +0100 Subject: [PATCH 17/24] replace ParseError::Todo with reasonable error messages --- src/parser/filter_map.rs | 179 ++++++++++++++++++++++++++++++--------- src/parser/mod.rs | 23 +++-- src/parser/value.rs | 24 +++++- 3 files changed, 174 insertions(+), 52 deletions(-) diff --git a/src/parser/filter_map.rs b/src/parser/filter_map.rs index 208a33d7..74d228e1 100644 --- a/src/parser/filter_map.rs +++ b/src/parser/filter_map.rs @@ -1,6 +1,16 @@ +use logos::Span; + use crate::{ ast::{ - AcceptReject, AccessExpr, AccessReceiver, ActionCallExpr, ActionSection, ActionSectionBody, AndExpr, ApplyBody, ApplyScope, ApplySection, BooleanExpr, CompareArg, CompareExpr, CompareOp, ComputeExpr, Define, DefineBody, FilterMap, FilterMapBody, FilterMapExpr, FilterMatchActionExpr, FilterType, GroupedLogicalExpr, ListCompareExpr, LogicalExpr, MatchActionExpr, MatchOperator, NotExpr, OrExpr, PatternMatchActionArm, PatternMatchActionExpr, RxTxType, TermBody, TermCallExpr, TermPatternMatchArm, TermScope, TermSection, TypeIdentField, ValueExpr + AcceptReject, AccessExpr, AccessReceiver, ActionCallExpr, + ActionSection, ActionSectionBody, AndExpr, ApplyBody, ApplyScope, + ApplySection, BooleanExpr, CompareArg, CompareExpr, CompareOp, + ComputeExpr, Define, DefineBody, FilterMap, FilterMapBody, + FilterMapExpr, FilterMatchActionExpr, FilterType, GroupedLogicalExpr, + ListCompareExpr, LogicalExpr, MatchActionExpr, MatchOperator, + NotExpr, OrExpr, PatternMatchActionArm, PatternMatchActionExpr, + RxTxType, TermBody, TermCallExpr, TermPatternMatchArm, TermScope, + TermSection, TypeIdentField, ValueExpr, }, token::Token, }; @@ -15,17 +25,23 @@ impl<'source> Parser<'source> { /// For With FilterMapBody /// ``` pub(super) fn filter_map(&mut self) -> ParseResult { - let (token, _span) = self.next()?; + let (token, span) = self.next()?; let ty = match token { Token::FilterMap => FilterType::FilterMap, Token::Filter => FilterType::Filter, - _ => return Err(ParseError::Todo(1)), + _ => { + return Err(ParseError::Expected { + expected: "'filter-map' or 'filter'".into(), + got: token.to_string(), + span, + }) + } }; let ident = self.identifier()?; let for_ident = self.for_clause()?; let with_kv = self.with_clause()?; - let body = self.filter_map_body()?; + let body = self.filter_map_body(span)?; Ok(FilterMap { ty, @@ -46,7 +62,7 @@ impl<'source> Parser<'source> { /// /// Not shown in the EBNF above, but the location of the define and apply /// sections doesn't matter, but they can both only appear once. - fn filter_map_body(&mut self) -> ParseResult { + fn filter_map_body(&mut self, span: Span) -> ParseResult { let mut define = None; let mut expressions: Vec = Vec::new(); let mut apply = None; @@ -54,10 +70,15 @@ impl<'source> Parser<'source> { self.take(Token::CurlyLeft)?; while !self.next_is(Token::CurlyRight) { - if self.next_is(Token::Define) { + if self.peek_is(Token::Define) { + let (_, span) = self.next()?; if define.is_some() { // Cannot have multiple define sections - return Err(ParseError::Todo(2)); + return Err(ParseError::Custom { + description: "a filter or filter-map cannot have multiple define sections".into(), + label: "merge this define section with the previous one".into(), + span, + }); } let for_kv = self.for_clause()?; let with_kv = self.with_clause()?; @@ -67,10 +88,15 @@ impl<'source> Parser<'source> { with_kv, body, }); - } else if self.next_is(Token::Apply) { + } else if self.peek_is(Token::Apply) { + let (_, span) = self.next()?; if apply.is_some() { // Cannot have multiple apply sections - return Err(ParseError::Todo(3)); + return Err(ParseError::Custom { + description: "a filter or filter-map cannot have multiple apply sections".into(), + label: "merge this apply section with the previous one".into(), + span, + }); } let for_kv = self.for_clause()?; let with_kv = self.with_clause()?; @@ -85,8 +111,19 @@ impl<'source> Parser<'source> { } } + let Some(define) = define else { + return Err(ParseError::Custom { + description: "a filter or filter-map requires at\ + least one define section" + .into(), + label: + "this filter or filter-map is missing a define section" + .into(), + span, + }); + }; Ok(FilterMapBody { - define: define.ok_or(ParseError::Todo(4))?, + define, expressions, apply, }) @@ -108,7 +145,8 @@ impl<'source> Parser<'source> { fn define_body(&mut self) -> ParseResult { self.take(Token::CurlyLeft)?; - let rx_tx_type = match self.next()?.0 { + let (token, span) = self.next()?; + let rx_tx_type = match token { Token::RxTx => { let field = self.type_ident_field()?; self.take(Token::SemiColon)?; @@ -125,7 +163,13 @@ impl<'source> Parser<'source> { RxTxType::RxOnly(rx_field) } } - _ => return Err(ParseError::Todo(9)), + _ => { + return Err(ParseError::Expected { + expected: "'rx' or 'rx_tx'".into(), + got: token.to_string(), + span, + }) + } }; let mut use_ext_data = Vec::new(); @@ -202,7 +246,12 @@ impl<'source> Parser<'source> { } else if self.next_is(Token::All) { MatchOperator::All } else { - return Err(ParseError::Todo(20)); + let (token, span) = self.next()?; + return Err(ParseError::Expected { + expected: "'match', 'exactly-one', 'some' or 'all'".into(), + got: token.to_string(), + span, + }); }; let filter_ident = self.value_expr()?; @@ -244,14 +293,13 @@ impl<'source> Parser<'source> { self.take(Token::CurlyLeft)?; while !self.next_is(Token::CurlyRight) { let variant_id = self.identifier()?; - let data_field = - if self.next_is(Token::RoundLeft) { - let id = self.identifier()?; - self.take(Token::RoundRight)?; - Some(id) - } else { - None - }; + let data_field = if self.next_is(Token::RoundLeft) { + let id = self.identifier()?; + self.take(Token::RoundRight)?; + Some(id) + } else { + None + }; let guard = if self.next_is(Token::Pipe) { let term_id = self.identifier()?; @@ -320,10 +368,18 @@ impl<'source> Parser<'source> { /// ``` fn try_accept_reject(&mut self) -> ParseResult> { if self.next_is(Token::Return) { - let value = match self.next()?.0 { + let (token, span) = self.next()?; + let value = match token { Token::Accept => AcceptReject::Accept, Token::Reject => AcceptReject::Reject, - _ => return Err(ParseError::Todo(10)), + _ => { + return Err(ParseError::Expected { + expected: "'accept' or 'reject' after 'return'" + .into(), + got: token.to_string(), + span, + }) + } }; self.take(Token::SemiColon)?; return Ok(Some(value)); @@ -349,7 +405,8 @@ impl<'source> Parser<'source> { /// | 'some' | 'exactly-one' | 'all' /// ``` fn match_operator(&mut self) -> ParseResult { - let op = match self.next()?.0 { + let (token, span) = self.next()?; + let op = match token { Token::Match => { if matches!(self.peek(), Some(Token::Ident(_))) { let ident = self.identifier()?; @@ -362,7 +419,14 @@ impl<'source> Parser<'source> { Token::Some => MatchOperator::Some, Token::ExactlyOne => MatchOperator::ExactlyOne, Token::All => MatchOperator::All, - _ => return Err(ParseError::Todo(11)), + _ => { + return Err(ParseError::Expected { + expected: "'match', 'exactly-one', 'some' or 'all'" + .into(), + got: token.to_string(), + span, + }) + } }; Ok(op) @@ -379,7 +443,12 @@ impl<'source> Parser<'source> { } else if self.peek_is(Token::Action) { Ok(FilterMapExpr::Action(self.action()?)) } else { - Err(ParseError::Todo(5)) + let (token, span) = self.next()?; + Err(ParseError::Expected { + expected: "'term' or 'action'".into(), + got: token.to_string(), + span, + }) } } @@ -517,10 +586,19 @@ impl<'source> Parser<'source> { fn boolean_expr(&mut self) -> ParseResult { let left = self.logical_or_value_expr()?; - if let Some(op) = self.try_compare_operator()? { + if let Some((op, span)) = self.try_compare_operator()? { if op == CompareOp::In || op == CompareOp::NotIn { let CompareArg::ValueExpr(left) = left else { - return Err(ParseError::Todo(16)); + let op = if op == CompareOp::NotIn { + "not in" + } else { + "in" + }; + return Err(ParseError::Custom { + description: format!("the {op} operator cannot take a logical expression as an argument"), + label: "this operator only acts on lists".into(), + span, + }); }; let right = self.value_expr()?; return Ok(BooleanExpr::ListCompareExpr(Box::new( @@ -558,7 +636,15 @@ impl<'source> Parser<'source> { ValueExpr::RootMethodCallExpr(_) | ValueExpr::AnonymousRecordExpr(_) | ValueExpr::TypedRecordExpr(_) - | ValueExpr::ListExpr(_) => return Err(ParseError::Todo(6)), + | ValueExpr::ListExpr(_) => { + // TODO: the span information should be better here. + let (_token, span) = self.next()?; + return Err(ParseError::Custom { + description: "not a valid boolean expression".into(), + label: "the expression ending here is not a valid boolean expression".into(), + span: span.start-1..span.end-1, + }); + } }) } @@ -573,12 +659,17 @@ impl<'source> Parser<'source> { /// Optionally parse a compare operator /// /// This method returns [`Option`], because we are never sure that there is - /// going to be a comparison operator. + /// going to be a comparison operator. A span is included with the operator + /// to allow error messages to be attached to the parsed operator. /// /// ```ebnf /// CompareOp ::= '==' | '!=' | '<' | '<=' | '>' | '>=' | 'not'? 'in' /// ``` - fn try_compare_operator(&mut self) -> ParseResult> { + /// + /// This function returns a span + fn try_compare_operator( + &mut self, + ) -> ParseResult> { let Some(tok) = self.peek() else { return Ok(None); }; @@ -592,15 +683,15 @@ impl<'source> Parser<'source> { Token::AngleRightEq => CompareOp::Ge, Token::In => CompareOp::In, Token::Not => { - self.take(Token::Not)?; - self.take(Token::In)?; - return Ok(Some(CompareOp::NotIn)); + let span1 = self.take(Token::Not)?; + let span2 = self.take(Token::In)?; + return Ok(Some((CompareOp::NotIn, span1.start..span2.end))); } _ => return Ok(None), }; - self.next()?; - Ok(Some(op)) + let (_, span) = self.next()?; + Ok(Some((op, span))) } fn grouped_logical_expr(&mut self) -> ParseResult { @@ -611,7 +702,7 @@ impl<'source> Parser<'source> { expr: Box::new(expr), }) } - + pub(super) fn action(&mut self) -> ParseResult { self.take(Token::Action)?; let ident = self.identifier()?; @@ -621,7 +712,7 @@ impl<'source> Parser<'source> { self.take(Token::CurlyLeft)?; while !self.next_is(Token::CurlyRight) { let value_expr = self.value_expr()?; - self.take(Token::SemiColon)?; + let span1 = self.take(Token::SemiColon)?; match value_expr { ValueExpr::ComputeExpr(x) => expressions.push(x), ValueExpr::RootMethodCallExpr(x) => { @@ -630,7 +721,15 @@ impl<'source> Parser<'source> { access_expr: vec![AccessExpr::MethodComputeExpr(x)], }) } - _ => return Err(ParseError::Todo(7)), + _ => { + // TODO: span information could be better + let (_, span2) = self.next()?; + return Err(ParseError::Custom { + description: "an action can only be a compute epression or root method call".into(), + label: "invalid action".into(), + span: span1.start..span2.end, + }); + } } } @@ -641,7 +740,7 @@ impl<'source> Parser<'source> { }) } - /// Parse an optional for clause for filter-map, define and apply + /// Parse an optional for clause for filter-map, define and apply /// /// ```ebnf /// For ::= ( 'for' TypeIdentField)? diff --git a/src/parser/mod.rs b/src/parser/mod.rs index f5981ba1..c3bcf124 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -24,10 +24,6 @@ pub enum ParseError { EndOfInput, FailedToParseEntireInput, InvalidToken(#[label("invalid token")] Span), - /// Dummy variant where more precise messages should be made - /// - /// The argument is just a unique identifier - Todo(usize), Expected { expected: String, got: String, @@ -41,6 +37,12 @@ pub enum ParseError { span: Span, inner_error: String, }, + Custom { + description: String, + label: String, + #[label("{label}")] + span: Span, + }, } impl std::fmt::Display for ParseError { @@ -53,9 +55,8 @@ impl std::fmt::Display for ParseError { write!(f, "failed to parse entire input") } Self::InvalidToken(_) => write!(f, "invalid token"), - Self::Todo(n) => write!(f, "add a nice message here {n}"), Self::Expected { expected, got, .. } => { - write!(f, "expected '{expected}' but got '{got}'") + write!(f, "expected {expected} but got '{got}'") } Self::InvalidLiteral { description, @@ -65,6 +66,12 @@ impl std::fmt::Display for ParseError { } => { write!(f, "found an invalid {description} literal '{token}': {inner_error}") } + Self::Custom { + description, + .. + } => { + write!(f, "{description}") + } } } } @@ -114,10 +121,10 @@ impl<'source> Parser<'source> { } /// Move the lexer forward and assert that it matches the token - fn take(&mut self, token: Token) -> ParseResult<()> { + fn take(&mut self, token: Token) -> ParseResult { let (next, span) = self.next()?; if next == token { - Ok(()) + Ok(span) } else { Err(ParseError::Expected { expected: token.to_string(), diff --git a/src/parser/value.rs b/src/parser/value.rs index 5046dfba..8494df8f 100644 --- a/src/parser/value.rs +++ b/src/parser/value.rs @@ -1,6 +1,13 @@ use crate::{ ast::{ - AccessExpr, AccessReceiver, AnonymousRecordValueExpr, ArgExprList, AsnLiteral, BooleanLiteral, ComputeExpr, ExtendedCommunityLiteral, FieldAccessExpr, HexLiteral, Identifier, IntegerLiteral, IpAddress, Ipv4Addr, Ipv6Addr, LargeCommunityLiteral, ListValueExpr, LiteralAccessExpr, LiteralExpr, MethodComputeExpr, Prefix, PrefixLength, PrefixLengthLiteral, PrefixLengthRange, PrefixMatchExpr, PrefixMatchType, StandardCommunityLiteral, StringLiteral, TypeIdentifier, TypedRecordValueExpr, ValueExpr + AccessExpr, AccessReceiver, AnonymousRecordValueExpr, ArgExprList, + AsnLiteral, BooleanLiteral, ComputeExpr, ExtendedCommunityLiteral, + FieldAccessExpr, HexLiteral, Identifier, IntegerLiteral, IpAddress, + Ipv4Addr, Ipv6Addr, LargeCommunityLiteral, ListValueExpr, + LiteralAccessExpr, LiteralExpr, MethodComputeExpr, Prefix, + PrefixLength, PrefixLengthLiteral, PrefixLengthRange, + PrefixMatchExpr, PrefixMatchType, StandardCommunityLiteral, + StringLiteral, TypeIdentifier, TypedRecordValueExpr, ValueExpr, }, parser::ParseError, token::Token, @@ -146,10 +153,17 @@ impl<'source> Parser<'source> { } fn ip_address(&mut self) -> ParseResult { - Ok(match self.next()?.0 { + let (token, span) = self.next()?; + Ok(match token { Token::IpV4(s) => IpAddress::Ipv4(Ipv4Addr(s.parse().unwrap())), Token::IpV6(s) => IpAddress::Ipv6(Ipv6Addr(s.parse().unwrap())), - _ => return Err(ParseError::Todo(15)), + _ => { + return Err(ParseError::Expected { + expected: "an IP address".into(), + got: token.to_string(), + span, + }) + } }) } @@ -288,7 +302,9 @@ impl<'source> Parser<'source> { /// | 'upto' PrefixLength /// | 'netmask' IpAddress /// ``` - fn try_prefix_match_type(&mut self) -> ParseResult> { + fn try_prefix_match_type( + &mut self, + ) -> ParseResult> { let match_type = if self.next_is(Token::Exact) { PrefixMatchType::Exact } else if self.next_is(Token::Longer) { From 9a7965351d08465747d4da7ac3439fd0b25b0291 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Thu, 29 Feb 2024 15:14:52 +0100 Subject: [PATCH 18/24] change unwraps to proper ParseError --- src/parser/value.rs | 95 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 76 insertions(+), 19 deletions(-) diff --git a/src/parser/value.rs b/src/parser/value.rs index 8494df8f..bec02c53 100644 --- a/src/parser/value.rs +++ b/src/parser/value.rs @@ -1,3 +1,5 @@ +use std::num::ParseIntError; + use crate::{ ast::{ AccessExpr, AccessReceiver, AnonymousRecordValueExpr, ArgExprList, @@ -155,8 +157,26 @@ impl<'source> Parser<'source> { fn ip_address(&mut self) -> ParseResult { let (token, span) = self.next()?; Ok(match token { - Token::IpV4(s) => IpAddress::Ipv4(Ipv4Addr(s.parse().unwrap())), - Token::IpV6(s) => IpAddress::Ipv6(Ipv6Addr(s.parse().unwrap())), + Token::IpV4(s) => IpAddress::Ipv4(Ipv4Addr( + s.parse::().map_err(|e| { + ParseError::InvalidLiteral { + description: "Ipv4 addresss".into(), + token: s.to_string(), + span, + inner_error: e.to_string(), + } + })?, + )), + Token::IpV6(s) => IpAddress::Ipv6(Ipv6Addr( + s.parse::().map_err(|e| { + ParseError::InvalidLiteral { + description: "Ipv6 addresss".into(), + token: s.to_string(), + span, + inner_error: e.to_string(), + } + })?, + )), _ => { return Err(ParseError::Expected { expected: "an IP address".into(), @@ -180,13 +200,32 @@ impl<'source> Parser<'source> { Token::Integer(s) => LiteralExpr::IntegerLiteral(IntegerLiteral( // This parse fails if the literal is too big, // it should be handled properly - s.parse().unwrap(), + s.parse::().map_err(|e| ParseError::InvalidLiteral { + description: "integer".into(), + token: token.to_string(), + span, + inner_error: e.to_string(), + })?, )), Token::Hex(s) => LiteralExpr::HexLiteral(HexLiteral( - u64::from_str_radix(&s[2..], 16).unwrap(), + u64::from_str_radix(&s[2..], 16).map_err(|e| { + ParseError::InvalidLiteral { + description: "hexadecimal integer".into(), + token: token.to_string(), + span, + inner_error: e.to_string(), + } + })?, )), Token::Asn(s) => LiteralExpr::AsnLiteral(AsnLiteral( - s[2..].parse::().unwrap(), + s[2..].parse::().map_err(|e| { + ParseError::InvalidLiteral { + description: "AS number".into(), + token: token.to_string(), + span, + inner_error: e.to_string(), + } + })?, )), Token::Bool(b) => LiteralExpr::BooleanLiteral(BooleanLiteral(b)), Token::Float => { @@ -199,28 +238,38 @@ impl<'source> Parser<'source> { // TODO: Change the AST so that it doesn't contain strings, but // routecore communities. - use routecore::bgp::communities::{self, Community}; - let parts: Vec<_> = s + use routecore::bgp::communities::Community; + + let parts = s .split(':') .map(|p| { if let Some(hex) = p.strip_prefix("0x") { - u32::from_str_radix(hex, 16).unwrap().to_string() + Ok(u32::from_str_radix(hex, 16)?.to_string()) } else { - p.to_string() + Ok(p.to_string()) } }) - .collect(); + .collect::, _>>() + .map_err(|e: ParseIntError| { + ParseError::InvalidLiteral { + description: "community".into(), + token: s.to_string(), + span: span.clone(), + inner_error: e.to_string(), + } + })?; let transformed = parts.join(":"); - let c: Community = transformed.parse().map_err( - |e: communities::ParseError| ParseError::InvalidLiteral { - description: "community".into(), - token: token.to_string(), - span, - inner_error: e.to_string(), - }, - )?; + let c: Community = + transformed.parse::().map_err(|e| { + ParseError::InvalidLiteral { + description: "community".into(), + token: token.to_string(), + span, + inner_error: e.to_string(), + } + })?; match c { Community::Standard(x) => { LiteralExpr::StandardCommunityLiteral( @@ -351,6 +400,14 @@ impl<'source> Parser<'source> { inner_error: String::new(), }); }; - Ok(PrefixLength(s[1..].parse().unwrap())) + let len = s[1..].parse::().map_err(|e| { + ParseError::InvalidLiteral { + description: "prefix length".into(), + token: token.to_string(), + span, + inner_error: e.to_string(), + } + })?; + Ok(PrefixLength(len)) } } From a0d5b50020f2ce3c0a7d453bcc4ceac233c953a3 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Thu, 29 Feb 2024 17:53:45 +0100 Subject: [PATCH 19/24] clean up apply scope parsing --- src/parser/filter_map.rs | 183 ++++++++++++++++++++++----------------- 1 file changed, 105 insertions(+), 78 deletions(-) diff --git a/src/parser/filter_map.rs b/src/parser/filter_map.rs index 74d228e1..d9278b8b 100644 --- a/src/parser/filter_map.rs +++ b/src/parser/filter_map.rs @@ -22,7 +22,7 @@ impl<'source> Parser<'source> { /// /// ```ebnf /// FilterMap ::= ( 'filter-map' | 'filter' ) Identifier - /// For With FilterMapBody + /// For? With? FilterMapBody /// ``` pub(super) fn filter_map(&mut self) -> ParseResult { let (token, span) = self.next()?; @@ -39,8 +39,8 @@ impl<'source> Parser<'source> { }; let ident = self.identifier()?; - let for_ident = self.for_clause()?; - let with_kv = self.with_clause()?; + let for_ident = self.try_for_clause()?; + let with_kv = self.try_with_clause()?; let body = self.filter_map_body(span)?; Ok(FilterMap { @@ -55,9 +55,9 @@ impl<'source> Parser<'source> { /// Parse the body of a filter-map or filter /// /// ```ebnf - /// FilterMapBody ::= '{' Define? FilterMapExpr+ Apply? '}' - /// Define ::= 'define' For With DefineBody - /// Apply ::= 'apply' For With ApplyBody + /// FilterMapBody ::= '{' Define FilterMapExpr+ Apply? '}' + /// Define ::= 'define' For? With? DefineBody + /// Apply ::= 'apply' For? With? ApplyBody /// ``` /// /// Not shown in the EBNF above, but the location of the define and apply @@ -80,8 +80,8 @@ impl<'source> Parser<'source> { span, }); } - let for_kv = self.for_clause()?; - let with_kv = self.with_clause()?; + let for_kv = self.try_for_clause()?; + let with_kv = self.try_with_clause()?; let body = self.define_body()?; define = Some(Define { for_kv, @@ -98,8 +98,8 @@ impl<'source> Parser<'source> { span, }); } - let for_kv = self.for_clause()?; - let with_kv = self.with_clause()?; + let for_kv = self.try_for_clause()?; + let with_kv = self.try_with_clause()?; let body = self.apply_body()?; apply = Some(ApplySection { for_kv, @@ -134,9 +134,9 @@ impl<'source> Parser<'source> { /// ```ebnf /// DefineBody ::= '{' RxTxType Use? Assignment* '}' /// - /// RxTxType ::= ( 'rx_tx' TypeIdentField ';' - /// | 'rx' TypeIdentField ';' 'tx' TypeIdentField ';' - /// | 'rx' TypeIdentField ) + /// RxTxType ::= 'rx_tx' TypeIdentField ';' + /// | 'rx' TypeIdentField ';' 'tx' TypeIdentField ';' + /// | 'rx' TypeIdentField ';' /// /// Use ::= 'use' Identifier Identifier /// @@ -222,72 +222,41 @@ impl<'source> Parser<'source> { /// Parse a scope of the body of apply /// /// ```ebnf - /// ApplyScope ::= 'filter' 'match' ValueExpr - /// 'not'? 'matching' - /// Actions ';' - /// - /// Actions ::= '{' (ValueExpr ';')* ( AcceptReject ';' )? '}' + /// ApplyScope ::= ApplyFilter | ApplyMatch /// ``` fn apply_scope(&mut self) -> ParseResult { - if self.peek_is(Token::Match) { - return self.apply_match(); - } - - self.take(Token::Filter)?; - - // This is not exactly self.match_operator because match ... with is - // not allowed. - let operator = if self.next_is(Token::Match) { - MatchOperator::Match - } else if self.next_is(Token::ExactlyOne) { - MatchOperator::ExactlyOne - } else if self.next_is(Token::Some) { - MatchOperator::Some - } else if self.next_is(Token::All) { - MatchOperator::All + let match_action = if self.peek_is(Token::Match) { + MatchActionExpr::PatternMatchAction(self.apply_match()?) + } else if self.peek_is(Token::Filter) { + MatchActionExpr::FilterMatchAction(self.apply_filter()?) } else { let (token, span) = self.next()?; return Err(ParseError::Expected { - expected: "'match', 'exactly-one', 'some' or 'all'".into(), + expected: "'match' or 'filter'".to_string(), got: token.to_string(), span, }); }; - let filter_ident = self.value_expr()?; - let negate = self.next_is(Token::Not); - self.take(Token::Matching)?; - self.take(Token::CurlyLeft)?; - - let mut actions = Vec::new(); - while !self.next_is(Token::CurlyRight) { - if let Some(accept_reject) = self.try_accept_reject()? { - self.take(Token::CurlyRight)?; - actions.push((None, Some(accept_reject))); - break; - } - - let val = self.value_expr()?; - self.take(Token::SemiColon)?; - actions.push((Some(val), None)); - } - - self.take(Token::SemiColon)?; - Ok(ApplyScope { scope: None, - match_action: MatchActionExpr::FilterMatchAction( - FilterMatchActionExpr { - operator, - negate, - actions, - filter_ident, - }, - ), + match_action, }) } - fn apply_match(&mut self) -> ParseResult { + /// Parse a match block in an apply section + /// + /// This differs from the other match construct because it can have + /// guards and the expressions are only action call expressions. + /// + /// ```ebnf + /// ApplyMatch ::= MatchOperator '{' ApplyMatchArm* '}' + /// ApplyMatchArm ::= Identifier ( '(' Identifier ')' )? Guard? '->' + /// Guard ::= '|' Identifier '(' (ValueExpr (',' ValueExpr)* ',')? ')' + /// ApplyMatchActions ::= ActionCallExpr ',' + /// | '{' (ActionCallExpr ';')* (AcceptReject ';' )? '}' ','? + /// ``` + fn apply_match(&mut self) -> ParseResult { let operator = self.match_operator()?; let mut match_arms = Vec::new(); self.take(Token::CurlyLeft)?; @@ -340,17 +309,75 @@ impl<'source> Parser<'source> { actions, }); } - Ok(ApplyScope { - scope: None, - match_action: MatchActionExpr::PatternMatchAction( - PatternMatchActionExpr { - operator, - match_arms, - }, - ), + Ok(PatternMatchActionExpr { + operator, + match_arms, + }) + } + + /// Parse a filter block in an apply section + /// + /// ```ebnf + /// ApplyFilter ::= 'filter' 'match' ValueExpr + /// 'not'? 'matching' + /// ApplyFilterActions ';' + /// ApplyFilterActions ::= '{' (ValueExpr ';')* ( AcceptReject ';' )? '}' + /// ``` + fn apply_filter(&mut self) -> ParseResult { + self.take(Token::Filter)?; + + // This is not exactly self.match_operator because match ... with is + // not allowed. + let operator = if self.next_is(Token::Match) { + MatchOperator::Match + } else if self.next_is(Token::ExactlyOne) { + MatchOperator::ExactlyOne + } else if self.next_is(Token::Some) { + MatchOperator::Some + } else if self.next_is(Token::All) { + MatchOperator::All + } else { + let (token, span) = self.next()?; + return Err(ParseError::Expected { + expected: "'match', 'exactly-one', 'some' or 'all'".into(), + got: token.to_string(), + span, + }); + }; + + let filter_ident = self.value_expr()?; + let negate = self.next_is(Token::Not); + self.take(Token::Matching)?; + self.take(Token::CurlyLeft)?; + + let mut actions = Vec::new(); + while !self.next_is(Token::CurlyRight) { + if let Some(accept_reject) = self.try_accept_reject()? { + self.take(Token::CurlyRight)?; + actions.push((None, Some(accept_reject))); + break; + } + + let val = self.value_expr()?; + self.take(Token::SemiColon)?; + actions.push((Some(val), None)); + } + + self.take(Token::SemiColon)?; + + Ok(FilterMatchActionExpr { + operator, + negate, + actions, + filter_ident, }) } + /// Parse an action call expr, resembling a method call + /// + /// ```ebnf + /// ActionCallExpr ::= Identifier '(' (ValueExpr (',' ValueExpr)* ',')? ')' + /// ``` fn action_call_expr(&mut self) -> ParseResult { let action_id = self.identifier()?; let args = if self.peek_is(Token::RoundLeft) { @@ -455,8 +482,8 @@ impl<'source> Parser<'source> { fn term(&mut self) -> ParseResult { self.take(Token::Term)?; let ident = self.identifier()?; - let for_kv = self.for_clause()?; - let with_kv = self.with_clause()?; + let for_kv = self.try_for_clause()?; + let with_kv = self.try_with_clause()?; let mut scopes = Vec::new(); self.take(Token::CurlyLeft)?; @@ -706,7 +733,7 @@ impl<'source> Parser<'source> { pub(super) fn action(&mut self) -> ParseResult { self.take(Token::Action)?; let ident = self.identifier()?; - let with_kv = self.with_clause()?; + let with_kv = self.try_with_clause()?; let mut expressions = Vec::new(); self.take(Token::CurlyLeft)?; @@ -727,7 +754,7 @@ impl<'source> Parser<'source> { return Err(ParseError::Custom { description: "an action can only be a compute epression or root method call".into(), label: "invalid action".into(), - span: span1.start..span2.end, + span: span1.start..span2.end, }); } } @@ -745,7 +772,7 @@ impl<'source> Parser<'source> { /// ```ebnf /// For ::= ( 'for' TypeIdentField)? /// ``` - fn for_clause(&mut self) -> ParseResult> { + fn try_for_clause(&mut self) -> ParseResult> { if self.next_is(Token::For) { Ok(Some(self.type_ident_field()?)) } else { @@ -758,7 +785,7 @@ impl<'source> Parser<'source> { /// ```ebnf /// With ::= ( 'with' TypeIdentField (',' TypeIdentField)*)? /// ``` - fn with_clause(&mut self) -> ParseResult> { + fn try_with_clause(&mut self) -> ParseResult> { let mut key_values = Vec::new(); if !self.next_is(Token::With) { From f4ca36b678b547a687dfbddfc951f6ce0944b009 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Fri, 1 Mar 2024 10:07:08 +0100 Subject: [PATCH 20/24] parser: clean up docstrings --- src/parser/filter_map.rs | 26 ++++++++++++++++++++++---- src/parser/mod.rs | 9 ++++++++- src/parser/rib_like.rs | 17 ++++++++++++++--- src/parser/value.rs | 1 + 4 files changed, 45 insertions(+), 8 deletions(-) diff --git a/src/parser/filter_map.rs b/src/parser/filter_map.rs index d9278b8b..0939879e 100644 --- a/src/parser/filter_map.rs +++ b/src/parser/filter_map.rs @@ -17,6 +17,7 @@ use crate::{ use super::{ParseError, ParseResult, Parser}; +/// # Parsing `filter-map` and `filter` sections impl<'source> Parser<'source> { /// Parse a filter-map or filter expression /// @@ -459,7 +460,7 @@ impl<'source> Parser<'source> { Ok(op) } - /// Parse a filter map expression, which is a term or an action + /// Parse a filter map section, which is a term or an action /// /// ```ebnf /// FilterMapExpr ::= Term | Action @@ -479,6 +480,11 @@ impl<'source> Parser<'source> { } } + /// Parse a term section + /// + /// ```ebnf + /// Term ::= Identifier For? With? '{' TermScope '}' + /// ``` fn term(&mut self) -> ParseResult { self.take(Token::Term)?; let ident = self.identifier()?; @@ -499,6 +505,12 @@ impl<'source> Parser<'source> { }) } + /// Parse a term scope + /// + /// ```ebnf + /// TermScope ::= 'match' Identifier 'with' '{' MatchArm '}' + /// | 'match' '{' ( LogicalExpr ';' )* '}' + /// ``` fn term_scope(&mut self) -> ParseResult { let operator = self.match_operator()?; @@ -513,7 +525,8 @@ impl<'source> Parser<'source> { } Ok(TermScope { - // TODO: remove the scope field (and rename this type probably) + // TODO: remove the scope field (and rename this type + // probably scope: None, operator, match_arms, @@ -533,6 +546,13 @@ impl<'source> Parser<'source> { } } + /// Parse a match arm + /// + /// ```ebnf + /// MatchArm ::= Identifier ( '(' Identifier ')' )? '->' MatchArmExpr + /// MatchArmExpr ::= LogicalExpr ',' + /// | '{' ( LogicalExpr ';' )* '}' ','? + /// ``` pub(super) fn match_arm( &mut self, ) -> ParseResult<(TermPatternMatchArm, Vec)> { @@ -692,8 +712,6 @@ impl<'source> Parser<'source> { /// ```ebnf /// CompareOp ::= '==' | '!=' | '<' | '<=' | '>' | '>=' | 'not'? 'in' /// ``` - /// - /// This function returns a span fn try_compare_operator( &mut self, ) -> ParseResult> { diff --git a/src/parser/mod.rs b/src/parser/mod.rs index c3bcf124..18e56ae4 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -181,7 +181,7 @@ impl<'source> Parser<'source> { } } -/// # Parsing complex expressions +/// # Parsing the syntax tree impl<'source> Parser<'source> { pub fn parse(input: &'source str) -> ParseResult { Self::run_parser(Self::tree, input) @@ -244,6 +244,10 @@ impl<'source> Parser<'source> { /// # Parsing identifiers impl<'source> Parser<'source> { + /// Parse an identifier + /// + /// The `contains` and `type` keywords are treated as identifiers, + /// because we already have tests that use these as names for methods. fn identifier(&mut self) -> ParseResult { let (token, span) = self.next()?; match token { @@ -263,6 +267,9 @@ impl<'source> Parser<'source> { } } + /// Parse a type identifier + /// + /// Currently, this is the same as [`Parser::identifier`]. fn type_identifier(&mut self) -> ParseResult { let (token, span) = self.next()?; match token { diff --git a/src/parser/rib_like.rs b/src/parser/rib_like.rs index 7403f333..f30b99b6 100644 --- a/src/parser/rib_like.rs +++ b/src/parser/rib_like.rs @@ -1,3 +1,8 @@ +//! Parsing constructs that have a syntax similar to rib declarations +//! +//! In other words, we parse the constructs that are type declarations. +//! These constructs are `rib`, `table`, `output-stream` and `type`. + use super::{ParseResult, Parser}; use crate::{ ast::{ @@ -7,8 +12,9 @@ use crate::{ token::Token, }; +/// # Rib-like declarations impl<'source> Parser<'source> { - /// Parse a rib expression + /// Parse a rib declaration /// /// ```ebnf /// Rib ::= 'rib' Identifier @@ -29,7 +35,7 @@ impl<'source> Parser<'source> { }) } - /// Parse a table expression + /// Parse a table declaration /// /// ```ebnf /// Table ::= 'table' Identifier @@ -50,7 +56,7 @@ impl<'source> Parser<'source> { }) } - /// Parse an output stream expression + /// Parse an output stream declaration /// /// ```ebnf /// OutputStream ::= 'output-stream' Identifier @@ -71,6 +77,11 @@ impl<'source> Parser<'source> { }) } + /// Parse a record type declaration + /// + /// ```ebnf + /// Type ::= 'type' TypeIdentifier RibBody + /// ``` pub(super) fn record_type_assignment( &mut self, ) -> ParseResult { diff --git a/src/parser/value.rs b/src/parser/value.rs index bec02c53..d8854ed1 100644 --- a/src/parser/value.rs +++ b/src/parser/value.rs @@ -17,6 +17,7 @@ use crate::{ use super::{ParseResult, Parser}; +/// # Parsing value expressions impl<'source> Parser<'source> { /// Parse a value expr /// From 866a56602d7a40e91dc9eac47368c43e1a27febd Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Fri, 1 Mar 2024 13:18:38 +0100 Subject: [PATCH 21/24] move token module to parser module --- src/lib.rs | 1 - src/parser/filter_map.rs | 25 +++++++++++-------------- src/parser/mod.rs | 3 ++- src/parser/rib_like.rs | 11 ++++------- src/{ => parser}/token.rs | 0 src/parser/value.rs | 20 ++++++++++---------- 6 files changed, 27 insertions(+), 33 deletions(-) rename src/{ => parser}/token.rs (100%) diff --git a/src/lib.rs b/src/lib.rs index 0421dce0..07596731 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,7 +5,6 @@ pub mod compiler; pub mod eval; pub mod parser; mod symbols; -pub mod token; pub mod traits; pub mod types; pub mod vm; diff --git a/src/parser/filter_map.rs b/src/parser/filter_map.rs index 0939879e..8d21aec3 100644 --- a/src/parser/filter_map.rs +++ b/src/parser/filter_map.rs @@ -1,21 +1,18 @@ use logos::Span; -use crate::{ - ast::{ - AcceptReject, AccessExpr, AccessReceiver, ActionCallExpr, - ActionSection, ActionSectionBody, AndExpr, ApplyBody, ApplyScope, - ApplySection, BooleanExpr, CompareArg, CompareExpr, CompareOp, - ComputeExpr, Define, DefineBody, FilterMap, FilterMapBody, - FilterMapExpr, FilterMatchActionExpr, FilterType, GroupedLogicalExpr, - ListCompareExpr, LogicalExpr, MatchActionExpr, MatchOperator, - NotExpr, OrExpr, PatternMatchActionArm, PatternMatchActionExpr, - RxTxType, TermBody, TermCallExpr, TermPatternMatchArm, TermScope, - TermSection, TypeIdentField, ValueExpr, - }, - token::Token, +use crate::ast::{ + AcceptReject, AccessExpr, AccessReceiver, ActionCallExpr, ActionSection, + ActionSectionBody, AndExpr, ApplyBody, ApplyScope, ApplySection, + BooleanExpr, CompareArg, CompareExpr, CompareOp, ComputeExpr, Define, + DefineBody, FilterMap, FilterMapBody, FilterMapExpr, + FilterMatchActionExpr, FilterType, GroupedLogicalExpr, ListCompareExpr, + LogicalExpr, MatchActionExpr, MatchOperator, NotExpr, OrExpr, + PatternMatchActionArm, PatternMatchActionExpr, RxTxType, TermBody, + TermCallExpr, TermPatternMatchArm, TermScope, TermSection, + TypeIdentField, ValueExpr, }; -use super::{ParseError, ParseResult, Parser}; +use super::{token::Token, ParseError, ParseResult, Parser}; /// # Parsing `filter-map` and `filter` sections impl<'source> Parser<'source> { diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 18e56ae4..725c50ec 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -2,7 +2,7 @@ use crate::ast::{ Identifier, RootExpr, SyntaxTree, TypeIdentifier, }; -use crate::token::Token; +use token::Token; use logos::{Lexer, Span, SpannedIter}; use miette::Diagnostic; use std::iter::Peekable; @@ -10,6 +10,7 @@ use std::iter::Peekable; mod filter_map; mod rib_like; mod value; +mod token; #[cfg(test)] mod test_expressions; diff --git a/src/parser/rib_like.rs b/src/parser/rib_like.rs index f30b99b6..04773743 100644 --- a/src/parser/rib_like.rs +++ b/src/parser/rib_like.rs @@ -3,13 +3,10 @@ //! In other words, we parse the constructs that are type declarations. //! These constructs are `rib`, `table`, `output-stream` and `type`. -use super::{ParseResult, Parser}; -use crate::{ - ast::{ - ListTypeIdentifier, OutputStream, RecordTypeAssignment, - RecordTypeIdentifier, Rib, RibBody, RibField, Table, TypeIdentField, - }, - token::Token, +use super::{token::Token, ParseResult, Parser}; +use crate::ast::{ + ListTypeIdentifier, OutputStream, RecordTypeAssignment, + RecordTypeIdentifier, Rib, RibBody, RibField, Table, TypeIdentField, }; /// # Rib-like declarations diff --git a/src/token.rs b/src/parser/token.rs similarity index 100% rename from src/token.rs rename to src/parser/token.rs diff --git a/src/parser/value.rs b/src/parser/value.rs index d8854ed1..82967f8c 100644 --- a/src/parser/value.rs +++ b/src/parser/value.rs @@ -12,10 +12,9 @@ use crate::{ StringLiteral, TypeIdentifier, TypedRecordValueExpr, ValueExpr, }, parser::ParseError, - token::Token, }; -use super::{ParseResult, Parser}; +use super::{token::Token, ParseResult, Parser}; /// # Parsing value expressions impl<'source> Parser<'source> { @@ -401,14 +400,15 @@ impl<'source> Parser<'source> { inner_error: String::new(), }); }; - let len = s[1..].parse::().map_err(|e| { - ParseError::InvalidLiteral { - description: "prefix length".into(), - token: token.to_string(), - span, - inner_error: e.to_string(), - } - })?; + let len = + s[1..] + .parse::() + .map_err(|e| ParseError::InvalidLiteral { + description: "prefix length".into(), + token: token.to_string(), + span, + inner_error: e.to_string(), + })?; Ok(PrefixLength(len)) } } From b41c8d31e8094276b426ec795e52b70243908017 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Fri, 1 Mar 2024 13:23:15 +0100 Subject: [PATCH 22/24] cargo clippy --- src/parser/value.rs | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/parser/value.rs b/src/parser/value.rs index 82967f8c..65cdad7f 100644 --- a/src/parser/value.rs +++ b/src/parser/value.rs @@ -110,19 +110,17 @@ impl<'source> Parser<'source> { access_expr.push(AccessExpr::MethodComputeExpr( MethodComputeExpr { ident, args }, )) + } else if let Some(AccessExpr::FieldAccessExpr( + FieldAccessExpr { field_names }, + )) = access_expr.last_mut() + { + field_names.push(ident); } else { - if let Some(AccessExpr::FieldAccessExpr(FieldAccessExpr { - field_names, - })) = access_expr.last_mut() - { - field_names.push(ident); - } else { - access_expr.push(AccessExpr::FieldAccessExpr( - FieldAccessExpr { - field_names: vec![ident], - }, - )) - } + access_expr.push(AccessExpr::FieldAccessExpr( + FieldAccessExpr { + field_names: vec![ident], + }, + )) } } From 35ce27bccf1554ba0e4bfec3b37a7f00306bccbc Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Fri, 1 Mar 2024 14:16:26 +0100 Subject: [PATCH 23/24] refactor route.rs --- src/types/builtin/route.rs | 202 +++++++------------------------------ 1 file changed, 37 insertions(+), 165 deletions(-) diff --git a/src/types/builtin/route.rs b/src/types/builtin/route.rs index 90c5a787..0d9f5126 100644 --- a/src/types/builtin/route.rs +++ b/src/types/builtin/route.rs @@ -78,11 +78,7 @@ impl From for MaterializedRoute { let status = raw_route.status_deltas.current(); let route_id = raw_route.raw_message.message_id; - let status = if let Ok(status) = status.try_into() { - status - } else { - RouteStatus::Unparsable - }; + let status = status.try_into().unwrap_or(RouteStatus::Unparsable); if let Ok(route) = raw_route.take_latest_attrs() { MaterializedRoute { @@ -209,43 +205,22 @@ impl RawRouteWithDeltas { pub fn with_peer_ip(self, peer_ip: IpAddr) -> Self { Self { - prefix: self.prefix, - raw_message: self.raw_message, - afi_safi: self.afi_safi, - path_id: self.path_id, peer_ip: Some(peer_ip), - peer_asn: self.peer_asn, - router_id: self.router_id, - attribute_deltas: self.attribute_deltas, - status_deltas: self.status_deltas, + ..self } } pub fn with_peer_asn(self, peer_asn: routecore::asn::Asn) -> Self { Self { - prefix: self.prefix, - raw_message: self.raw_message, - afi_safi: self.afi_safi, - path_id: self.path_id, - peer_ip: self.peer_ip, peer_asn: Some(peer_asn), - router_id: self.router_id, - attribute_deltas: self.attribute_deltas, - status_deltas: self.status_deltas, + ..self } } pub fn with_router_id(self, router_id: Arc) -> Self { Self { - prefix: self.prefix, - raw_message: self.raw_message, - afi_safi: self.afi_safi, - path_id: self.path_id, - peer_ip: self.peer_ip, - peer_asn: self.peer_asn, router_id: Some(router_id), - attribute_deltas: self.attribute_deltas, - status_deltas: self.status_deltas, + ..self } } @@ -453,11 +428,8 @@ impl RawRouteWithDeltas { &self, field_token: usize, ) -> Result, VmError> { - let current_set = if let Some(atrd) = - self.attribute_deltas.get_latest_change_set() - { - atrd - } else { + let Some(current_set) = self.attribute_deltas.get_latest_change_set() + else { return Err(VmError::InvalidRecord); }; @@ -485,135 +457,36 @@ impl RawRouteWithDeltas { &self, field_token: usize, ) -> Result { - match field_token.try_into()? { - RouteToken::AsPath => self - .raw_message - .raw_message - .0 - .aspath() - .ok() - .flatten() - .map(|p| p.to_hop_path()) - .map(TypeValue::from) - .ok_or(VmError::InvalidFieldAccess), - RouteToken::OriginType => self - .raw_message - .raw_message - .0 - .origin() - .ok() - .flatten() - .map(TypeValue::from) - .ok_or(VmError::InvalidFieldAccess), - RouteToken::NextHop => self - .raw_message - .raw_message - .0 - .find_next_hop(self.afi_safi) - .map_err(|_| VmError::InvalidFieldAccess) - .map(TypeValue::from), - RouteToken::MultiExitDisc => self - .raw_message - .raw_message - .0 - .multi_exit_disc() - .ok() - .flatten() - .map(TypeValue::from) - .ok_or(VmError::InvalidFieldAccess), - RouteToken::LocalPref => self - .raw_message - .raw_message - .0 - .local_pref() - .ok() - .flatten() - .map(TypeValue::from) - .ok_or(VmError::InvalidFieldAccess), - RouteToken::AtomicAggregate => Some(TypeValue::from( - self.raw_message - .raw_message - .0 - .is_atomic_aggregate() - .unwrap_or(false), - )) - .ok_or(VmError::InvalidFieldAccess), - RouteToken::Aggregator => self - .raw_message - .raw_message - .0 - .aggregator() - .ok() - .flatten() - .map(TypeValue::from) - .ok_or(VmError::InvalidFieldAccess), - RouteToken::Communities => self - .raw_message - .raw_message - .0 - .all_human_readable_communities() - .ok() - .flatten() - .map(TypeValue::from) - .ok_or(VmError::InvalidFieldAccess), - RouteToken::Prefix => Ok(self.prefix.into()), - RouteToken::Status => Ok(self.status_deltas.current()), - RouteToken::PeerIp => self - .peer_ip - .map(TypeValue::from) - .ok_or(VmError::InvalidFieldAccess), - RouteToken::PeerAsn => self - .peer_asn - .map(TypeValue::from) - .ok_or(VmError::InvalidFieldAccess), - // _ => None, - // originator_id: ChangedOption { - // value: None, - // changed: false, - // }, - // cluster_list: ChangedOption { - // value: None, - // changed: false, - // }, - // RouteToken::ExtendedCommunities => self.raw_message.raw_message.ext_communities() - // .map(|c| c.collect::>()).into(), - // RouteToken::As4Path => self.raw_message.raw_message.as4path(), - // as4_aggregator: ChangedOption { - // value: None, - // changed: false, - // }, - // connector: ChangedOption { - // value: None, - // changed: false, - // }, - // as_path_limit: ChangedOption { - // value: None, - // changed: false, - // }, - // pmsi_tunnel: ChangedOption { - // value: None, - // changed: false, - // }, - // ipv6_extended_communities: ChangedOption { - // value: None, - // changed: false, - // }, - // RouteToken::largeCommunities => self.raw_message.raw_message - // .large_communities() - // .map(|c| c.collect::>()).into() - // bgpsec_as_path: ChangedOption { - // value: None, - // changed: false, - // }, - // attr_set: ChangedOption { - // value: None, - // changed: false, - // }, - // rsrvd_development: ChangedOption { - // value: None, - // changed: false, - // }, - } + self.get_field_by_index_internal(field_token.try_into()?) + .ok_or(VmError::InvalidFieldAccess) + } + + pub fn get_field_by_index_internal( + &self, + route_token: RouteToken, + ) -> Option { + let msg = &self.raw_message.raw_message.0; + + Some(match route_token { + RouteToken::AsPath => msg.aspath().ok()??.to_hop_path().into(), + RouteToken::OriginType => msg.origin().ok()??.into(), + RouteToken::NextHop => { + msg.find_next_hop(self.afi_safi).ok()?.into() + } + RouteToken::MultiExitDisc => msg.multi_exit_disc().ok()??.into(), + RouteToken::LocalPref => msg.local_pref().ok()??.into(), + RouteToken::AtomicAggregate => { + msg.is_atomic_aggregate().unwrap_or(false).into() + } + RouteToken::Aggregator => msg.aggregator().ok()??.into(), + RouteToken::Communities => { + msg.all_human_readable_communities().ok()??.into() + } + RouteToken::Prefix => self.prefix.into(), + RouteToken::Status => self.status_deltas.current(), + RouteToken::PeerIp => self.peer_ip?.into(), + RouteToken::PeerAsn => self.peer_asn?.into(), + }) } pub fn status(&self) -> RouteStatus { @@ -1295,8 +1168,7 @@ impl RotoType for RouteStatus { _res_type: TypeDef, ) -> Result { Ok(TypeValue::Builtin(BuiltinTypeValue::Bool( - method_token - == usize::from(*self), + method_token == usize::from(*self), ))) } From b7d593c1ad0f7c2e280d73db4f06936046b4ad34 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Mon, 26 Feb 2024 13:19:33 +0100 Subject: [PATCH 24/24] remove and refactor methods on Rotolo --- examples/eval.rs | 2 +- examples/route.rs | 2 +- src/compiler/compile.rs | 228 +++++----------------------------- tests/accept_reject_simple.rs | 2 +- tests/bgp_filters.rs | 2 +- tests/bgp_update_message.rs | 2 +- tests/bmp_message.rs | 10 +- tests/helpers.rs | 6 +- tests/my_message.rs | 2 +- tests/records.rs | 3 +- tests/string_conversions.rs | 2 +- tests/two_filters.rs | 2 +- 12 files changed, 50 insertions(+), 213 deletions(-) diff --git a/examples/eval.rs b/examples/eval.rs index e91d4db3..28f143cf 100644 --- a/examples/eval.rs +++ b/examples/eval.rs @@ -18,7 +18,7 @@ fn test_data( // Compile the source code in this example let rotolo = Compiler::build(source_code)?; - let roto_pack = rotolo.retrieve_pack_as_arcs(&name)?; + let roto_pack = rotolo.retrieve_pack_as_refs(&name)?; // Create a payload type and instance to feed into a VM. let _count: TypeValue = 1_u32.into(); diff --git a/examples/route.rs b/examples/route.rs index 4bdbb636..ded41c54 100644 --- a/examples/route.rs +++ b/examples/route.rs @@ -17,7 +17,7 @@ fn test_data( // Compile the source code in this example let rotolo = Compiler::build(source_code)?; - let roto_pack = rotolo.retrieve_pack_as_arcs(&name)?; + let roto_pack = rotolo.retrieve_pack_as_refs(&name)?; // BGP UPDATE message containing MP_REACH_NLRI path attribute, // comprising 5 IPv6 NLRIs diff --git a/src/compiler/compile.rs b/src/compiler/compile.rs index 86c96716..0ae72e32 100644 --- a/src/compiler/compile.rs +++ b/src/compiler/compile.rs @@ -28,7 +28,7 @@ //! bump only into the variables that are actually used. use std::{ - collections::{HashMap, VecDeque}, + collections::VecDeque, fmt::{Display, Formatter}, sync::Arc, }; @@ -71,7 +71,6 @@ pub use crate::compiler::error::CompileError; /// Rotolo holds all the attributes of the compilation as owned. There are /// public methods to retrieve and iter over the packs filled with references /// or with arcs. - #[derive(Debug, Clone)] pub struct Rotolo { packs: Vec, @@ -79,152 +78,57 @@ pub struct Rotolo { } impl Rotolo { - pub fn inspect_all_arguments( - &self, - ) -> HashMap> { - let mut res = HashMap::>::new(); - for rp in self.packs.iter() { - res.insert( - rp.filter_map_name.clone(), - rp.arguments - .iter() - .map(|a| (a.get_name(), a.get_type())) - .collect::>(), - ); - } - - res - } - - pub fn is_success(&self) -> bool { - self.mis_compilations.is_empty() - } - - pub fn get_mis_compilations(&self) -> &Vec<(Scope, CompileError)> { + pub fn get_mis_compilations(&self) -> &[(Scope, CompileError)] { &self.mis_compilations } - pub fn take_pack_by_name( - &mut self, - name: &Scope, - ) -> Result { - let idx = self.packs.iter().position(|p| p.filter_map_name == *name); - if let Some(idx) = idx { - let p = self.packs.remove(idx); - Ok(p) + /// Get the packs from this Rotolo + /// + /// If any miscompilations occurred, those miscompilations will be + /// returned instead. + pub fn packs(self) -> Result, Vec<(Scope, CompileError)>> { + if self.mis_compilations.is_empty() { + Ok(self.packs) } else { - Err(CompileError::from(format!( - "Cannot find roto pack with name '{:?}'", - name - ))) + Err(self.mis_compilations) } } - pub fn packs_to_owned(&mut self) -> Vec { - std::mem::take(&mut self.packs) - } - - pub fn clean_packs_to_owned(mut self) -> Vec { - self.packs.retain(|p| { - matches!( - p.filter_type, - FilterType::Filter | FilterType::FilterMap - ) - }); - self.packs - } - - pub fn get_scopes(&self) -> Vec { - self.iter_as_refs().map(|p| p.0).collect::>() - } - // this iterator is not public because that would leak the (private) // RotoPack. - fn iter<'a, T: From<&'a RotoPack>>( - &'a self, - ) -> impl Iterator)> + 'a { + fn iter( + &self, + ) -> impl Iterator)> + { let mp = self - .get_mis_compilations() + .mis_compilations .iter() - .map(|mp| (mp.0.clone(), Err(mp.1.clone()))); + .map(|(scope, err)| (scope, Err(err))); self.packs .iter() - .map(|p| (p.filter_map_name.clone(), Ok(p.into()))) + .map(|p| (&p.filter_map_name, Ok(p))) .chain(mp) } - // this iterator is not public because that would leak the (private) - // RotoPack. - fn iter_clean<'a, T: From<&'a RotoPack>>( - &'a self, - ) -> impl Iterator + 'a { - self.packs - .iter() - .filter(|p| { - matches!( - p.filter_type, - FilterType::Filter | FilterType::FilterMap - ) - }) - .map(|p| p.into()) - } - - /// Returns an iterator that goes over all the mis-compilations and packs. - /// The items returned from this Iterator are Results over RotoPacks - /// filled with `Arc` over all collection-type attributes T inside the - /// pack, or, they are a Compile Error, indicating a mis-compilation. - pub fn iter_as_arcs( - &self, - ) -> impl Iterator)> - { - self.iter::() - } - - /// Returns an iterator that goes over all the mis-compilations and packs. - /// The items returned from this Iterator are Results over RotoPacks - /// filled with `Arc` over all collection-type attributes T inside the - /// pack, or, they are a Compile Error, indicating a mis-compilation. - pub fn iter_clean_as_arcs(&self) -> impl Iterator { - self.iter_clean::() - } - - /// Returns an iterator that goes over all the mis-compilations and packs. - /// The items returned from this Iterator are RotoPacks filled with &T - /// over all collection-type attributes T inside the pack, or, they are a - /// Compile Error, indicating a mis-compilation. - pub fn iter_as_refs( - &self, - ) -> impl Iterator)> - { - self.iter::() - } - // Not public, because it would leak the (private) RotoPack. fn retrieve_pack<'a, T: From<&'a RotoPack>>( &'a self, name: &'a Scope, ) -> Result { - if !self.mis_compilations.is_empty() { - return Err(self.mis_compilations[0].1.clone()); + match self.iter().find(|p| p.0 == name) { + None => Err(CompileError::from(format!( + "Can't find filter-map with specified name in this pack: {}", + name + ))), + Some((_, Ok(p))) => Ok(p.into()), + Some((_, Err(e))) => { + Err(CompileError::from(format!( + "The filter-map {} was defined for but contained the following error:\n{}", + name, + e + ))) + }, } - self.iter::<&RotoPack>() - .find(|p| p.0 == *name) - .ok_or_else(|| { - CompileError::from(format!( - "Can't find filter-map with specified name in this pack: {}", - name - )) - }) - .and_then(|p| { - if let Ok(p) = p.1 { - Ok(p.into()) - } else { - Err(p.1.err().unwrap_or(CompileError::from(format!( - "Can't retrieve filter-map name: {} for this roto pack.", - name - )))) - } - }) } /// Retrieves a pack by name, returns a Result over a pack that contains @@ -234,65 +138,7 @@ impl Rotolo { &'a self, name: &'a Scope, ) -> Result { - self.retrieve_pack::>(name) - } - - /// Retrieves a pack by name, returns a Result over a pack that contains - /// `Arc` for every collection-type attribute: T inside the pack. An - /// error indicates a mis-compilation for this Filter(Map). - pub fn retrieve_pack_as_arcs<'a>( - &'a self, - name: &'a Scope, - ) -> Result { - self.retrieve_pack::>(name) - } - - /// Retrieves the first pack in this rotolo, either a pack that contains - /// `&T` for every collection-type attribute: T inside the pack, or an - /// error indicating a mis-compilation for this Filter(Map). - pub fn retrieve_first_pack_as_arcs( - &self, - ) -> Result { - self.iter::<&RotoPack>() - .take(1) - .next() - .ok_or_else(|| { - CompileError::from( - "No filter-maps are available in this pack", - ) - }) - .and_then(|p| { - if let Ok(p) = p.1 { - Ok(p.into()) - } else { - Err(p.1.err().unwrap_or(CompileError::from( - "Can't find filter-map with specified name in this pack" - ))) - } - }) - } - - pub fn compile_all_arguments( - &self, - mut args: HashMap>, - ) -> Result, CompileError> { - let mut res = HashMap::::new(); - for pack in self.packs.iter() { - let args = std::mem::take( - args.get_mut(&pack.filter_map_name).ok_or_else(|| { - CompileError::Internal(format!( - "Cannot compile arguments: {:?}", - pack.filter_map_name - )) - })?, - ); - let cp = pack.arguments.compile_arguments(args); - if let Ok(map) = cp { - res.insert(pack.filter_map_name.clone(), map); - } - } - - Ok(res) + self.retrieve_pack(name) } pub fn compile_arguments( @@ -302,12 +148,7 @@ impl Rotolo { ) -> Result { let pack = self.packs.iter().find(|p| p.filter_map_name == *name); if let Some(pack) = pack { - let cp = pack.arguments.compile_arguments(args); - - match cp { - Ok(map) => Ok(map), - Err(err) => Err(err), - } + pack.arguments.compile_arguments(args) } else { Err(format!( "Can't find with specified filter-map name: {}", @@ -321,7 +162,7 @@ impl Rotolo { //------------ InternalPack ------------------------------------------------- /// A compiled Filter(Map) - +/// /// RotoPacks are the public representation of the compiled packs, where the /// collection-type fields can be `&T`, `Arc`, or really any other /// `T: AsRef<[T]>.`. @@ -435,8 +276,7 @@ impl< //------------ RotoPack ----------------------------------------------------- -// The internal representation of a RotoPack, where all values are owned. - +/// The internal representation of a RotoPack, where all values are owned. #[derive(Debug, Clone)] pub struct RotoPack { filter_map_name: Scope, diff --git a/tests/accept_reject_simple.rs b/tests/accept_reject_simple.rs index dfa5502c..b9a91c9c 100644 --- a/tests/accept_reject_simple.rs +++ b/tests/accept_reject_simple.rs @@ -19,7 +19,7 @@ fn test_data( // Compile the source code in this example let rotolo = Compiler::build(source_code)?; - let roto_pack = rotolo.retrieve_pack_as_arcs(&name)?; + let roto_pack = rotolo.retrieve_pack_as_refs(&name)?; let payload_type = TypeDef::new_record_type(vec![("asn", Box::new(TypeDef::Asn))])?; diff --git a/tests/bgp_filters.rs b/tests/bgp_filters.rs index 5252f167..297ddd83 100644 --- a/tests/bgp_filters.rs +++ b/tests/bgp_filters.rs @@ -38,7 +38,7 @@ fn test_data( } let rotolo = compile_res?; - let roto_pack = rotolo.retrieve_pack_as_arcs(&name)?; + let roto_pack = rotolo.retrieve_pack_as_refs(&name)?; // Create a BGP packet let prefix_str = "192.0.2.1"; diff --git a/tests/bgp_update_message.rs b/tests/bgp_update_message.rs index 50cb51e7..86f3f860 100644 --- a/tests/bgp_update_message.rs +++ b/tests/bgp_update_message.rs @@ -19,7 +19,7 @@ fn test_data( // Compile the source code in this example let rotolo = Compiler::build(source_code)?; - let roto_pack = rotolo.retrieve_pack_as_arcs(&name)?; + let roto_pack = rotolo.retrieve_pack_as_refs(&name)?; // BGP UPDATE message containing MP_REACH_NLRI path attribute, // comprising 5 IPv6 NLRIs diff --git a/tests/bmp_message.rs b/tests/bmp_message.rs index 7384a696..157a3d46 100644 --- a/tests/bmp_message.rs +++ b/tests/bmp_message.rs @@ -31,7 +31,7 @@ fn test_data( } let rotolo = compile_res?; - let roto_pack = rotolo.retrieve_pack_as_arcs(&name)?; + let roto_pack = rotolo.retrieve_pack_as_refs(&name)?; let rm_msg = BytesRecord::::new(buf.clone().into()); assert!(rm_msg.is_ok()); @@ -96,7 +96,7 @@ fn test_data_2( } let rotolo = compile_res?; - let roto_pack = rotolo.retrieve_pack_as_arcs(&name)?; + let roto_pack = rotolo.retrieve_pack_as_refs(&name)?; let buf = vec![ 0x03, 0x00, 0x00, 0x00, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -169,7 +169,7 @@ fn test_data_3( } let rotolo = compile_res?; - let roto_pack = rotolo.retrieve_pack_as_arcs(&name)?; + let roto_pack = rotolo.retrieve_pack_as_refs(&name)?; // BMP PeerDownNotification type 3, containing a BGP NOTIFICATION. let buf = vec![ @@ -240,7 +240,7 @@ fn test_data_4( } let rotolo = compile_res?; - let roto_pack = rotolo.retrieve_pack_as_arcs(&name)?; + let roto_pack = rotolo.retrieve_pack_as_refs(&name)?; trace!("Used Arguments"); trace!("{:#?}", &roto_pack.get_arguments()); @@ -303,7 +303,7 @@ fn compile_initiation_payload( } let rotolo = compile_res?; - let roto_pack = rotolo.retrieve_pack_as_arcs(&name)?; + let roto_pack = rotolo.retrieve_pack_as_refs(&name)?; // assert!(i_msg.is_ok()); diff --git a/tests/helpers.rs b/tests/helpers.rs index bd55959e..4dfb6cf4 100644 --- a/tests/helpers.rs +++ b/tests/helpers.rs @@ -61,11 +61,7 @@ impl<'a> TestCompiler<'a> { trace!("compile eval {}", self.name); let compile_res = self.compiler.compile().unwrap(); - let res = if compile_res.is_success() { - Ok(()) - } else { - Err(compile_res.get_mis_compilations().to_vec()) - }; + let res = compile_res.packs().map(|_| ()); match expect_success { false => assert!(res.is_err()), diff --git a/tests/my_message.rs b/tests/my_message.rs index aabccfda..b3318dbf 100644 --- a/tests/my_message.rs +++ b/tests/my_message.rs @@ -27,7 +27,7 @@ fn test_data( println!("miscompilations"); println!("{:?}", roto_packs.get_mis_compilations()); - let roto_pack = roto_packs.retrieve_first_pack_as_arcs()?; + let roto_pack = roto_packs.retrieve_pack_as_refs(&name)?; let _count: TypeValue = 1_u32.into(); let prefix: TypeValue = diff --git a/tests/records.rs b/tests/records.rs index 88b91fe9..dee91fc7 100644 --- a/tests/records.rs +++ b/tests/records.rs @@ -252,7 +252,8 @@ fn test_records_compare_6() { .to_string(); assert_eq!( test_run, - "This record: {\n\tasn: AS100\n } is of type Record {asn: Asn, i: U8, }, but we got a record with type Record {asn: Asn, }. It's not the same and cannot be converted." + "The filter-map filter-map 'in-filter-map' was defined for but contained the following error:\n\ + This record: {\n\tasn: AS100\n } is of type Record {asn: Asn, i: U8, }, but we got a record with type Record {asn: Asn, }. It's not the same and cannot be converted." ); } diff --git a/tests/string_conversions.rs b/tests/string_conversions.rs index c0a6bfd7..1c28381b 100644 --- a/tests/string_conversions.rs +++ b/tests/string_conversions.rs @@ -86,7 +86,7 @@ fn test_data( println!("miscompilations"); println!("{:?}", roto_packs.get_mis_compilations()); - let roto_pack = roto_packs.retrieve_first_pack_as_arcs()?; + let roto_pack = roto_packs.retrieve_pack_as_refs(&name)?; let _count: TypeValue = 1_u32.into(); let prefix: TypeValue = diff --git a/tests/two_filters.rs b/tests/two_filters.rs index 4d0aaedc..032f6fee 100644 --- a/tests/two_filters.rs +++ b/tests/two_filters.rs @@ -18,7 +18,7 @@ fn test_data( // Compile the source code in this example let rotolo = Compiler::build(source_code)?; - let roto_pack = rotolo.retrieve_pack_as_arcs(&name)?; + let roto_pack = rotolo.retrieve_pack_as_refs(&name)?; // BGP UPDATE message containing MP_REACH_NLRI path attribute, // comprising 5 IPv6 NLRIs