diff --git a/rusty_parser/src/built_ins/field.rs b/rusty_parser/src/built_ins/field.rs index 1b711a28..ef16c3c2 100644 --- a/rusty_parser/src/built_ins/field.rs +++ b/rusty_parser/src/built_ins/field.rs @@ -1,7 +1,7 @@ use rusty_common::*; use rusty_pc::*; -use crate::expr::expr_pos_ws_p; +use crate::expr::expr_ws_keyword_p; use crate::expr::file_handle::file_handle_p; use crate::input::StringView; use crate::pc_specific::*; @@ -23,12 +23,9 @@ pub fn parse() -> impl Parser impl Parser { - seq3( - expr_pos_ws_p(), - keyword_ws_p(Keyword::As), + expr_ws_keyword_p(Keyword::As).and_tuple(demand_lead_ws( name_p().with_pos().or_expected("variable name"), - |width, _, name| (width, name), - ) + )) } fn build_args( diff --git a/rusty_parser/src/built_ins/name.rs b/rusty_parser/src/built_ins/name.rs index 09412858..0d211c1a 100644 --- a/rusty_parser/src/built_ins/name.rs +++ b/rusty_parser/src/built_ins/name.rs @@ -1,16 +1,15 @@ use rusty_pc::*; -use crate::expr::{ws_expr_pos_p, ws_expr_pos_ws_p}; +use crate::expr::{demand_ws_expr_ws_keyword_p, ws_expr_pos_p}; use crate::input::StringView; use crate::pc_specific::*; use crate::{BuiltInSub, ParserError, *}; pub fn parse() -> impl Parser { - seq4( + seq3( keyword(Keyword::Name), - ws_expr_pos_ws_p().or_expected("old file name"), - keyword(Keyword::As), + demand_ws_expr_ws_keyword_p("old file name", Keyword::As), ws_expr_pos_p().or_expected("new file name"), - |_, l, _, r| Statement::built_in_sub_call(BuiltInSub::Name, vec![l, r]), + |_, l, r| Statement::built_in_sub_call(BuiltInSub::Name, vec![l, r]), ) } diff --git a/rusty_parser/src/built_ins/open.rs b/rusty_parser/src/built_ins/open.rs index fc2b9a08..4e669a49 100644 --- a/rusty_parser/src/built_ins/open.rs +++ b/rusty_parser/src/built_ins/open.rs @@ -7,6 +7,29 @@ use crate::input::StringView; use crate::pc_specific::*; use crate::tokens::equal_sign_ws; use crate::{BuiltInSub, ParserError, *}; + +/// Original doc from QBasic regarding OPEN: +/// +/// Opens a file or device. +/// +/// ```txt +/// OPEN file$ [FOR mode] [ACCESS access] [lock] AS [#]filenumber% [LEN=reclen%] +/// ``` +/// +/// file$: The name of the file or device. The file name may include a drive and path. +/// +/// mode: One of the following file modes: APPEND, BINARY, INPUT, OUTPUT, or RANDOM. +/// +/// access: In network environments, specifies whether the file is +/// opened for READ, WRITE, or READ WRITE access. +/// +/// lock: Specifies the file locking in network environments: +/// SHARED, LOCK READ, LOCK WRITE, LOCK READ WRITE. +/// +/// filenumber%: A number in the range 1 through 255 that identifies the file while it is open. +/// +/// reclen%: For random-access files, the record length (default is 128 bytes). For sequential files, +/// the number of characters buffered (default is 512 bytes). pub fn parse() -> impl Parser { seq6( keyword(Keyword::Open), @@ -64,6 +87,8 @@ fn parse_file_number_p() -> impl Parser impl Parser { seq3( lead_ws(keyword_ignoring(Keyword::Len)), diff --git a/rusty_parser/src/built_ins/view_print.rs b/rusty_parser/src/built_ins/view_print.rs index e3527f8f..03fecd68 100644 --- a/rusty_parser/src/built_ins/view_print.rs +++ b/rusty_parser/src/built_ins/view_print.rs @@ -1,6 +1,7 @@ +use rusty_pc::and::VecCombiner; use rusty_pc::*; -use crate::expr::{ws_expr_pos_p, ws_expr_pos_ws_p}; +use crate::expr::{ws_expr_pos_p, ws_expr_ws_keyword_p}; use crate::input::StringView; use crate::pc_specific::*; use crate::{BuiltInSub, ParserError, *}; @@ -12,12 +13,7 @@ pub fn parse() -> impl Parser impl Parser { - seq3( - ws_expr_pos_ws_p(), - keyword(Keyword::To), - ws_expr_pos_p().or_expected("expression"), - |l, _, r| vec![l, r], - ) + ws_expr_ws_keyword_p(Keyword::To).and(ws_expr_pos_p().or_expected("expression"), VecCombiner) } #[cfg(test)] diff --git a/rusty_parser/src/core/dim_name.rs b/rusty_parser/src/core/dim_name.rs index bb3eb404..803edc52 100644 --- a/rusty_parser/src/core/dim_name.rs +++ b/rusty_parser/src/core/dim_name.rs @@ -4,7 +4,9 @@ use rusty_pc::*; use crate::core::var_name; use crate::input::StringView; use crate::pc_specific::*; -use crate::{ParserError, *}; +use crate::{ + ArrayDimensions, BareName, BuiltInStyle, DimList, DimType, Name, ParserError, ToBareName, TypeQualifier, TypedName +}; pub type DimVar = TypedName; pub type DimVarPos = Positioned; @@ -122,10 +124,10 @@ where mod array_dimensions { use rusty_pc::*; - use crate::expr::{expression_pos_p, opt_second_expression_after_keyword}; + use crate::expr::expr_keyword_opt_expr; use crate::input::StringView; use crate::pc_specific::*; - use crate::{ParserError, *}; + use crate::{ArrayDimension, ArrayDimensions, Keyword, ParserError}; pub fn array_dimensions_p() -> impl Parser { @@ -137,12 +139,7 @@ mod array_dimensions { // paren_expr ws* TO ws* paren_expr fn array_dimension_p() -> impl Parser { - opt_second_expression_after_keyword( - expression_pos_p(), - Keyword::To, - ExpressionTrait::is_parenthesis, - ) - .map(|(l, opt_r)| match opt_r { + expr_keyword_opt_expr(Keyword::To).map(|(l, opt_r)| match opt_r { Some(r) => ArrayDimension { lbound: Some(l), ubound: r, @@ -158,12 +155,12 @@ mod array_dimensions { mod type_definition { use rusty_pc::*; - use crate::core::VarNameCtx; + use crate::core::{VarNameCtx, user_defined_type}; use crate::expr::expression_pos_p; use crate::input::StringView; use crate::pc_specific::*; use crate::tokens::star_ws; - use crate::{ParserError, *}; + use crate::{BuiltInStyle, DimType, ExpressionPos, Keyword, ParserError, TypeQualifier}; pub fn extended_type() -> impl Parser { diff --git a/rusty_parser/src/core/for_loop.rs b/rusty_parser/src/core/for_loop.rs index 47f956d4..aefa2795 100644 --- a/rusty_parser/src/core/for_loop.rs +++ b/rusty_parser/src/core/for_loop.rs @@ -2,11 +2,11 @@ use rusty_pc::*; use crate::core::statements::zero_or_more_statements; use crate::error::ParserError; -use crate::expr::{expr_pos_ws_p, opt_second_expression_after_keyword, property, ws_expr_pos_p}; +use crate::expr::{demand_expr_ws_keyword_p, property, ws_expr_pos_p}; use crate::input::StringView; use crate::pc_specific::*; use crate::tokens::equal_sign_ws; -use crate::*; +use crate::{ExpressionPos, ExpressionTrait, ForLoop, Keyword, Statement}; // FOR I = 0 TO 5 STEP 1 // statements @@ -42,24 +42,24 @@ fn parse_for_step_p() -> impl Parser< ), Error = ParserError, > { - opt_second_expression_after_keyword(parse_for_p(), Keyword::Step, |(_var, _low, upper)| { - upper.is_parenthesis() - }) - .map(|((n, l, u), opt_step)| (n, l, u, opt_step)) + parse_for_p().then_with_in_context( + opt_step_p(), + |(_, _, upper)| upper.is_parenthesis(), + |(n, l, u), opt_step| (n, l, u, opt_step), + ) } /// Parses the "FOR I = 1 TO 2" part fn parse_for_p() -> impl Parser { - seq6( - keyword_ws_p(Keyword::For), - property::parser().or_expected("name after FOR"), + seq5( + keyword_ignoring(Keyword::For), + demand_lead_ws(property::parser().or_expected("name after FOR")), equal_sign_ws(), - expr_pos_ws_p().or_expected("lower bound of FOR loop"), - keyword(Keyword::To), + demand_expr_ws_keyword_p("lower bound of FOR loop", Keyword::To), ws_expr_pos_p().or_expected("upper bound of FOR loop"), - |_, name, _, lower_bound, _, upper_bound| (name, lower_bound, upper_bound), + |_, name, _, lower_bound, upper_bound| (name, lower_bound, upper_bound), ) } @@ -67,6 +67,18 @@ fn next_counter_p() -> impl Parser impl Parser, Error = ParserError> +{ + conditionally_opt_whitespace() + .and_keep_right(keyword_ignoring(Keyword::Step).no_context()) + .and_keep_right( + ws_expr_pos_p() + .or_expected("expression after STEP") + .no_context(), + ) + .to_option() +} + #[cfg(test)] mod tests { use rusty_common::*; diff --git a/rusty_parser/src/core/if_block.rs b/rusty_parser/src/core/if_block.rs index b912e4c3..e6207fe0 100644 --- a/rusty_parser/src/core/if_block.rs +++ b/rusty_parser/src/core/if_block.rs @@ -5,7 +5,7 @@ use crate::core::single_line_statements::{ single_line_non_comment_statements_p, single_line_statements_p }; use crate::core::statements::zero_or_more_statements; -use crate::expr::ws_expr_pos_ws_p; +use crate::expr::demand_ws_expr_ws_keyword_p; use crate::input::StringView; use crate::pc_specific::*; use crate::{ParserError, *}; @@ -35,12 +35,10 @@ pub fn if_block_p() -> impl Parser impl Parser { - seq3( - keyword(Keyword::If), - ws_expr_pos_ws_p().or_expected("expression after IF"), - keyword(Keyword::Then), - |_, m, _| m, - ) + keyword(Keyword::If).and_keep_right(demand_ws_expr_ws_keyword_p( + "expression after IF", + Keyword::Then, + )) } fn single_line_if_else_p() -> impl Parser< @@ -79,12 +77,10 @@ fn multi_line_if_p() -> impl Parser< } fn else_if_expr_then_p() -> impl Parser { - seq3( - keyword(Keyword::ElseIf), - ws_expr_pos_ws_p().or_expected("expression after ELSEIF"), - keyword(Keyword::Then), - |_, m, _| m, - ) + keyword(Keyword::ElseIf).and_keep_right(demand_ws_expr_ws_keyword_p( + "expression after ELSEIF", + Keyword::Then, + )) } fn else_if_block_p() -> impl Parser { diff --git a/rusty_parser/src/core/select_case.rs b/rusty_parser/src/core/select_case.rs index 266fbb72..59fce85a 100644 --- a/rusty_parser/src/core/select_case.rs +++ b/rusty_parser/src/core/select_case.rs @@ -105,11 +105,11 @@ mod case_expression_parser { use rusty_common::Positioned; use rusty_pc::*; - use crate::expr::{expression_pos_p, opt_second_expression_after_keyword}; + use crate::expr::{expr_keyword_opt_expr, expression_pos_p}; use crate::input::StringView; use crate::pc_specific::*; use crate::tokens::{TokenType, any_token}; - use crate::{CaseExpression, ExpressionTrait, Keyword, Operator, ParserError}; + use crate::{CaseExpression, Keyword, Operator, ParserError}; pub fn parser() -> impl Parser { case_is().or(simple_or_range()) @@ -140,12 +140,7 @@ mod case_expression_parser { } fn simple_or_range() -> impl Parser { - opt_second_expression_after_keyword( - expression_pos_p(), - Keyword::To, - ExpressionTrait::is_parenthesis, - ) - .map(|(left, opt_right)| match opt_right { + expr_keyword_opt_expr(Keyword::To).map(|(left, opt_right)| match opt_right { Some(right) => CaseExpression::Range(left, right), _ => CaseExpression::Simple(left), }) diff --git a/rusty_parser/src/expr/binary_expression.rs b/rusty_parser/src/expr/binary_expression.rs index 1cafc9a2..e1bd323c 100644 --- a/rusty_parser/src/expr/binary_expression.rs +++ b/rusty_parser/src/expr/binary_expression.rs @@ -3,14 +3,14 @@ use rusty_pc::and::TupleCombiner; use rusty_pc::*; use crate::error::ParserError; -use crate::expr::expression_pos_p; +use crate::expr::{expression_pos_p, ws_expr_pos_p}; use crate::input::StringView; use crate::pc_specific::{OrExpected, WithPos, lead_opt_ws, lead_ws}; use crate::tokens::{TokenType, any_token}; -use crate::*; +use crate::{ExpressionPos, ExpressionPosTrait, ExpressionTrait, Keyword, Operator}; // result ::= -pub fn parser() -> impl Parser { +pub(super) fn parser() -> impl Parser { non_bin_expr() .then_with_in_context( second_parser(), @@ -31,7 +31,7 @@ fn second_parser() -> impl Parser< Error = ParserError, > { operator() - .then_with_in_context(third_parser(), is_keyword_op, TupleCombiner) + .then_with_in_context(expr_after_binary_operator(), is_keyword_op, TupleCombiner) .to_option() } @@ -39,20 +39,16 @@ fn is_keyword_op(op: &Positioned) -> bool { op.element == Operator::And || op.element == Operator::Or || op.element == Operator::Modulo } -fn third_parser() -> impl Parser { +fn expr_after_binary_operator() +-> impl Parser { + // boxed breaks apart the recursive type evaluation IifParser::new( - super::guard::parser().to_fatal(), - super::guard::parser().to_option().map_to_unit(), + // the previous operator is a keyword op, must have whitespace or parenthesis + ws_expr_pos_p().boxed(), + // the previous operator is a symbol, whitespace is optional + lead_opt_ws(expression_pos_p().boxed()), ) - .and_keep_right(right_side_expr().no_context()) -} - -/// Parses the right side expression, after having parsed the binary operator -fn right_side_expr() -> impl Parser { - // boxed breaks apart the recursive type evaluation - expression_pos_p() - .or_expected("expression after operator") - .boxed() + .or_expected("expression after operator") } fn non_bin_expr() -> impl Parser { @@ -77,7 +73,7 @@ fn operator() -> impl Parser, Er // no whitespace needed lead_opt_ws(operator_p()), // whitespace needed - lead_ws(operator_p()).or(lead_opt_ws(symbol_operator_p())), + symbol_operator_p().or(lead_ws(operator_p())), ) } diff --git a/rusty_parser/src/expr/built_in_function_call.rs b/rusty_parser/src/expr/built_in_function_call.rs index 716c785d..84a48092 100644 --- a/rusty_parser/src/expr/built_in_function_call.rs +++ b/rusty_parser/src/expr/built_in_function_call.rs @@ -3,9 +3,9 @@ use rusty_pc::Parser; use crate::built_ins::built_in_function_call_p; use crate::input::StringView; use crate::pc_specific::WithPos; -use crate::{ParserError, *}; +use crate::{ExpressionPos, ParserError}; -pub fn parser() -> impl Parser { +pub(super) fn parser() -> impl Parser { built_in_function_call_p().with_pos() } diff --git a/rusty_parser/src/expr/file_handle.rs b/rusty_parser/src/expr/file_handle.rs index 290f5a62..c94bdf7f 100644 --- a/rusty_parser/src/expr/file_handle.rs +++ b/rusty_parser/src/expr/file_handle.rs @@ -8,7 +8,7 @@ use crate::expr::ws_expr_pos_p; use crate::input::StringView; use crate::pc_specific::*; use crate::tokens::{TokenType, any_token_of, pound}; -use crate::*; +use crate::{Expression, ExpressionPos, FileHandle}; pub fn file_handle_p() -> impl Parser, Error = ParserError> { diff --git a/rusty_parser/src/expr/function_call_or_array_element.rs b/rusty_parser/src/expr/function_call_or_array_element.rs index e056fca9..a23a35fe 100644 --- a/rusty_parser/src/expr/function_call_or_array_element.rs +++ b/rusty_parser/src/expr/function_call_or_array_element.rs @@ -4,7 +4,7 @@ use crate::core::name_as_tokens_p; use crate::expr::expression_pos_p; use crate::input::StringView; use crate::pc_specific::{WithPos, csv, in_parenthesis}; -use crate::{ParserError, *}; +use crate::{Expression, ExpressionPos, Expressions, NameAsTokens, ParserError}; // function_call ::= "(" * ")" // function-name ::= @@ -16,7 +16,7 @@ use crate::{ParserError, *}; // // A function can be qualified. -pub fn parser() -> impl Parser { +pub(super) fn parser() -> impl Parser { name_as_tokens_p() .and( in_parenthesis(csv(expression_pos_p()).or_default()), diff --git a/rusty_parser/src/expr/guard.rs b/rusty_parser/src/expr/guard.rs deleted file mode 100644 index d33aec3e..00000000 --- a/rusty_parser/src/expr/guard.rs +++ /dev/null @@ -1,23 +0,0 @@ -use rusty_pc::*; - -use crate::ParserError; -use crate::input::StringView; -use crate::pc_specific::{WithExpected, whitespace_ignoring}; -use crate::tokens::{any_symbol_of, any_token_of}; - -/// `result ::= " " | "("` -/// -/// The "(" will be undone. -pub fn parser() -> impl Parser { - whitespace_guard() - .or(lparen_guard()) - .with_expected_message("Expected: '(' or whitespace") -} - -fn whitespace_guard() -> impl Parser { - whitespace_ignoring() -} - -fn lparen_guard() -> impl Parser { - any_symbol_of!('(').map_to_unit().peek() -} diff --git a/rusty_parser/src/expr/integer_or_long_literal.rs b/rusty_parser/src/expr/integer_or_long_literal.rs index 457672dd..d8d1f4aa 100644 --- a/rusty_parser/src/expr/integer_or_long_literal.rs +++ b/rusty_parser/src/expr/integer_or_long_literal.rs @@ -5,10 +5,10 @@ use crate::error::ParserError; use crate::input::StringView; use crate::pc_specific::WithPos; use crate::tokens::{TokenType, any_token_of}; -use crate::*; +use crate::{Expression, ExpressionPos}; // result ::= | | -pub fn parser() -> impl Parser { +pub(super) fn parser() -> impl Parser { any_token_of!( TokenType::Digits, TokenType::HexDigits, diff --git a/rusty_parser/src/expr/mod.rs b/rusty_parser/src/expr/mod.rs index 89b90a7f..a1c665aa 100644 --- a/rusty_parser/src/expr/mod.rs +++ b/rusty_parser/src/expr/mod.rs @@ -2,9 +2,7 @@ mod binary_expression; mod built_in_function_call; pub mod file_handle; mod function_call_or_array_element; -mod guard; mod integer_or_long_literal; -mod opt_second_expression; mod parenthesis; mod parsers; pub mod property; @@ -14,5 +12,4 @@ pub mod types; mod unary_expression; mod variable; -pub use self::opt_second_expression::opt_second_expression_after_keyword; pub use self::parsers::*; diff --git a/rusty_parser/src/expr/opt_second_expression.rs b/rusty_parser/src/expr/opt_second_expression.rs deleted file mode 100644 index 6e40a23b..00000000 --- a/rusty_parser/src/expr/opt_second_expression.rs +++ /dev/null @@ -1,77 +0,0 @@ -use rusty_pc::and::TupleCombiner; -use rusty_pc::{IifParser, Parser, ParserErrorTrait}; - -use crate::error::ParserError; -use crate::expr::parsers::ws_expr_pos_p; -use crate::input::StringView; -use crate::pc_specific::{keyword, whitespace_ignoring}; -use crate::{ExpressionPos, Keyword}; - -/// Parses an optional second expression that follows the first expression -/// and a keyword. -/// -/// If the keyword is present, the second expression is mandatory. -/// -/// Example: `FOR I = 1 TO 100 [STEP 5]` -pub fn opt_second_expression_after_keyword( - first_parser: P, - keyword: Keyword, - is_first_wrapped_in_parenthesis: F, -) -> impl Parser), Error = ParserError> -where - P: Parser, - F: Fn(&P::Output) -> bool + 'static, -{ - first_parser.then_with_in_context( - parse_second(keyword), - move |first| is_first_wrapped_in_parenthesis(first), - TupleCombiner, - ) -} - -// first_parser AND [ cond_ws(is_first_paren) KEYWORD !AND! ws_expr ] -fn parse_second( - k: Keyword, -) -> impl Parser, Error = ParserError> { - // the left side needs the context - ws_keyword(k) - .and_keep_right( - // but the right side does not need it... - ws_expr_pos_p().no_context().or_fail(err(k)), - ) - // finally to_option needs to send the context down to the "and_keep_right" - .to_option() -} - -fn ws_keyword(k: Keyword) -> impl Parser { - // the left side has the context - conditionally_opt_whitespace().and_tuple( - // but the right side does not - keyword(k).no_context(), - ) -} - -fn err(keyword: Keyword) -> ParserError { - ParserError::expected(&format!("expression after {}", keyword)).to_fatal() -} - -/// Creates a parser that parses whitespace, -/// conditionally allowing it to be missing. -/// When [allow_none] is false, whitespace is mandatory. -/// When [allow_none] is true, the whitespace can be missing. -/// This is typically the case when the previously parsed -/// token was a right side parenthesis. -/// -/// Examples -/// -/// * `(1 + 2)AND` no whitespace is required before `AND` -/// * `1 + 2AND` the lack of whitespace before `AND` is an error -pub(super) fn conditionally_opt_whitespace() --> impl Parser { - IifParser::new( - // allow none - whitespace_ignoring().to_option().map_to_unit(), - // whitespace is required - whitespace_ignoring(), - ) -} diff --git a/rusty_parser/src/expr/parenthesis.rs b/rusty_parser/src/expr/parenthesis.rs index 7161d655..d18dbd7c 100644 --- a/rusty_parser/src/expr/parenthesis.rs +++ b/rusty_parser/src/expr/parenthesis.rs @@ -3,9 +3,9 @@ use rusty_pc::*; use crate::expr::expression_pos_p; use crate::input::StringView; use crate::pc_specific::{OrExpected, WithPos, in_parenthesis}; -use crate::{ParserError, *}; +use crate::{Expression, ExpressionPos, ParserError}; -pub fn parser() -> impl Parser { +pub(super) fn parser() -> impl Parser { in_parenthesis(expression_pos_p().or_expected("expression inside parenthesis")) .map(|child| Expression::Parenthesis(Box::new(child))) .with_pos() diff --git a/rusty_parser/src/expr/parsers.rs b/rusty_parser/src/expr/parsers.rs index 52bfaf9f..605fd60a 100644 --- a/rusty_parser/src/expr/parsers.rs +++ b/rusty_parser/src/expr/parsers.rs @@ -1,11 +1,16 @@ -use rusty_pc::and::{KeepLeftCombiner, VecCombiner}; +use rusty_pc::and::{KeepLeftCombiner, TupleCombiner, VecCombiner}; use rusty_pc::*; -use crate::expr::opt_second_expression::conditionally_opt_whitespace; use crate::input::StringView; use crate::pc_specific::*; use crate::tokens::comma_ws; -use crate::{ExpressionPos, ExpressionTrait, Expressions, ParserError}; +use crate::{ExpressionPos, ExpressionTrait, Expressions, Keyword, ParserError}; + +/// Parses an expression. +pub fn expression_pos_p() -> impl Parser { + lazy(super::binary_expression::parser) +} + /// `( expr [, expr]* )` pub fn in_parenthesis_csv_expressions_non_opt( expectation: &str, @@ -35,37 +40,15 @@ pub fn csv_expressions_first_guarded() ) } -pub fn expression_pos_p() -> impl Parser { - lazy(eager_expression_pos_p) -} - /// Parses an expression that is either preceded by whitespace /// or is a parenthesis expression. /// /// ```text -/// | -/// | -/// +/// | +/// /// ``` pub fn ws_expr_pos_p() -> impl Parser { - // ws* ( expr ) - // ws+ expr - preceded_by_ws(expression_pos_p()) -} - -/// Parses an expression that is either followed by whitespace -/// or is a parenthesis expression. -/// -/// The whitespace is mandatory after a non-parenthesis -/// expression. -/// -/// ```text -/// | -/// | -/// -/// ``` -pub fn expr_pos_ws_p() -> impl Parser { - followed_by_ws(expression_pos_p()) + super::parenthesis::parser().or(lead_ws(expression_pos_p())) } /// Parses an expression that is either surrounded by whitespace @@ -83,12 +66,6 @@ pub fn ws_expr_pos_ws_p() -> impl Parser, -) -> impl Parser { - super::guard::parser().and_keep_right(parser) -} - fn followed_by_ws( parser: impl Parser, ) -> impl Parser { @@ -99,8 +76,81 @@ fn followed_by_ws( ) } -/// Parses an expression -fn eager_expression_pos_p() -> impl Parser -{ - super::binary_expression::parser() +/// Parses an expression, +/// then demands whitespace, unless the expression is a parenthesis. +/// Finally it demands the given keyword. +pub fn expr_ws_keyword_p( + keyword: Keyword, +) -> impl Parser { + expr_ws_followed_by(expression_pos_p(), keyword_ignoring(keyword)) +} + +/// Parses an expression, failing fatally with the given expectation message if it can't be parsed. +/// Then it demands whitespace, unless the expression is a parenthesis. +/// Finally it demands the given keyword. +pub fn demand_expr_ws_keyword_p( + expectation: &str, + keyword: Keyword, +) -> impl Parser { + expr_ws_followed_by( + expression_pos_p().or_expected(expectation), + keyword_ignoring(keyword), + ) +} + +pub fn ws_expr_ws_keyword_p( + keyword: Keyword, +) -> impl Parser { + expr_ws_followed_by(ws_expr_pos_p(), keyword_ignoring(keyword)) +} + +pub fn demand_ws_expr_ws_keyword_p( + expectation: &str, + keyword: Keyword, +) -> impl Parser { + expr_ws_followed_by( + ws_expr_pos_p().or_expected(expectation), + keyword_ignoring(keyword), + ) +} + +/// Parses the expression using the given parser, +/// then demands whitespace, unless the expression is a parenthesis. +/// Finally it demands the second parser. +pub fn expr_ws_followed_by( + expr_parser: impl Parser, + other_parser: impl Parser, +) -> impl Parser { + expr_parser.then_with_in_context( + conditionally_opt_whitespace() + .to_fatal() + .and_keep_right(other_parser.to_fatal().no_context()), + |e| e.is_parenthesis(), + KeepLeftCombiner, + ) +} + +/// Parses an expression, followed optionally by a keyword and a second expression. +/// If the keyword is present, the second expression is mandatory. +/// +/// Examples: `FOR I = 1 TO 100 [STEP 5]`, `CASE 1 [TO 2]` +pub fn expr_keyword_opt_expr( + keyword: Keyword, +) -> impl Parser), Error = ParserError> { + expression_pos_p().then_with_in_context( + opt_keyword_expr(keyword), + ExpressionTrait::is_parenthesis, + TupleCombiner, + ) +} + +/// Parses the optional `TO expr` part (e.g. `CASE 1 TO 2`) +fn opt_keyword_expr( + keyword: Keyword, +) -> impl Parser, Error = ParserError> { + let msg = format!("expression after {}", keyword); + conditionally_opt_whitespace() + .and_keep_right(keyword_ignoring(keyword).no_context()) + .and_keep_right(ws_expr_pos_p().or_expected(&msg).no_context()) + .to_option() } diff --git a/rusty_parser/src/expr/property.rs b/rusty_parser/src/expr/property.rs index 0f99f186..6de20254 100644 --- a/rusty_parser/src/expr/property.rs +++ b/rusty_parser/src/expr/property.rs @@ -6,7 +6,7 @@ use crate::error::ParserError; use crate::input::StringView; use crate::pc_specific::OrExpected; use crate::tokens::dot; -use crate::*; +use crate::{BareName, Expression, ExpressionPos, ExpressionType, Name, NameAsTokens}; // property ::= "." // property-name ::= diff --git a/rusty_parser/src/expr/single_or_double_literal.rs b/rusty_parser/src/expr/single_or_double_literal.rs index 056eacda..d519bcf5 100644 --- a/rusty_parser/src/expr/single_or_double_literal.rs +++ b/rusty_parser/src/expr/single_or_double_literal.rs @@ -3,7 +3,7 @@ use rusty_pc::*; use crate::input::StringView; use crate::pc_specific::WithPos; use crate::tokens::{digits, dot, pound}; -use crate::{ParserError, *}; +use crate::{Expression, ExpressionPos, ParserError}; // single ::= . // single ::= . (without leading zero) @@ -12,7 +12,7 @@ use crate::{ParserError, *}; // TODO support more qualifiers besides '#' -pub fn parser() -> impl Parser { +pub(super) fn parser() -> impl Parser { // read integer digits optionally (might start with . e.g. `.123`) digits() .to_option() diff --git a/rusty_parser/src/expr/string_literal.rs b/rusty_parser/src/expr/string_literal.rs index 9dd708f9..11ec55a1 100644 --- a/rusty_parser/src/expr/string_literal.rs +++ b/rusty_parser/src/expr/string_literal.rs @@ -4,9 +4,9 @@ use rusty_pc::*; use crate::input::StringView; use crate::pc_specific::*; use crate::tokens::{MatchMode, TokenType, any_symbol_of, any_token_of}; -use crate::{ParserError, *}; +use crate::{Expression, ExpressionPos, ParserError}; -pub fn parser() -> impl Parser { +pub(super) fn parser() -> impl Parser { surround( string_delimiter(), inside_string(), diff --git a/rusty_parser/src/expr/unary_expression.rs b/rusty_parser/src/expr/unary_expression.rs index 3e478fdd..95f95574 100644 --- a/rusty_parser/src/expr/unary_expression.rs +++ b/rusty_parser/src/expr/unary_expression.rs @@ -1,27 +1,34 @@ use rusty_common::Positioned; use rusty_pc::*; -use crate::expr::{expression_pos_p, guard}; +use crate::expr::{expression_pos_p, ws_expr_pos_p}; use crate::input::StringView; use crate::pc_specific::{OrExpected, WithPos, keyword}; use crate::tokens::minus_sign; -use crate::{ParserError, *}; +use crate::{ExpressionPos, ExpressionPosTrait, Keyword, ParserError, UnaryOperator}; -pub fn parser() -> impl Parser { - seq2( - unary_op(), - expression_pos_p().or_expected("expression after unary operator"), - |Positioned { element: op, pos }, expr| expr.apply_unary_priority_order(op, pos), - ) +pub(super) fn parser() -> impl Parser { + unary_minus() + .or(unary_not()) + .map(|(Positioned { element: op, pos }, expr)| expr.apply_unary_priority_order(op, pos)) } -fn unary_op() -> impl Parser, Error = ParserError> { +fn unary_minus() +-> impl Parser, ExpressionPos), Error = ParserError> +{ minus_sign() .map(|_| UnaryOperator::Minus) - .or(keyword(Keyword::Not) - .and_keep_right(guard::parser().to_fatal()) - .map(|_| UnaryOperator::Not)) .with_pos() + .and_tuple(expression_pos_p().or_expected("expression after -")) +} + +fn unary_not() +-> impl Parser, ExpressionPos), Error = ParserError> +{ + keyword(Keyword::Not) + .map(|_| UnaryOperator::Not) + .with_pos() + .and_tuple(ws_expr_pos_p().or_expected("expression after NOT")) } #[cfg(test)] diff --git a/rusty_parser/src/expr/variable.rs b/rusty_parser/src/expr/variable.rs index 0d885f62..14ab606a 100644 --- a/rusty_parser/src/expr/variable.rs +++ b/rusty_parser/src/expr/variable.rs @@ -1,4 +1,3 @@ -// TODO consider nesting variable/function_call modules inside property as they are only used there use std::collections::VecDeque; use rusty_pc::*; @@ -6,7 +5,9 @@ use rusty_pc::*; use crate::core::{name_as_tokens_p, token_to_type_qualifier}; use crate::input::StringView; use crate::pc_specific::WithPos; -use crate::{ParserError, *}; +use crate::{ + BareName, Expression, ExpressionPos, ExpressionType, Name, NameAsTokens, ParserError, VariableInfo +}; // variable ::= // | @@ -15,7 +16,7 @@ use crate::{ParserError, *}; // must not be followed by parenthesis (solved by ordering of parsers) // // if contains dots, it might be converted to a property expression -pub fn parser() -> impl Parser { +pub(super) fn parser() -> impl Parser { name_as_tokens_p().map(map_to_expr).with_pos() } diff --git a/rusty_parser/src/pc_specific/whitespace.rs b/rusty_parser/src/pc_specific/whitespace.rs index e0918fa4..1c86c5d1 100644 --- a/rusty_parser/src/pc_specific/whitespace.rs +++ b/rusty_parser/src/pc_specific/whitespace.rs @@ -1,4 +1,4 @@ -use rusty_pc::{Parser, SurroundMode, surround}; +use rusty_pc::{IifParser, Parser, SurroundMode, surround}; use crate::ParserError; use crate::input::StringView; @@ -70,3 +70,24 @@ where { demand_ws().no_context().and_keep_right(parser) } + +/// Creates a parser that parses whitespace, +/// conditionally allowing it to be missing. +/// When [allow_none] is false, whitespace is mandatory. +/// When [allow_none] is true, the whitespace can be missing. +/// This is typically the case when the previously parsed +/// token was a right side parenthesis. +/// +/// Examples +/// +/// * `(1 + 2)AND` no whitespace is required before `AND` +/// * `1 + 2AND` the lack of whitespace before `AND` is an error +pub fn conditionally_opt_whitespace() +-> impl Parser { + IifParser::new( + // allow none + opt_ws(), + // whitespace is required + whitespace_ignoring(), + ) +} diff --git a/rusty_pc/src/and.rs b/rusty_pc/src/and.rs index b92de24f..cdd418e4 100644 --- a/rusty_pc/src/and.rs +++ b/rusty_pc/src/and.rs @@ -103,9 +103,17 @@ where } } -/// Combines two vectors by concatenating them into one. +/// Combines items into a vector. pub struct VecCombiner; +/// Combines two items into a vector. +impl Combiner> for VecCombiner { + fn combine(&self, left: L, right: L) -> Vec { + vec![left, right] + } +} + +/// Combines two vectors by concatenating them into one. impl Combiner, Vec, Vec> for VecCombiner { fn combine(&self, mut left: Vec, mut right: Vec) -> Vec { left.append(&mut right);