Skip to content

Commit 62f62ae

Browse files
authored
refactor(parser): Improve expression parsers regarding whitespace (#121)
* Remove wildcard imports * Reduced visibility of specialized expression parsers * Removed guard parser * Refactored binary_expression implementation * Updated documentation * Refactored operator parser of binary_expression * Deprecate `expr_pos_ws_p` * Added expr_ws_keyword_p and demand_expr_ws_keyword_p * Added `demand_ws_expr_ws_keyword_p` and `ws_expr_ws_keyword_p` * Added some TODO comments * Moved `conditionally_opt_whitespace` to `whitespace` module. Added `expr_keyword_opt_expr` parser. * Removed opt_second_expression module * Added original QBasic doc for OPEN * Leaving OPEN use seq6 for now
1 parent ca24e6c commit 62f62ae

25 files changed

Lines changed: 248 additions & 251 deletions

rusty_parser/src/built_ins/field.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use rusty_common::*;
22
use rusty_pc::*;
33

4-
use crate::expr::expr_pos_ws_p;
4+
use crate::expr::expr_ws_keyword_p;
55
use crate::expr::file_handle::file_handle_p;
66
use crate::input::StringView;
77
use crate::pc_specific::*;
@@ -23,12 +23,9 @@ pub fn parse() -> impl Parser<StringView, Output = Statement, Error = ParserErro
2323

2424
fn field_item_p() -> impl Parser<StringView, Output = (ExpressionPos, NamePos), Error = ParserError>
2525
{
26-
seq3(
27-
expr_pos_ws_p(),
28-
keyword_ws_p(Keyword::As),
26+
expr_ws_keyword_p(Keyword::As).and_tuple(demand_lead_ws(
2927
name_p().with_pos().or_expected("variable name"),
30-
|width, _, name| (width, name),
31-
)
28+
))
3229
}
3330

3431
fn build_args(

rusty_parser/src/built_ins/name.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
use rusty_pc::*;
22

3-
use crate::expr::{ws_expr_pos_p, ws_expr_pos_ws_p};
3+
use crate::expr::{demand_ws_expr_ws_keyword_p, ws_expr_pos_p};
44
use crate::input::StringView;
55
use crate::pc_specific::*;
66
use crate::{BuiltInSub, ParserError, *};
77

88
pub fn parse() -> impl Parser<StringView, Output = Statement, Error = ParserError> {
9-
seq4(
9+
seq3(
1010
keyword(Keyword::Name),
11-
ws_expr_pos_ws_p().or_expected("old file name"),
12-
keyword(Keyword::As),
11+
demand_ws_expr_ws_keyword_p("old file name", Keyword::As),
1312
ws_expr_pos_p().or_expected("new file name"),
14-
|_, l, _, r| Statement::built_in_sub_call(BuiltInSub::Name, vec![l, r]),
13+
|_, l, r| Statement::built_in_sub_call(BuiltInSub::Name, vec![l, r]),
1514
)
1615
}

rusty_parser/src/built_ins/open.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,29 @@ use crate::input::StringView;
77
use crate::pc_specific::*;
88
use crate::tokens::equal_sign_ws;
99
use crate::{BuiltInSub, ParserError, *};
10+
11+
/// Original doc from QBasic regarding OPEN:
12+
///
13+
/// Opens a file or device.
14+
///
15+
/// ```txt
16+
/// OPEN file$ [FOR mode] [ACCESS access] [lock] AS [#]filenumber% [LEN=reclen%]
17+
/// ```
18+
///
19+
/// file$: The name of the file or device. The file name may include a drive and path.
20+
///
21+
/// mode: One of the following file modes: APPEND, BINARY, INPUT, OUTPUT, or RANDOM.
22+
///
23+
/// access: In network environments, specifies whether the file is
24+
/// opened for READ, WRITE, or READ WRITE access.
25+
///
26+
/// lock: Specifies the file locking in network environments:
27+
/// SHARED, LOCK READ, LOCK WRITE, LOCK READ WRITE.
28+
///
29+
/// filenumber%: A number in the range 1 through 255 that identifies the file while it is open.
30+
///
31+
/// reclen%: For random-access files, the record length (default is 128 bytes). For sequential files,
32+
/// the number of characters buffered (default is 512 bytes).
1033
pub fn parse() -> impl Parser<StringView, Output = Statement, Error = ParserError> {
1134
seq6(
1235
keyword(Keyword::Open),
@@ -64,6 +87,8 @@ fn parse_file_number_p() -> impl Parser<StringView, Output = ExpressionPos, Erro
6487
.and_keep_right(guarded_file_handle_or_expression_p().or_expected("#file-number%"))
6588
}
6689

90+
// TODO LEN does not need whitespace if the file expression was in parenthesis
91+
// i.e. OPEN "input.txt" AS (#1)LEN = 10 should be supported.
6792
fn parse_len_p() -> impl Parser<StringView, Output = ExpressionPos, Error = ParserError> {
6893
seq3(
6994
lead_ws(keyword_ignoring(Keyword::Len)),

rusty_parser/src/built_ins/view_print.rs

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
use rusty_pc::and::VecCombiner;
12
use rusty_pc::*;
23

3-
use crate::expr::{ws_expr_pos_p, ws_expr_pos_ws_p};
4+
use crate::expr::{ws_expr_pos_p, ws_expr_ws_keyword_p};
45
use crate::input::StringView;
56
use crate::pc_specific::*;
67
use crate::{BuiltInSub, ParserError, *};
@@ -12,12 +13,7 @@ pub fn parse() -> impl Parser<StringView, Output = Statement, Error = ParserErro
1213
}
1314

1415
fn parse_args() -> impl Parser<StringView, Output = Expressions, Error = ParserError> {
15-
seq3(
16-
ws_expr_pos_ws_p(),
17-
keyword(Keyword::To),
18-
ws_expr_pos_p().or_expected("expression"),
19-
|l, _, r| vec![l, r],
20-
)
16+
ws_expr_ws_keyword_p(Keyword::To).and(ws_expr_pos_p().or_expected("expression"), VecCombiner)
2117
}
2218

2319
#[cfg(test)]

rusty_parser/src/core/dim_name.rs

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ use rusty_pc::*;
44
use crate::core::var_name;
55
use crate::input::StringView;
66
use crate::pc_specific::*;
7-
use crate::{ParserError, *};
7+
use crate::{
8+
ArrayDimensions, BareName, BuiltInStyle, DimList, DimType, Name, ParserError, ToBareName, TypeQualifier, TypedName
9+
};
810

911
pub type DimVar = TypedName<DimType>;
1012
pub type DimVarPos = Positioned<DimVar>;
@@ -122,10 +124,10 @@ where
122124
mod array_dimensions {
123125
use rusty_pc::*;
124126

125-
use crate::expr::{expression_pos_p, opt_second_expression_after_keyword};
127+
use crate::expr::expr_keyword_opt_expr;
126128
use crate::input::StringView;
127129
use crate::pc_specific::*;
128-
use crate::{ParserError, *};
130+
use crate::{ArrayDimension, ArrayDimensions, Keyword, ParserError};
129131

130132
pub fn array_dimensions_p()
131133
-> impl Parser<StringView, Output = ArrayDimensions, Error = ParserError> {
@@ -137,12 +139,7 @@ mod array_dimensions {
137139
// paren_expr ws* TO ws* paren_expr
138140
fn array_dimension_p() -> impl Parser<StringView, Output = ArrayDimension, Error = ParserError>
139141
{
140-
opt_second_expression_after_keyword(
141-
expression_pos_p(),
142-
Keyword::To,
143-
ExpressionTrait::is_parenthesis,
144-
)
145-
.map(|(l, opt_r)| match opt_r {
142+
expr_keyword_opt_expr(Keyword::To).map(|(l, opt_r)| match opt_r {
146143
Some(r) => ArrayDimension {
147144
lbound: Some(l),
148145
ubound: r,
@@ -158,12 +155,12 @@ mod array_dimensions {
158155
mod type_definition {
159156
use rusty_pc::*;
160157

161-
use crate::core::VarNameCtx;
158+
use crate::core::{VarNameCtx, user_defined_type};
162159
use crate::expr::expression_pos_p;
163160
use crate::input::StringView;
164161
use crate::pc_specific::*;
165162
use crate::tokens::star_ws;
166-
use crate::{ParserError, *};
163+
use crate::{BuiltInStyle, DimType, ExpressionPos, Keyword, ParserError, TypeQualifier};
167164

168165
pub fn extended_type()
169166
-> impl Parser<StringView, VarNameCtx, Output = DimType, Error = ParserError> {

rusty_parser/src/core/for_loop.rs

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ use rusty_pc::*;
22

33
use crate::core::statements::zero_or_more_statements;
44
use crate::error::ParserError;
5-
use crate::expr::{expr_pos_ws_p, opt_second_expression_after_keyword, property, ws_expr_pos_p};
5+
use crate::expr::{demand_expr_ws_keyword_p, property, ws_expr_pos_p};
66
use crate::input::StringView;
77
use crate::pc_specific::*;
88
use crate::tokens::equal_sign_ws;
9-
use crate::*;
9+
use crate::{ExpressionPos, ExpressionTrait, ForLoop, Keyword, Statement};
1010

1111
// FOR I = 0 TO 5 STEP 1
1212
// statements
@@ -42,31 +42,43 @@ fn parse_for_step_p() -> impl Parser<
4242
),
4343
Error = ParserError,
4444
> {
45-
opt_second_expression_after_keyword(parse_for_p(), Keyword::Step, |(_var, _low, upper)| {
46-
upper.is_parenthesis()
47-
})
48-
.map(|((n, l, u), opt_step)| (n, l, u, opt_step))
45+
parse_for_p().then_with_in_context(
46+
opt_step_p(),
47+
|(_, _, upper)| upper.is_parenthesis(),
48+
|(n, l, u), opt_step| (n, l, u, opt_step),
49+
)
4950
}
5051

5152
/// Parses the "FOR I = 1 TO 2" part
5253
fn parse_for_p()
5354
-> impl Parser<StringView, Output = (ExpressionPos, ExpressionPos, ExpressionPos), Error = ParserError>
5455
{
55-
seq6(
56-
keyword_ws_p(Keyword::For),
57-
property::parser().or_expected("name after FOR"),
56+
seq5(
57+
keyword_ignoring(Keyword::For),
58+
demand_lead_ws(property::parser().or_expected("name after FOR")),
5859
equal_sign_ws(),
59-
expr_pos_ws_p().or_expected("lower bound of FOR loop"),
60-
keyword(Keyword::To),
60+
demand_expr_ws_keyword_p("lower bound of FOR loop", Keyword::To),
6161
ws_expr_pos_p().or_expected("upper bound of FOR loop"),
62-
|_, name, _, lower_bound, _, upper_bound| (name, lower_bound, upper_bound),
62+
|_, name, _, lower_bound, upper_bound| (name, lower_bound, upper_bound),
6363
)
6464
}
6565

6666
fn next_counter_p() -> impl Parser<StringView, Output = ExpressionPos, Error = ParserError> {
6767
lead_ws(property::parser())
6868
}
6969

70+
fn opt_step_p() -> impl Parser<StringView, bool, Output = Option<ExpressionPos>, Error = ParserError>
71+
{
72+
conditionally_opt_whitespace()
73+
.and_keep_right(keyword_ignoring(Keyword::Step).no_context())
74+
.and_keep_right(
75+
ws_expr_pos_p()
76+
.or_expected("expression after STEP")
77+
.no_context(),
78+
)
79+
.to_option()
80+
}
81+
7082
#[cfg(test)]
7183
mod tests {
7284
use rusty_common::*;

rusty_parser/src/core/if_block.rs

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use crate::core::single_line_statements::{
55
single_line_non_comment_statements_p, single_line_statements_p
66
};
77
use crate::core::statements::zero_or_more_statements;
8-
use crate::expr::ws_expr_pos_ws_p;
8+
use crate::expr::demand_ws_expr_ws_keyword_p;
99
use crate::input::StringView;
1010
use crate::pc_specific::*;
1111
use crate::{ParserError, *};
@@ -35,12 +35,10 @@ pub fn if_block_p() -> impl Parser<StringView, Output = Statement, Error = Parse
3535
// multi line if ::= statements else-if-blocks else-block END IF
3636

3737
fn if_expr_then_p() -> impl Parser<StringView, Output = ExpressionPos, Error = ParserError> {
38-
seq3(
39-
keyword(Keyword::If),
40-
ws_expr_pos_ws_p().or_expected("expression after IF"),
41-
keyword(Keyword::Then),
42-
|_, m, _| m,
43-
)
38+
keyword(Keyword::If).and_keep_right(demand_ws_expr_ws_keyword_p(
39+
"expression after IF",
40+
Keyword::Then,
41+
))
4442
}
4543

4644
fn single_line_if_else_p() -> impl Parser<
@@ -79,12 +77,10 @@ fn multi_line_if_p() -> impl Parser<
7977
}
8078

8179
fn else_if_expr_then_p() -> impl Parser<StringView, Output = ExpressionPos, Error = ParserError> {
82-
seq3(
83-
keyword(Keyword::ElseIf),
84-
ws_expr_pos_ws_p().or_expected("expression after ELSEIF"),
85-
keyword(Keyword::Then),
86-
|_, m, _| m,
87-
)
80+
keyword(Keyword::ElseIf).and_keep_right(demand_ws_expr_ws_keyword_p(
81+
"expression after ELSEIF",
82+
Keyword::Then,
83+
))
8884
}
8985

9086
fn else_if_block_p() -> impl Parser<StringView, Output = ConditionalBlock, Error = ParserError> {

rusty_parser/src/core/select_case.rs

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -105,11 +105,11 @@ mod case_expression_parser {
105105
use rusty_common::Positioned;
106106
use rusty_pc::*;
107107

108-
use crate::expr::{expression_pos_p, opt_second_expression_after_keyword};
108+
use crate::expr::{expr_keyword_opt_expr, expression_pos_p};
109109
use crate::input::StringView;
110110
use crate::pc_specific::*;
111111
use crate::tokens::{TokenType, any_token};
112-
use crate::{CaseExpression, ExpressionTrait, Keyword, Operator, ParserError};
112+
use crate::{CaseExpression, Keyword, Operator, ParserError};
113113

114114
pub fn parser() -> impl Parser<StringView, Output = CaseExpression, Error = ParserError> {
115115
case_is().or(simple_or_range())
@@ -140,12 +140,7 @@ mod case_expression_parser {
140140
}
141141

142142
fn simple_or_range() -> impl Parser<StringView, Output = CaseExpression, Error = ParserError> {
143-
opt_second_expression_after_keyword(
144-
expression_pos_p(),
145-
Keyword::To,
146-
ExpressionTrait::is_parenthesis,
147-
)
148-
.map(|(left, opt_right)| match opt_right {
143+
expr_keyword_opt_expr(Keyword::To).map(|(left, opt_right)| match opt_right {
149144
Some(right) => CaseExpression::Range(left, right),
150145
_ => CaseExpression::Simple(left),
151146
})

rusty_parser/src/expr/binary_expression.rs

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@ use rusty_pc::and::TupleCombiner;
33
use rusty_pc::*;
44

55
use crate::error::ParserError;
6-
use crate::expr::expression_pos_p;
6+
use crate::expr::{expression_pos_p, ws_expr_pos_p};
77
use crate::input::StringView;
88
use crate::pc_specific::{OrExpected, WithPos, lead_opt_ws, lead_ws};
99
use crate::tokens::{TokenType, any_token};
10-
use crate::*;
10+
use crate::{ExpressionPos, ExpressionPosTrait, ExpressionTrait, Keyword, Operator};
1111

1212
// result ::= <non-bin-expr> <operator> <expr>
13-
pub fn parser() -> impl Parser<StringView, Output = ExpressionPos, Error = ParserError> {
13+
pub(super) fn parser() -> impl Parser<StringView, Output = ExpressionPos, Error = ParserError> {
1414
non_bin_expr()
1515
.then_with_in_context(
1616
second_parser(),
@@ -31,28 +31,24 @@ fn second_parser() -> impl Parser<
3131
Error = ParserError,
3232
> {
3333
operator()
34-
.then_with_in_context(third_parser(), is_keyword_op, TupleCombiner)
34+
.then_with_in_context(expr_after_binary_operator(), is_keyword_op, TupleCombiner)
3535
.to_option()
3636
}
3737

3838
fn is_keyword_op(op: &Positioned<Operator>) -> bool {
3939
op.element == Operator::And || op.element == Operator::Or || op.element == Operator::Modulo
4040
}
4141

42-
fn third_parser() -> impl Parser<StringView, bool, Output = ExpressionPos, Error = ParserError> {
42+
fn expr_after_binary_operator()
43+
-> impl Parser<StringView, bool, Output = ExpressionPos, Error = ParserError> {
44+
// boxed breaks apart the recursive type evaluation
4345
IifParser::new(
44-
super::guard::parser().to_fatal(),
45-
super::guard::parser().to_option().map_to_unit(),
46+
// the previous operator is a keyword op, must have whitespace or parenthesis
47+
ws_expr_pos_p().boxed(),
48+
// the previous operator is a symbol, whitespace is optional
49+
lead_opt_ws(expression_pos_p().boxed()),
4650
)
47-
.and_keep_right(right_side_expr().no_context())
48-
}
49-
50-
/// Parses the right side expression, after having parsed the binary operator
51-
fn right_side_expr() -> impl Parser<StringView, Output = ExpressionPos, Error = ParserError> {
52-
// boxed breaks apart the recursive type evaluation
53-
expression_pos_p()
54-
.or_expected("expression after operator")
55-
.boxed()
51+
.or_expected("expression after operator")
5652
}
5753

5854
fn non_bin_expr() -> impl Parser<StringView, Output = ExpressionPos, Error = ParserError> {
@@ -77,7 +73,7 @@ fn operator() -> impl Parser<StringView, bool, Output = Positioned<Operator>, Er
7773
// no whitespace needed
7874
lead_opt_ws(operator_p()),
7975
// whitespace needed
80-
lead_ws(operator_p()).or(lead_opt_ws(symbol_operator_p())),
76+
symbol_operator_p().or(lead_ws(operator_p())),
8177
)
8278
}
8379

rusty_parser/src/expr/built_in_function_call.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ use rusty_pc::Parser;
33
use crate::built_ins::built_in_function_call_p;
44
use crate::input::StringView;
55
use crate::pc_specific::WithPos;
6-
use crate::{ParserError, *};
6+
use crate::{ExpressionPos, ParserError};
77

8-
pub fn parser() -> impl Parser<StringView, Output = ExpressionPos, Error = ParserError> {
8+
pub(super) fn parser() -> impl Parser<StringView, Output = ExpressionPos, Error = ParserError> {
99
built_in_function_call_p().with_pos()
1010
}
1111

0 commit comments

Comments
 (0)