From b1759383b3dd3c2b08f6961bc842b9b231c269c1 Mon Sep 17 00:00:00 2001 From: Afsal Thaj Date: Thu, 19 Sep 2024 19:45:51 +1000 Subject: [PATCH] Proper custom errors, better error messages and flexible identifiers --- golem-rib/src/parser/binary_comparison.rs | 15 +- golem-rib/src/parser/boolean.rs | 15 +- golem-rib/src/parser/call.rs | 21 +- golem-rib/src/parser/cond.rs | 19 +- golem-rib/src/parser/errors.rs | 412 ++++++++---------- golem-rib/src/parser/flag.rs | 11 +- golem-rib/src/parser/identifier.rs | 75 +++- golem-rib/src/parser/let_binding.rs | 32 +- golem-rib/src/parser/literal.rs | 33 +- golem-rib/src/parser/multi_line_code_block.rs | 15 +- golem-rib/src/parser/not.rs | 12 +- golem-rib/src/parser/number.rs | 33 +- golem-rib/src/parser/optional.rs | 29 +- golem-rib/src/parser/pattern_match.rs | 103 +++-- golem-rib/src/parser/record.rs | 31 +- golem-rib/src/parser/result.rs | 23 +- golem-rib/src/parser/rib_expr.rs | 34 +- golem-rib/src/parser/select_field.rs | 47 +- golem-rib/src/parser/select_index.rs | 28 +- golem-rib/src/parser/sequence.rs | 11 +- golem-rib/src/parser/tuple.rs | 9 +- golem-rib/src/parser/type_name.rs | 32 +- 22 files changed, 648 insertions(+), 392 deletions(-) diff --git a/golem-rib/src/parser/binary_comparison.rs b/golem-rib/src/parser/binary_comparison.rs index 4f6c6e09d..cc8a63eb4 100644 --- a/golem-rib/src/parser/binary_comparison.rs +++ b/golem-rib/src/parser/binary_comparison.rs @@ -12,10 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. +use combine::parser::char::{spaces, string}; +use combine::{attempt, choice, ParseError, Parser}; + use crate::expr::Expr; +use crate::parser::errors::RibParseError; use crate::InferredType; -use combine::parser::char::{spaces, string}; -use combine::{attempt, choice, Parser}; pub fn binary( left_expr: impl Parser, @@ -23,6 +25,9 @@ pub fn binary( ) -> impl Parser where Input: combine::Stream, + RibParseError: Into< + >::StreamError, + >, { spaces().with( ( @@ -55,10 +60,12 @@ where #[cfg(test)] mod test { - use super::*; - use crate::parser::rib_expr::rib_expr; use combine::EasyParser; + use crate::parser::rib_expr::rib_expr; + + use super::*; + #[test] fn test_greater_than() { let input = "foo > bar"; diff --git a/golem-rib/src/parser/boolean.rs b/golem-rib/src/parser/boolean.rs index caed932c5..d04596d81 100644 --- a/golem-rib/src/parser/boolean.rs +++ b/golem-rib/src/parser/boolean.rs @@ -12,14 +12,19 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::expr::Expr; use combine::parser::char::spaces; use combine::parser::char::string; -use combine::{attempt, Parser}; +use combine::{attempt, ParseError, Parser}; + +use crate::expr::Expr; +use crate::parser::errors::RibParseError; pub fn boolean_literal() -> impl Parser where Input: combine::Stream, + RibParseError: Into< + >::StreamError, + >, { attempt(string("true")) .map(|_| Expr::boolean(true)) @@ -30,10 +35,12 @@ where #[cfg(test)] mod tests { - use super::*; - use crate::parser::rib_expr::rib_expr; use combine::EasyParser; + use crate::parser::rib_expr::rib_expr; + + use super::*; + #[test] fn test_boolean_true() { let input = "true"; diff --git a/golem-rib/src/parser/call.rs b/golem-rib/src/parser/call.rs index 18dfc0a00..d07c6e18b 100644 --- a/golem-rib/src/parser/call.rs +++ b/golem-rib/src/parser/call.rs @@ -16,20 +16,23 @@ use combine::error::Commit; use combine::parser::char::{alpha_num, string}; use combine::parser::char::{char, spaces}; use combine::parser::repeat::take_until; - -use combine::{any, attempt, between, choice, many1, optional, parser, token, Parser}; +use combine::sep_by; +use combine::{any, attempt, between, choice, many1, optional, parser, token, ParseError, Parser}; use crate::expr::Expr; use crate::function_name::{ ParsedFunctionName, ParsedFunctionReference, ParsedFunctionSite, SemVer, }; +use crate::parser::errors::RibParseError; use crate::parser::rib_expr::rib_expr; -use combine::sep_by; // A call can be a function or constructing an anonymous variant at the type of writing Rib which user expects to work at runtime pub fn call() -> impl Parser where Input: combine::Stream, + RibParseError: Into< + >::StreamError, + >, { ( function_name().skip(spaces()), @@ -50,6 +53,9 @@ where pub fn function_name() -> impl Parser where Input: combine::Stream, + RibParseError: Into< + >::StreamError, + >, { let identifier = || many1(alpha_num().or(token('-'))).map(|string: String| string); let namespace = many1(identifier()).message("namespace"); @@ -97,9 +103,12 @@ where let version = attempt(token('@')) .with(take_until(attempt(string(".{")))) - .map(|v: String| { + .and_then(|v: String| { let stripped = v.strip_suffix('.').unwrap_or(&v); - semver::Version::parse(stripped).expect("Failed to parse version") + match semver::Version::parse(stripped) { + Ok(version) => Ok(version), + Err(_) => Err(RibParseError::Message("Invalid version".to_string()).into()), + } }) .message("version"); @@ -196,14 +205,12 @@ where #[cfg(test)] mod function_call_tests { - use combine::EasyParser; use crate::expr::Expr; use crate::function_name::{ ParsedFunctionName, ParsedFunctionReference, ParsedFunctionSite, SemVer, }; - use crate::parser::rib_expr::rib_expr; #[test] diff --git a/golem-rib/src/parser/cond.rs b/golem-rib/src/parser/cond.rs index e2b543af9..75f9e3882 100644 --- a/golem-rib/src/parser/cond.rs +++ b/golem-rib/src/parser/cond.rs @@ -12,17 +12,27 @@ // See the License for the specific language governing permissions and // limitations under the License. +use combine::parser::char::{alpha_num, char, spaces, string}; +use combine::{attempt, not_followed_by, ParseError, Parser}; + use crate::expr::Expr; +use crate::parser::errors::RibParseError; use crate::parser::rib_expr::rib_expr; -use combine::parser::char::{spaces, string}; -use combine::{attempt, Parser}; pub fn conditional() -> impl Parser where Input: combine::Stream, + RibParseError: Into< + >::StreamError, + >, { // Use attempt only for the initial "if" to resolve ambiguity with identifiers - attempt(string("if").skip(spaces())).with( + attempt( + string("if") + .skip(not_followed_by(alpha_num().or(char('-')).or(char('_')))) + .skip(spaces()), + ) + .with( ( rib_expr().skip(spaces()), string("then").skip(spaces()), @@ -36,9 +46,10 @@ where #[cfg(test)] mod tests { - use super::*; use combine::EasyParser; + use super::*; + #[test] fn test_conditional() { let input = "if foo then bar else baz"; diff --git a/golem-rib/src/parser/errors.rs b/golem-rib/src/parser/errors.rs index 7c7dc7c5a..53ed19a24 100644 --- a/golem-rib/src/parser/errors.rs +++ b/golem-rib/src/parser/errors.rs @@ -1,65 +1,49 @@ -// A curated list of most common syntax errors, with the intent -// not regress user-facing error messages with changing parsing logic -#[cfg(test)] -mod error_tests { +use std::fmt::Display; - use crate::Expr; +use serde::de::StdError; - #[test] - fn test_pattern_match_error_missing_opening_curly_brace() { - let input = r#"match foo - ok(x) => x, - err(x) => x, - _ => bar, - }"#; - let result = Expr::from_text(input); - let expected_error = [ - "Parse error at line: 2, column: 13", - "Unexpected `o`", - "Expected whitespace or `{`", - "Invalid syntax for pattern match", - "", - ] - .join("\n"); +// Custom error type to hold specific error messages within individual parser +// which later gets converted to StreamError +#[derive(Debug, PartialEq, Clone)] +pub enum RibParseError { + Message(String), +} - assert_eq!(result, Err(expected_error)); +impl Display for RibParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + RibParseError::Message(msg) => write!(f, "{}", msg), + } } +} - #[test] - fn test_pattern_match_error_missing_closing_curly_brace() { - let input = r#"match foo { - ok(x) => x, - err(x) => x, - _ => bar - "#; - let result = Expr::from_text(input); - let expected_error = [ - "Parse error at line: 5, column: 11", - "Unexpected end of input", - "Expected whitespace or `}`", - "Invalid syntax for pattern match", - "", - ] - .join("\n"); - - assert_eq!(result, Err(expected_error)); +impl StdError for RibParseError { + fn description(&self) -> &str { + match self { + RibParseError::Message(msg) => msg, + } } +} + +// A curated list of most common syntax errors, with the intent +// to not regress user-facing error messages with changing parsing logic +#[cfg(test)] +mod invalid_syntax_tests { + use crate::Expr; - // TODO; Missing comma since we have multiple arms is a better error message - // This requires change in parsing logic to avoid using sep_by1 #[test] - fn test_pattern_match_error_missing_comma_between_arms() { - let input = r#"match foo { - ok(x) => x - err(x) => x, - _ => bar, - }"#; + fn dangling_some_in_rib_program() { + let input = r#" + let x = 1; + let y = 2; + let z = 3; + let result = some [x); + result"#; let result = Expr::from_text(input); let expected_error = [ - "Parse error at line: 3, column: 13", - "Unexpected `e`", - "Expected whitespace or `}`", - "Invalid syntax for pattern match", + "Parse error at line: 5, column: 24", + "some is a keyword", + "Invalid identifier", "", ] .join("\n"); @@ -68,18 +52,18 @@ mod error_tests { } #[test] - fn test_pattern_match_error_missing_arrow() { - let input = r#"match foo { - ok(x) x, - err(x) => x, - _ => bar, - }"#; + fn dangling_ok_in_rib_program() { + let input = r#" + let x = 1; + let y = 2; + let z = 3; + let result = ok [x); + result"#; let result = Expr::from_text(input); let expected_error = [ - "Parse error at line: 2, column: 19", - "Unexpected `x`", - "Expected whitespace or =>", - "Invalid syntax for pattern match", + "Parse error at line: 5, column: 24", + "ok is a keyword", + "Invalid identifier", "", ] .join("\n"); @@ -88,14 +72,18 @@ mod error_tests { } #[test] - fn test_let_binding_error_missing_variable() { - let input = r#"let = 1;"#; + fn dangling_err_in_rib_program() { + let input = r#" + let x = 1; + let y = 2; + let z = 3; + let result = err [x); + result"#; let result = Expr::from_text(input); let expected_error = [ - "Parse error at line: 1, column: 5", - "Unexpected `=`", - "Expected whitespace, letter, digit or `_`", - "Unable to parse binding variable", + "Parse error at line: 5, column: 24", + "err is a keyword", + "Invalid identifier", "", ] .join("\n"); @@ -104,24 +92,16 @@ mod error_tests { } #[test] - fn test_let_binding_error_missing_assignment() { - let input = r#"let x 1;"#; - let result = Expr::from_text(input); - let expected_error = [ - "Parse error at line: 1, column: 7", - "Unexpected `1`", - "Expected whitespace, `:`, whitespaces, bool, s8, u8, s16, u16, s32, u32, s64, u64, f32, f64, chr, str, list, tuple, option or `=`", - "" - ].join("\n"); - - assert_eq!(result, Err(expected_error)); - } - #[test] - fn test_conditional_no_then() { - let input = r#"if x 1 else 2"#; + fn invalid_conditional_in_rib_program() { + let input = r#" + let x = 1; + let y = 2; + let z = 3; + let result = { if x > y 1 else 0 }; + result"#; let result = Expr::from_text(input); let expected_error = [ - "Parse error at line: 1, column: 6", + "Parse error at line: 5, column: 35", "Unexpected `1`", "Expected whitespace or then", "", @@ -132,14 +112,18 @@ mod error_tests { } #[test] - fn test_result_ok_missing_braces() { - let input = r#"ok(1"#; + fn invalid_flag_in_rib_program() { + let input = r#" + let x = 1; + let y = 2; + let z = 3; + let result = {x, y, z; + result"#; let result = Expr::from_text(input); let expected_error = [ - "Parse error at line: 1, column: 5", - "Unexpected end of input", - "Expected whitespace or `)`", - "Invalid syntax for Result type", + "Parse error at line: 5, column: 26", + "Unexpected `,`", + "Expected whitespace or `}`", "", ] .join("\n"); @@ -148,14 +132,22 @@ mod error_tests { } #[test] - fn test_result_err_missing_braces() { - let input = r#"err(1"#; + fn invalid_pattern_match_in_rib_program() { + let input = r#" + let x = 1; + let y = 2; + let z = 3; + let result = match x { + ok(x) => x + err(x) => x, + }; + result"#; let result = Expr::from_text(input); let expected_error = [ - "Parse error at line: 1, column: 6", - "Unexpected end of input", - "Expected whitespace or `)`", - "Invalid syntax for Result type", + "Parse error at line: 7, column: 13", + "Unexpected `e`", + "Expected whitespace or `}`", + "Invalid syntax for pattern match", "", ] .join("\n"); @@ -164,14 +156,18 @@ mod error_tests { } #[test] - fn test_option_some_missing_braces() { - let input = r#"some(1"#; + fn invalid_record_in_rib_program() { + let input = r#" + let x = 1; + let y = 2; + let z = 3; + let result = {a : b, c : d; + result"#; let result = Expr::from_text(input); let expected_error = [ - "Parse error at line: 1, column: 7", - "Unexpected end of input", - "Expected whitespace or `)`", - "Invalid syntax for Option type", + "Parse error at line: 5, column: 27", + "Unexpected `:`", + "Expected whitespace or `}`", "", ] .join("\n"); @@ -180,13 +176,19 @@ mod error_tests { } #[test] - fn test_option_none_redundant_braces() { - let input = r#"none()"#; + fn invalid_sequence_in_rib_program() { + let input = r#" + let x = 1; + let y = 2; + let z = 3; + let result = [x, y, z; + result"#; let result = Expr::from_text(input); let expected_error = [ - "Parse error at line: 1, column: 5", - "Unexpected `(`", - "Expected `;`, whitespaces or end of input", + "Parse error at line: 5, column: 32", + "Unexpected `;`", + "Expected `,`, whitespaces or `]`", + "Invalid syntax for sequence type", "", ] .join("\n"); @@ -195,16 +197,19 @@ mod error_tests { } #[test] - fn test_syntax_error_in_rib_program_missing_semi_column() { + fn invalid_tuple_in_rib_program() { let input = r#" let x = 1; - let y = 2 - y"#; + let y = 2; + let z = 3; + let result = (x, y, z; + result"#; let result = Expr::from_text(input); let expected_error = [ - "Parse error at line: 4, column: 11", - "Unexpected `y`", - "Expected `;`, whitespaces or end of input", + "Parse error at line: 5, column: 32", + "Unexpected `;`", + "Expected `,`, whitespaces or `)`", + "Invalid syntax for tuple type", "", ] .join("\n"); @@ -213,18 +218,18 @@ mod error_tests { } #[test] - fn test_syntax_error_in_rib_program_let_statement() { - let input = r#" - let x = 1; - let y = 2; - let z = 3; - let"#; + fn missing_arrow_in_pattern_match() { + let input = r#"match foo { + ok(x) x, + err(x) => x, + _ => bar, + }"#; let result = Expr::from_text(input); let expected_error = [ - "Parse error at line: 5, column: 14", - "Unexpected end of input", - "Expected whitespace, letter, digit or `_`", - "Unable to parse binding variable", + "Parse error at line: 2, column: 19", + "Unexpected `x`", + "Expected whitespace or =>", + "Invalid syntax for pattern match", "", ] .join("\n"); @@ -233,18 +238,14 @@ mod error_tests { } #[test] - fn test_syntax_error_in_rib_program_if_cond() { - let input = r#" - let x = 1; - let y = 2; - let z = 3; - let result = { if x > y 1 else 0 }; - result"#; + fn missing_closing_bracket_in_err() { + let input = r#"err(1"#; let result = Expr::from_text(input); let expected_error = [ - "Parse error at line: 5, column: 35", - "Unexpected `1`", - "Expected whitespace or then", + "Parse error at line: 1, column: 6", + "Unexpected end of input", + "Expected whitespace or `)`", + "Invalid syntax for Result type", "", ] .join("\n"); @@ -253,22 +254,14 @@ mod error_tests { } #[test] - fn test_syntax_error_in_rib_program_pattern_match() { - let input = r#" - let x = 1; - let y = 2; - let z = 3; - let result = match x { - ok(x) => x - err(x) => x, - }; - result"#; + fn missing_closing_bracket_in_ok() { + let input = r#"ok(1"#; let result = Expr::from_text(input); let expected_error = [ - "Parse error at line: 7, column: 13", - "Unexpected `e`", - "Expected whitespace or `}`", - "Invalid syntax for pattern match", + "Parse error at line: 1, column: 5", + "Unexpected end of input", + "Expected whitespace or `)`", + "Invalid syntax for Result type", "", ] .join("\n"); @@ -277,19 +270,14 @@ mod error_tests { } #[test] - fn test_syntax_error_in_rib_program_err() { - let input = r#" - let x = 1; - let y = 2; - let z = 3; - let result = err1(x); - result"#; + fn missing_closing_bracket_in_some() { + let input = r#"some(1"#; let result = Expr::from_text(input); let expected_error = [ - "Parse error at line: 5, column: 27", - "Unexpected `1`", - "Expected `(`", - "Invalid syntax for Result type", + "Parse error at line: 1, column: 7", + "Unexpected end of input", + "Expected whitespace or `)`", + "Invalid syntax for Option type", "", ] .join("\n"); @@ -298,19 +286,18 @@ mod error_tests { } #[test] - fn test_syntax_error_in_rib_program_ok() { - let input = r#" - let x = 1; - let y = 2; - let z = 3; - let result = ok1(x); - result"#; + fn missing_closing_braces_in_pattern_match() { + let input = r#"match foo { + ok(x) => x, + err(x) => x, + _ => bar + "#; let result = Expr::from_text(input); let expected_error = [ - "Parse error at line: 5, column: 26", - "Unexpected `1`", - "Expected `(`", - "Invalid syntax for Result type", + "Parse error at line: 5, column: 11", + "Unexpected end of input", + "Expected whitespace or `}`", + "Invalid syntax for pattern match", "", ] .join("\n"); @@ -319,19 +306,18 @@ mod error_tests { } #[test] - fn test_syntax_error_in_rib_program_option_some() { - let input = r#" - let x = 1; - let y = 2; - let z = 3; - let result = some1(x); - result"#; + fn missing_comma_in_pattern_match() { + let input = r#"match foo { + ok(x) => x + err(x) => x, + _ => bar, + }"#; let result = Expr::from_text(input); let expected_error = [ - "Parse error at line: 5, column: 28", - "Unexpected `1`", - "Expected `(`", - "Invalid syntax for Option type", + "Parse error at line: 3, column: 13", + "Unexpected `e`", + "Expected whitespace or `}`", + "Invalid syntax for pattern match", "", ] .join("\n"); @@ -340,19 +326,18 @@ mod error_tests { } #[test] - fn test_syntax_error_in_rib_program_invalid_tuple() { - let input = r#" - let x = 1; - let y = 2; - let z = 3; - let result = (x, y, z; - result"#; + fn missing_opening_braces_in_pattern_match() { + let input = r#"match foo + ok(x) => x, + err(x) => x, + _ => bar, + }"#; let result = Expr::from_text(input); let expected_error = [ - "Parse error at line: 5, column: 32", - "Unexpected `;`", - "Expected `,`, whitespaces or `)`", - "Invalid syntax for tuple type", + "Parse error at line: 2, column: 13", + "Unexpected `o`", + "Expected whitespace or `{`", + "Invalid syntax for pattern match", "", ] .join("\n"); @@ -361,19 +346,16 @@ mod error_tests { } #[test] - fn test_syntax_error_in_rib_program_invalid_sequence() { + fn missing_semi_column() { let input = r#" let x = 1; - let y = 2; - let z = 3; - let result = [x, y, z; - result"#; + let y = 2 + y"#; let result = Expr::from_text(input); let expected_error = [ - "Parse error at line: 5, column: 32", - "Unexpected `;`", - "Expected `,`, whitespaces or `]`", - "Invalid syntax for sequence type", + "Parse error at line: 4, column: 11", + "Unexpected `y`", + "Expected `;`, whitespaces or end of input", "", ] .join("\n"); @@ -382,18 +364,13 @@ mod error_tests { } #[test] - fn test_syntax_error_in_rib_program_invalid_flag() { - let input = r#" - let x = 1; - let y = 2; - let z = 3; - let result = {x, y, z; - result"#; + fn missing_then_in_conditional() { + let input = r#"if x 1 else 2"#; let result = Expr::from_text(input); let expected_error = [ - "Parse error at line: 5, column: 26", - "Unexpected `,`", - "Expected whitespace or `}`", + "Parse error at line: 1, column: 6", + "Unexpected `1`", + "Expected whitespace or then", "", ] .join("\n"); @@ -402,18 +379,13 @@ mod error_tests { } #[test] - fn test_syntax_error_in_rib_program_invalid_record() { - let input = r#" - let x = 1; - let y = 2; - let z = 3; - let result = {a : b, c : d; - result"#; + fn redundant_parenthesis_in_none() { + let input = r#"none()"#; let result = Expr::from_text(input); let expected_error = [ - "Parse error at line: 5, column: 27", - "Unexpected `:`", - "Expected whitespace or `}`", + "Parse error at line: 1, column: 5", + "Unexpected `(`", + "Expected `;`, whitespaces or end of input", "", ] .join("\n"); diff --git a/golem-rib/src/parser/flag.rs b/golem-rib/src/parser/flag.rs index f7ba61c6b..81042d59c 100644 --- a/golem-rib/src/parser/flag.rs +++ b/golem-rib/src/parser/flag.rs @@ -13,18 +13,22 @@ // limitations under the License. use combine::parser::char::digit; +use combine::sep_by; use combine::{ between, many1, parser::char::{char as char_, letter, spaces}, - Parser, + ParseError, Parser, }; use crate::expr::Expr; -use combine::sep_by; +use crate::parser::errors::RibParseError; pub fn flag() -> impl Parser where Input: combine::Stream, + RibParseError: Into< + >::StreamError, + >, { let flag_name = many1(letter().or(char_('_')).or(digit()).or(char_('-'))) .map(|s: Vec| s.into_iter().collect()); @@ -43,10 +47,11 @@ where #[cfg(test)] mod tests { + use combine::EasyParser; + use crate::parser::rib_expr::rib_expr; use super::*; - use combine::EasyParser; #[test] fn test_empty_flag() { diff --git a/golem-rib/src/parser/identifier.rs b/golem-rib/src/parser/identifier.rs index aa795820d..d1f454cf4 100644 --- a/golem-rib/src/parser/identifier.rs +++ b/golem-rib/src/parser/identifier.rs @@ -12,47 +12,82 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::expr::Expr; use combine::parser::char::digit; -use combine::parser::char::{char as char_, letter, spaces}; -use combine::{many, Parser}; +use combine::parser::char::{char as char_, letter}; +use combine::{many, ParseError, Parser, Stream}; + +use crate::expr::Expr; +use crate::parser::errors::RibParseError; + +const RESERVED_KEYWORDS: &[&str] = &[ + "if", "then", "else", "match", "ok", "some", "err", "none", "let", +]; pub fn identifier() -> impl Parser where - Input: combine::Stream, + Input: Stream, + Input::Error: ParseError, + RibParseError: Into< + >::StreamError, + >, { identifier_text() .map(Expr::identifier) .message("Invalid identifier") } - pub fn identifier_text() -> impl Parser where - Input: combine::Stream, + Input: Stream, + Input::Error: ParseError, + RibParseError: Into< + >::StreamError, + >, { - spaces().with( - ( - letter(), - many(letter().or(digit()).or(char_('_').or(char_('-')))), - ) - .map(|(a, s): (char, Vec)| { - let mut vec = vec![a]; - vec.extend(s); - vec.iter().collect::() - }), + ( + letter(), + many(letter().or(digit()).or(char_('_').or(char_('-')))), ) + .map(|(a, s): (char, Vec)| { + let mut vec = vec![a]; + vec.extend(s); + vec.iter().collect::() + }) + .and_then(|ident: String| { + if RESERVED_KEYWORDS.contains(&ident.as_str()) { + Err(RibParseError::Message(format!("{} is a keyword", ident))) + } else { + Ok(ident) + } + }) } #[cfg(test)] mod tests { use super::*; - use crate::parser::rib_expr::rib_expr; - use combine::EasyParser; #[test] fn test_identifier() { let input = "foo"; - let result = rib_expr().easy_parse(input); - assert_eq!(result, Ok((Expr::identifier("foo"), ""))); + let result = Expr::from_text(input); + assert_eq!(result, Ok(Expr::identifier("foo"))); + } + + #[test] + fn test_identifiers_containing_key_words() { + let inputs = RESERVED_KEYWORDS.iter().flat_map(|k| { + vec![ + format!("{}foo", k), + format!("{}_foo", k), + format!("{}-foo", k), + format!("foo{}", k), + format!("foo_{}", k), + format!("foo-{}", k), + ] + }); + + for input in inputs { + let result = Expr::from_text(&input); + assert!(result.is_ok()) + } } } diff --git a/golem-rib/src/parser/let_binding.rs b/golem-rib/src/parser/let_binding.rs index ad851525d..88cc8c37e 100644 --- a/golem-rib/src/parser/let_binding.rs +++ b/golem-rib/src/parser/let_binding.rs @@ -12,22 +12,30 @@ // See the License for the specific language governing permissions and // limitations under the License. -use combine::parser::char::digit; +use combine::parser::char::{alpha_num, char}; use combine::{ - attempt, many1, optional, - parser::char::{char as char_, letter, spaces, string}, - Parser, + attempt, not_followed_by, optional, + parser::char::{char as char_, spaces, string}, + ParseError, Parser, }; use crate::expr::Expr; +use crate::parser::errors::RibParseError; +use crate::parser::identifier::identifier_text; use crate::parser::rib_expr::rib_expr; use crate::parser::type_name::parse_type_name; pub fn let_binding() -> impl Parser where Input: combine::Stream, + RibParseError: Into< + >::StreamError, + >, { - attempt(string("let")).skip(spaces()).with( + attempt( + string("let").skip(not_followed_by(alpha_num().or(char('-')).or(char('_'))).skip(spaces())), + ) + .with( ( let_variable().skip(spaces()), optional( @@ -52,18 +60,22 @@ where fn let_variable() -> impl Parser where Input: combine::Stream, + Input::Error: ParseError, + RibParseError: Into< + >::StreamError, + >, { - many1(letter().or(digit()).or(char_('_'))) - .map(|s: Vec| s.into_iter().collect()) - .message("Unable to parse binding variable") + identifier_text().message("Unable to parse binding variable") } #[cfg(test)] mod tests { - use super::*; + use combine::EasyParser; + use crate::parser::type_name::TypeName; use crate::{InferredType, VariableId}; - use combine::EasyParser; + + use super::*; #[test] fn test_let_binding() { diff --git a/golem-rib/src/parser/literal.rs b/golem-rib/src/parser/literal.rs index b395122b9..efb93281a 100644 --- a/golem-rib/src/parser/literal.rs +++ b/golem-rib/src/parser/literal.rs @@ -12,15 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::expr::Expr; +use combine::{parser, ParseError, Stream}; +use crate::expr::Expr; +use crate::parser::errors::RibParseError; use crate::parser::literal::internal::literal_; -use combine::{parser, Stream}; parser! { pub fn literal[Input]()(Input) -> Expr where [ - Input: Stream + Input: Stream, + RibParseError: Into<>::StreamError>, ] { literal_() @@ -28,18 +30,22 @@ parser! { } mod internal { - use crate::expr::Expr; - use crate::parser::rib_expr::rib_expr; use combine::parser::char::{char as char_, char, letter, space}; use combine::parser::char::{digit, spaces}; use combine::parser::repeat::many; + use combine::{between, choice, many1, sep_by, ParseError, Parser}; - use combine::{between, choice, many1, sep_by, Parser}; + use crate::expr::Expr; + use crate::parser::errors::RibParseError; + use crate::parser::rib_expr::rib_expr; // Literal can handle string interpolation pub fn literal_() -> impl Parser where Input: combine::Stream, + RibParseError: Into< + >::StreamError, + >, { spaces() .with( @@ -64,6 +70,9 @@ mod internal { fn static_part() -> impl Parser where Input: combine::Stream, + RibParseError: Into< + >::StreamError, + >, { many1( letter().or(space()).or(digit()).or(char_('_').or(char_('-') @@ -78,6 +87,9 @@ mod internal { fn interpolation() -> impl Parser where Input: combine::Stream, + RibParseError: Into< + >::StreamError, + >, { between( char_('$').with(char_('{')).skip(spaces()), @@ -89,6 +101,9 @@ mod internal { pub fn block() -> impl Parser where Input: combine::Stream, + RibParseError: Into< + >::StreamError, + >, { spaces().with( sep_by(rib_expr().skip(spaces()), char(';').skip(spaces())).map( @@ -106,11 +121,13 @@ mod internal { #[cfg(test)] mod tests { - use super::*; - use crate::parser::rib_expr::{rib_expr, rib_program}; use combine::stream::position; use combine::EasyParser; + use crate::parser::rib_expr::{rib_expr, rib_program}; + + use super::*; + #[test] fn test_empty_literal() { let input = "\"\""; diff --git a/golem-rib/src/parser/multi_line_code_block.rs b/golem-rib/src/parser/multi_line_code_block.rs index 9354e02b9..07b7dca8d 100644 --- a/golem-rib/src/parser/multi_line_code_block.rs +++ b/golem-rib/src/parser/multi_line_code_block.rs @@ -15,14 +15,18 @@ use combine::{ between, parser::char::{char as char_, spaces}, - Parser, + ParseError, Parser, }; use crate::expr::Expr; +use crate::parser::errors::RibParseError; pub fn multi_line_block() -> impl Parser where Input: combine::Stream, + RibParseError: Into< + >::StreamError, + >, { spaces().with(between( char_('{').skip(spaces()), @@ -32,15 +36,20 @@ where } mod internal { + use combine::parser::char::{char, spaces}; + use combine::{sep_by, ParseError, Parser}; + + use crate::parser::errors::RibParseError; use crate::parser::rib_expr::rib_expr; use crate::Expr; - use combine::parser::char::{char, spaces}; - use combine::{sep_by, Parser}; // A block is different to a complete rib-program that the it may not be the end of the stream pub fn block() -> impl Parser where Input: combine::Stream, + RibParseError: Into< + >::StreamError, + >, { spaces().with( sep_by(rib_expr().skip(spaces()), char(';').skip(spaces())).map( diff --git a/golem-rib/src/parser/not.rs b/golem-rib/src/parser/not.rs index 494190727..eccfd419f 100644 --- a/golem-rib/src/parser/not.rs +++ b/golem-rib/src/parser/not.rs @@ -12,14 +12,19 @@ // See the License for the specific language governing permissions and // limitations under the License. +use combine::parser::char::{spaces, string}; +use combine::{ParseError, Parser}; + use crate::expr::Expr; +use crate::parser::errors::RibParseError; use crate::parser::rib_expr::rib_expr; -use combine::parser::char::{spaces, string}; -use combine::Parser; pub fn not() -> impl Parser where Input: combine::Stream, + RibParseError: Into< + >::StreamError, + >, { spaces() .with( @@ -32,9 +37,10 @@ where #[cfg(test)] mod tests { - use super::*; use combine::EasyParser; + use super::*; + #[test] fn test_not_identifier() { let input = "!foo"; diff --git a/golem-rib/src/parser/number.rs b/golem-rib/src/parser/number.rs index b35171567..f2d802c56 100644 --- a/golem-rib/src/parser/number.rs +++ b/golem-rib/src/parser/number.rs @@ -12,17 +12,20 @@ // See the License for the specific language governing permissions and // limitations under the License. +use combine::parser::char::{char, digit, spaces}; use combine::{many1, optional, ParseError, Parser}; use crate::expr::Expr; -use combine::parser::char::{char, digit, spaces}; - +use crate::parser::errors::RibParseError; use crate::parser::type_name::{parse_basic_type, TypeName}; pub fn number() -> impl Parser where Input: combine::Stream, Input::Error: ParseError, + RibParseError: Into< + >::StreamError, + >, { spaces() .with( @@ -30,17 +33,20 @@ where many1(digit().or(char('-')).or(char('.'))), optional(parse_basic_type()), ) - .map(|(s, typ_name): (Vec, Option)| { - let primitive = s - .into_iter() - .collect::() - .parse::() - .expect("digit"); + .and_then(|(s, typ_name): (Vec, Option)| { + let primitive = s.into_iter().collect::().parse::(); - if let Some(typ_name) = typ_name { - Expr::number_with_type_name(primitive, typ_name.clone()) - } else { - Expr::number(primitive) + match primitive { + Ok(primitive) => { + if let Some(typ_name) = typ_name { + Ok(Expr::number_with_type_name(primitive, typ_name.clone())) + } else { + Ok(Expr::number(primitive)) + } + } + Err(_) => { + Err(RibParseError::Message("Unable to parse number".to_string()).into()) + } } }), ) @@ -49,9 +55,10 @@ where #[cfg(test)] mod tests { - use super::*; use combine::EasyParser; + use super::*; + #[test] fn test_number() { let input = "123"; diff --git a/golem-rib/src/parser/optional.rs b/golem-rib/src/parser/optional.rs index bd7257e87..b93ca49b1 100644 --- a/golem-rib/src/parser/optional.rs +++ b/golem-rib/src/parser/optional.rs @@ -12,32 +12,45 @@ // See the License for the specific language governing permissions and // limitations under the License. +use combine::parser::char::alpha_num; +use combine::parser::char::spaces; use combine::{ - attempt, between, + attempt, choice, not_followed_by, parser::char::{char, string}, - Parser, + ParseError, Parser, }; use crate::expr::Expr; +use crate::parser::errors::RibParseError; use super::rib_expr::rib_expr; pub fn option() -> impl Parser where Input: combine::Stream, + RibParseError: Into< + >::StreamError, + >, { - attempt(string("some")) - .with(between(char('('), char(')'), rib_expr())) - .map(|expr| Expr::option(Some(expr))) - .or(attempt(string("none")).map(|_| Expr::option(None))) - .message("Invalid syntax for Option type") + choice(( + attempt(string("some").skip(char('('))).with( + rib_expr() + .skip(spaces()) + .skip(char(')')) + .map(|expr| Expr::option(Some(expr))), + ), + (attempt(string("none").skip(not_followed_by(alpha_num().or(char('-')).or(char('_'))))) + .map(|_| Expr::option(None))), + )) + .message("Invalid syntax for Option type") } #[cfg(test)] mod tests { - use super::*; use combine::EasyParser; + use super::*; + #[test] fn test_some() { let input = "some(foo)"; diff --git a/golem-rib/src/parser/pattern_match.rs b/golem-rib/src/parser/pattern_match.rs index 3d56fdc60..12ef99adf 100644 --- a/golem-rib/src/parser/pattern_match.rs +++ b/golem-rib/src/parser/pattern_match.rs @@ -12,47 +12,58 @@ // See the License for the specific language governing permissions and // limitations under the License. +use combine::parser::char::{alpha_num, char, spaces, string}; +use combine::{attempt, not_followed_by, sep_by1, ParseError, Parser}; + use match_arm::*; use crate::expr::Expr; +use crate::parser::errors::RibParseError; use crate::parser::rib_expr::rib_expr; -use combine::parser::char::{char, spaces, string}; -use combine::{attempt, sep_by1, Parser}; pub fn pattern_match() -> impl Parser where Input: combine::Stream, + RibParseError: Into< + >::StreamError, + >, { let arms = sep_by1(match_arm().skip(spaces()), char(',').skip(spaces())); - attempt(string("match")) - .skip(spaces()) - .with( - ( - rib_expr().skip(spaces()), - char('{').skip(spaces()), - arms.skip(spaces()), - char('}').skip(spaces()), - ) - .map(|(expr, _, arms, _)| Expr::pattern_match(expr, arms)), + attempt( + string("match") + .skip(not_followed_by(alpha_num().or(char('_')).or(char('-')))) + .skip(spaces()), + ) + .with( + ( + rib_expr().skip(spaces()), + char('{').skip(spaces()), + arms.skip(spaces()), + char('}').skip(spaces()), ) - .message("Invalid syntax for pattern match") + .map(|(expr, _, arms, _)| Expr::pattern_match(expr, arms)), + ) + .message("Invalid syntax for pattern match") } mod match_arm { - use combine::{parser::char::string, Parser}; - use combine::parser::char::spaces; - - use super::arm_pattern::*; + use combine::{parser::char::string, ParseError, Parser}; use crate::expr::MatchArm; + use crate::parser::errors::RibParseError; use crate::parser::rib_expr::rib_expr; + use super::arm_pattern::*; + // RHS of a match arm pub(crate) fn match_arm() -> impl Parser where Input: combine::Stream, + RibParseError: Into< + >::StreamError, + >, { ( //LHS @@ -67,19 +78,21 @@ mod match_arm { // Keep the module structure same to avoid recursion related compiler errors mod arm_pattern { - use combine::{choice, parser, parser::char::char, Parser, Stream}; - - use crate::parser::pattern_match::internal::*; - - use crate::expr::ArmPattern; - use combine::attempt; use combine::parser::char::spaces; + use combine::{choice, parser, parser::char::char, ParseError, Parser, Stream}; + + use crate::expr::ArmPattern; + use crate::parser::errors::RibParseError; + use crate::parser::pattern_match::internal::*; // LHS of a match arm fn arm_pattern_() -> impl Parser where Input: combine::Stream, + RibParseError: Into< + >::StreamError, + >, { choice(( attempt(arm_pattern_constructor()), @@ -98,31 +111,34 @@ mod arm_pattern { parser! { pub(crate) fn arm_pattern[Input]()(Input) -> ArmPattern - where [Input: Stream]{ + where [Input: Stream, RibParseError: Into<>::StreamError>,]{ arm_pattern_() } } } mod internal { - use combine::choice; + use combine::attempt; + use combine::many1; + use combine::parser::char::{char, digit, letter}; + use combine::parser::char::{spaces, string}; + use combine::sep_by; + use combine::{choice, ParseError}; use combine::{parser::char::char as char_, Parser}; use crate::expr::ArmPattern; + use crate::parser::errors::RibParseError; use crate::parser::optional::option; + use crate::parser::pattern_match::arm_pattern::*; use crate::parser::result::result; use crate::parser::rib_expr::rib_expr; - use crate::parser::pattern_match::arm_pattern::*; - use combine::attempt; - use combine::many1; - use combine::parser::char::{char, digit, letter}; - use combine::parser::char::{spaces, string}; - use combine::sep_by; - pub(crate) fn arm_pattern_constructor() -> impl Parser where Input: combine::Stream, + RibParseError: Into< + >::StreamError, + >, { choice(( attempt(option().map(|expr| ArmPattern::Literal(Box::new(expr)))), @@ -135,6 +151,9 @@ mod internal { pub(crate) fn arm_pattern_literal() -> impl Parser where Input: combine::Stream, + RibParseError: Into< + >::StreamError, + >, { rib_expr().map(|lit| ArmPattern::Literal(Box::new(lit))) } @@ -142,6 +161,9 @@ mod internal { pub(crate) fn alias_name() -> impl Parser where Input: combine::Stream, + RibParseError: Into< + >::StreamError, + >, { many1(letter().or(digit()).or(char_('_'))) .map(|s: Vec| s.into_iter().collect()) @@ -151,6 +173,9 @@ mod internal { fn custom_arm_pattern_constructor() -> impl Parser where Input: combine::Stream, + RibParseError: Into< + >::StreamError, + >, { ( constructor_type_name().skip(spaces()), @@ -164,6 +189,9 @@ mod internal { fn tuple_arm_pattern_constructor() -> impl Parser where Input: combine::Stream, + RibParseError: Into< + >::StreamError, + >, { ( string("(").skip(spaces()), @@ -176,6 +204,9 @@ mod internal { fn constructor_type_name() -> impl Parser where Input: combine::Stream, + RibParseError: Into< + >::StreamError, + >, { many1(letter().or(digit()).or(char_('_')).or(char('-'))) .map(|s: Vec| s.into_iter().collect()) @@ -185,12 +216,14 @@ mod internal { #[cfg(test)] mod tests { - use super::*; + use combine::stream::position; + use combine::EasyParser; + use crate::expr::ArmPattern; use crate::expr::Expr; use crate::expr::MatchArm; - use combine::stream::position; - use combine::EasyParser; + + use super::*; #[test] fn test_simple_pattern_match() { diff --git a/golem-rib/src/parser/record.rs b/golem-rib/src/parser/record.rs index d8b449cc4..f9c6368d9 100644 --- a/golem-rib/src/parser/record.rs +++ b/golem-rib/src/parser/record.rs @@ -15,17 +15,19 @@ use combine::{ between, many1, parser, parser::char::{char as char_, letter, spaces}, - sep_by1, Parser, Stream, + sep_by1, ParseError, Parser, Stream, }; use crate::expr::Expr; +use crate::parser::errors::RibParseError; use super::rib_expr::rib_expr; parser! { pub fn record[Input]()(Input) -> Expr where [ - Input: Stream + Input: Stream, + RibParseError: Into<>::StreamError>, ] { record_() @@ -35,6 +37,9 @@ parser! { pub fn record_() -> impl Parser where Input: combine::Stream, + RibParseError: Into< + >::StreamError, + >, { spaces() .with( @@ -58,6 +63,9 @@ where fn field_key() -> impl Parser where Input: combine::Stream, + RibParseError: Into< + >::StreamError, + >, { many1(letter().or(char_('_').or(char_('-')))) .map(|s: Vec| s.into_iter().collect()) @@ -72,6 +80,9 @@ struct Field { fn field() -> impl Parser where Input: combine::Stream, + RibParseError: Into< + >::StreamError, + >, { ( field_key().skip(spaces()), @@ -86,9 +97,10 @@ where #[cfg(test)] mod tests { - use super::*; use combine::EasyParser; + use super::*; + #[test] fn test_singleton_record() { let input = "{foo: bar}"; @@ -198,4 +210,17 @@ mod tests { )) ); } + + #[test] + fn test_record_keys_can_be_key_words() { + let input = "{err: bar}"; + let result = rib_expr().easy_parse(input); + assert_eq!( + result, + Ok(( + Expr::record(vec![("err".to_string(), Expr::identifier("bar"))]), + "" + )) + ); + } } diff --git a/golem-rib/src/parser/result.rs b/golem-rib/src/parser/result.rs index 0df20c7b5..9b3c5df78 100644 --- a/golem-rib/src/parser/result.rs +++ b/golem-rib/src/parser/result.rs @@ -12,37 +12,40 @@ // See the License for the specific language governing permissions and // limitations under the License. +use combine::parser::char::spaces; use combine::{ attempt, choice, parser::char::{char, string}, - Parser, + ParseError, Parser, }; -use combine::parser::char::spaces; - use crate::expr::Expr; +use crate::parser::errors::RibParseError; use super::rib_expr::rib_expr; pub fn result() -> impl Parser where Input: combine::Stream, + RibParseError: Into< + >::StreamError, + >, { choice(( - attempt(string("ok")).with( - (char('('), rib_expr().skip(spaces()), char(')')).map(|(_, expr, _)| Expr::ok(expr)), - ), - attempt(string("err")).with( - (char('('), rib_expr().skip(spaces()), char(')')).map(|(_, expr, _)| Expr::err(expr)), - ), + attempt(string("ok").skip(char('('))) + .with((rib_expr().skip(spaces()), char(')')).map(|(expr, _)| Expr::ok(expr))), + attempt(string("err").skip(char('('))) + .with((rib_expr().skip(spaces()), char(')')).map(|(expr, _)| Expr::err(expr))), )) .message("Invalid syntax for Result type") } + #[cfg(test)] mod tests { - use super::*; use combine::EasyParser; + use super::*; + #[test] fn test_result() { let input = "ok(foo)"; diff --git a/golem-rib/src/parser/rib_expr.rs b/golem-rib/src/parser/rib_expr.rs index 07b127756..8d1f8b2f9 100644 --- a/golem-rib/src/parser/rib_expr.rs +++ b/golem-rib/src/parser/rib_expr.rs @@ -15,12 +15,13 @@ use combine::parser::char; use combine::parser::char::{char, spaces}; use combine::parser::choice::choice; -use combine::{attempt, eof, Parser, Stream}; +use combine::{attempt, eof, ParseError, Parser, Stream}; use combine::{parser, sep_by}; use crate::expr::Expr; use crate::parser::boolean::boolean_literal; use crate::parser::call::call; +use crate::parser::errors::RibParseError; use crate::parser::identifier::identifier; use crate::parser::literal::literal; use crate::parser::multi_line_code_block::multi_line_block; @@ -46,6 +47,9 @@ use super::tuple::tuple; pub fn rib_program() -> impl Parser where Input: combine::Stream, + RibParseError: Into< + >::StreamError, + >, { spaces().with( sep_by(rib_expr().skip(spaces()), char(';').skip(spaces())) @@ -66,7 +70,7 @@ where // This may not be intuitive however will work! parser! { pub fn rib_expr[Input]()(Input) -> Expr - where [Input: combine::Stream] + where [Input: combine::Stream, RibParseError: Into<>::StreamError>,] { rib_expr_() } @@ -75,6 +79,9 @@ parser! { pub fn rib_expr_() -> impl Parser where Input: combine::Stream, + RibParseError: Into< + >::StreamError, + >, { spaces() .with(choice(( @@ -93,8 +100,8 @@ where option(), result(), attempt(call()), - number(), identifier(), + number(), ))) .skip(spaces()) } @@ -102,6 +109,9 @@ where pub fn binary_rib() -> impl Parser where Input: combine::Stream, + RibParseError: Into< + >::StreamError, + >, { attempt(binary(comparison_operands(), comparison_operands())) } @@ -109,6 +119,9 @@ where pub fn flag_or_record() -> impl Parser where Input: combine::Stream, + RibParseError: Into< + >::StreamError, + >, { choice((attempt(flag()), attempt(record()))).message("Unable to parse flag or record") } @@ -116,6 +129,9 @@ where fn selection_expr_() -> impl Parser where Input: combine::Stream, + RibParseError: Into< + >::StreamError, + >, { choice((attempt(select_field()), attempt(select_index()))) .message("Unable to parse selection expression") @@ -123,7 +139,7 @@ where parser! { fn selection_expr[Input]()(Input) -> Expr - where [Input: Stream] + where [Input: Stream, RibParseError: Into<>::StreamError>,] { selection_expr_() } @@ -132,13 +148,16 @@ parser! { fn simple_expr_() -> impl Parser where Input: combine::Stream, + RibParseError: Into< + >::StreamError, + >, { choice((literal(), not(), number(), boolean_literal(), identifier())) } parser! { fn simple_expr[Input]()(Input) -> Expr - where [Input: Stream] + where [Input: Stream, RibParseError: Into<>::StreamError>,] { simple_expr_() } @@ -147,13 +166,16 @@ parser! { fn comparison_operands_() -> impl Parser where Input: combine::Stream, + RibParseError: Into< + >::StreamError, + >, { selection_expr().or(simple_expr()) } parser! { fn comparison_operands[Input]()(Input) -> Expr - where [Input: Stream] + where [Input: Stream, RibParseError: Into<>::StreamError>,] { comparison_operands_() } diff --git a/golem-rib/src/parser/select_field.rs b/golem-rib/src/parser/select_field.rs index 32f0f2fb9..b62674568 100644 --- a/golem-rib/src/parser/select_field.rs +++ b/golem-rib/src/parser/select_field.rs @@ -12,38 +12,42 @@ // See the License for the specific language governing permissions and // limitations under the License. +use combine::parser; use combine::parser::char::spaces; -use combine::{attempt, choice, Parser, Stream}; +use combine::{attempt, choice, ParseError, Parser, Stream}; -use crate::expr::Expr; -use crate::parser::record::record; +use internal::*; +use crate::expr::Expr; +use crate::parser::errors::RibParseError; use crate::parser::identifier::identifier; - -use combine::parser; -use internal::*; +use crate::parser::record::record; parser! { pub fn select_field[Input]()(Input) -> Expr - where [Input: Stream] + where [Input: Stream, RibParseError: Into<>::StreamError>,] { select_field_() } } mod internal { + use combine::parser::char::{char, digit, letter}; + use combine::{many1, ParseError}; + + use crate::parser::errors::RibParseError; use crate::parser::select_index::select_index; use super::*; - use combine::parser::char::char; - - use crate::parser::identifier; // We make base_expr and the children strict enough carefully, to avoid // stack overflow without affecting the grammer. pub(crate) fn select_field_() -> impl Parser where Input: combine::Stream, + RibParseError: Into< + >::StreamError, + >, { spaces().with( ( @@ -84,9 +88,12 @@ mod internal { } } - pub(crate) fn base_expr() -> impl Parser + fn base_expr() -> impl Parser where Input: combine::Stream, + RibParseError: Into< + >::StreamError, + >, { choice(( attempt(select_index()), @@ -95,19 +102,31 @@ mod internal { )) } - pub(crate) fn field_name() -> impl Parser + fn field_name() -> impl Parser where Input: combine::Stream, + RibParseError: Into< + >::StreamError, + >, { - identifier::identifier_text().message("Unable to parse field name") + text().message("Unable to parse field name") + } + + fn text() -> impl Parser + where + Input: Stream, + { + many1(letter().or(digit()).or(char('_').or(char('-')))) + .map(|s: Vec| s.into_iter().collect::()) } } #[cfg(test)] mod tests { + use combine::EasyParser; + use crate::expr::*; use crate::parser::rib_expr::rib_expr; - use combine::EasyParser; #[test] fn test_select_field() { diff --git a/golem-rib/src/parser/select_index.rs b/golem-rib/src/parser/select_index.rs index 2157a1b93..c091259e1 100644 --- a/golem-rib/src/parser/select_index.rs +++ b/golem-rib/src/parser/select_index.rs @@ -12,15 +12,21 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::expr::Expr; -use crate::parser::identifier::identifier; use combine::parser::char::{char as char_, spaces}; -use combine::{attempt, choice, many1, optional, Parser}; +use combine::{attempt, choice, many1, optional, ParseError, Parser}; + use internal::*; +use crate::expr::Expr; +use crate::parser::errors::RibParseError; +use crate::parser::identifier::identifier; + pub fn select_index() -> impl Parser where Input: combine::Stream, + RibParseError: Into< + >::StreamError, + >, { spaces().with( ( @@ -42,12 +48,12 @@ where } mod internal { - use super::*; + use combine::parser::char::char as char_; use crate::parser::number::number; use crate::parser::sequence::sequence; - use combine::parser::char::char as char_; + use super::*; pub(crate) fn build_select_index_from(base_expr: Expr, indices: Vec) -> Expr { let mut result = base_expr; @@ -60,6 +66,9 @@ mod internal { pub(crate) fn nested_indices() -> impl Parser> where Input: combine::Stream, + RibParseError: Into< + >::StreamError, + >, { many1( ( @@ -75,6 +84,9 @@ mod internal { pub(crate) fn pos_num() -> impl Parser where Input: combine::Stream, + RibParseError: Into< + >::StreamError, + >, { number().map(|s: Expr| match s { Expr::Number(number, _, _) => { @@ -91,6 +103,9 @@ mod internal { pub(crate) fn base_expr() -> impl Parser where Input: combine::Stream, + RibParseError: Into< + >::StreamError, + >, { choice((attempt(sequence()), attempt(identifier()))) } @@ -98,9 +113,10 @@ mod internal { #[cfg(test)] mod tests { + use combine::EasyParser; + use crate::expr::*; use crate::parser::rib_expr::rib_expr; - use combine::EasyParser; #[test] fn test_select_index() { diff --git a/golem-rib/src/parser/sequence.rs b/golem-rib/src/parser/sequence.rs index 299fec640..aa2e5986a 100644 --- a/golem-rib/src/parser/sequence.rs +++ b/golem-rib/src/parser/sequence.rs @@ -12,16 +12,20 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::expr::Expr; use combine::parser::char::{char, spaces}; -use combine::sep_by; use combine::{between, Parser}; +use combine::{sep_by, ParseError}; +use crate::expr::Expr; +use crate::parser::errors::RibParseError; use crate::parser::rib_expr::rib_expr; pub fn sequence() -> impl Parser where Input: combine::Stream, + RibParseError: Into< + >::StreamError, + >, { spaces() .with( @@ -37,9 +41,10 @@ where #[cfg(test)] mod tests { - use super::*; use combine::EasyParser; + use super::*; + #[test] fn test_empty_sequence() { let input = "[]"; diff --git a/golem-rib/src/parser/tuple.rs b/golem-rib/src/parser/tuple.rs index bd5a262bd..9fb8029ae 100644 --- a/golem-rib/src/parser/tuple.rs +++ b/golem-rib/src/parser/tuple.rs @@ -15,16 +15,20 @@ use combine::{ between, parser::char::{char, spaces}, - sep_by, Parser, + sep_by, ParseError, Parser, }; use crate::expr::Expr; +use crate::parser::errors::RibParseError; use super::rib_expr::rib_expr; pub fn tuple() -> impl Parser where Input: combine::Stream, + RibParseError: Into< + >::StreamError, + >, { spaces() .with( @@ -40,10 +44,11 @@ where #[cfg(test)] mod tests { - use super::*; use combine::stream::position; use combine::EasyParser; + use super::*; + #[test] fn test_empty_tuple() { let input = "()"; diff --git a/golem-rib/src/parser/type_name.rs b/golem-rib/src/parser/type_name.rs index b3a34f767..ca8f1402e 100644 --- a/golem-rib/src/parser/type_name.rs +++ b/golem-rib/src/parser/type_name.rs @@ -12,19 +12,23 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::InferredType; +use std::fmt::Display; +use std::ops::Deref; + use bincode::{Decode, Encode}; -use combine::parser; use combine::parser::char; use combine::parser::char::{char, spaces, string}; use combine::parser::choice::choice; use combine::{attempt, between, sep_by, Parser}; +use combine::{parser, ParseError}; + use golem_api_grpc::proto::golem::rib::type_name::Kind as InnerTypeName; use golem_api_grpc::proto::golem::rib::{ BasicTypeName, ListType, OptionType, TupleType, TypeName as ProtoTypeName, }; -use std::fmt::Display; -use std::ops::Deref; + +use crate::parser::errors::RibParseError; +use crate::InferredType; #[derive(Debug, Hash, Clone, Eq, PartialEq, Encode, Decode)] pub enum TypeName { @@ -191,6 +195,9 @@ impl From for InferredType { pub fn parse_basic_type() -> impl Parser where Input: combine::Stream, + RibParseError: Into< + >::StreamError, + >, { choice(( attempt(string("bool").map(|_| TypeName::Bool)), @@ -213,6 +220,9 @@ where pub fn parse_list_type() -> impl Parser where Input: combine::Stream, + RibParseError: Into< + >::StreamError, + >, { string("list") .skip(spaces()) @@ -227,6 +237,9 @@ where pub fn parse_option_type() -> impl Parser where Input: combine::Stream, + RibParseError: Into< + >::StreamError, + >, { string("option") .skip(spaces()) @@ -241,6 +254,9 @@ where pub fn parse_tuple_type() -> impl Parser where Input: combine::Stream, + RibParseError: Into< + >::StreamError, + >, { string("tuple") .skip(spaces()) @@ -255,6 +271,9 @@ where pub fn parse_type_name_() -> impl Parser where Input: combine::Stream, + RibParseError: Into< + >::StreamError, + >, { spaces().with(choice(( attempt(parse_basic_type()), @@ -266,7 +285,7 @@ where parser! { pub fn parse_type_name[Input]()(Input) -> TypeName - where [Input: combine::Stream] + where [Input: combine::Stream, RibParseError: Into<>::StreamError>,] { parse_type_name_() } @@ -274,9 +293,10 @@ parser! { #[cfg(test)] mod type_name_parser_tests { - use super::*; use combine::EasyParser; + use super::*; + fn parse_and_compare(input: &str, expected: TypeName) { let result = parse_type_name().easy_parse(input); assert_eq!(result, Ok((expected, "")));