Skip to content

Commit 33b484d

Browse files
committed
Error message if parenthesis missing binding multiple variables
1 parent 033dd54 commit 33b484d

File tree

4 files changed

+88
-1
lines changed

4 files changed

+88
-1
lines changed

src/formatting/formatter.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,12 @@ pub fn render_invocation<'i>(invocation: &'i Invocation, renderer: &dyn Render)
127127
render_fragments(&sub.fragments, renderer)
128128
}
129129

130+
pub fn render_descriptive<'i>(descriptive: &'i Descriptive, renderer: &dyn Render) -> String {
131+
let mut sub = Formatter::new(78);
132+
sub.append_descriptive(descriptive);
133+
render_fragments(&sub.fragments, renderer)
134+
}
135+
130136
pub fn render_function<'i>(function: &'i Function, renderer: &dyn Render) -> String {
131137
let mut sub = Formatter::new(78);
132138
sub.append_function(function);
@@ -584,7 +590,16 @@ impl<'i> Formatter<'i> {
584590
}
585591
}
586592

587-
fn append_descriptives(&mut self, descriptives: &'i Vec<Descriptive>) {
593+
// This is a helper for rendering a single descriptives in error messages.
594+
// The real method is append_decriptives() below; this method simply
595+
// creates a single element slice that can be passed to it.
596+
fn append_descriptive(&mut self, descriptive: &'i Descriptive) {
597+
use std::slice;
598+
let slice = slice::from_ref(descriptive);
599+
self.append_descriptives(slice);
600+
}
601+
602+
fn append_descriptives(&mut self, descriptives: &'i [Descriptive<'i>]) {
588603
let syntax = self.current;
589604
let mut line = self.builder();
590605

src/parsing/parser.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ pub enum ParsingError {
3232
UnexpectedEndOfInput(usize),
3333
Expected(usize, &'static str),
3434
ExpectedMatchingChar(usize, &'static str, char, char),
35+
MissingParenthesis(usize),
3536
// more specific errors
3637
InvalidCharacter(usize, char),
3738
InvalidHeader(usize),
@@ -68,6 +69,7 @@ impl ParsingError {
6869
ParsingError::Unrecognized(offset) => *offset,
6970
ParsingError::Expected(offset, _) => *offset,
7071
ParsingError::ExpectedMatchingChar(offset, _, _, _) => *offset,
72+
ParsingError::MissingParenthesis(offset) => *offset,
7173
ParsingError::UnclosedInterpolation(offset) => *offset,
7274
ParsingError::InvalidHeader(offset) => *offset,
7375
ParsingError::InvalidCharacter(offset, _) => *offset,
@@ -1297,6 +1299,15 @@ impl<'i> Parser<'i> {
12971299

12981300
if is_binding(content) {
12991301
self.read_binding_expression()
1302+
} else if malformed_binding_pattern(content) {
1303+
if let Some(tilde_pos) = self
1304+
.source
1305+
.find('~')
1306+
{
1307+
self.advance(tilde_pos + 1); // Move past ~
1308+
self.trim_whitespace();
1309+
}
1310+
return Err(ParsingError::MissingParenthesis(self.offset));
13001311
} else if is_repeat_keyword(content) {
13011312
self.read_repeat_expression()
13021313
} else if is_foreach_keyword(content) {
@@ -2022,7 +2033,20 @@ impl<'i> Parser<'i> {
20222033
if parser.peek_next_char() == Some('~') {
20232034
parser.advance(1);
20242035
parser.trim_whitespace();
2036+
let start_pos = parser.offset;
20252037
let variable = parser.read_identifier()?;
2038+
2039+
// Check for malformed tuple binding (missing parentheses)
2040+
parser.trim_whitespace();
2041+
if parser
2042+
.source
2043+
.starts_with(',')
2044+
{
2045+
return Err(ParsingError::MissingParenthesis(
2046+
start_pos,
2047+
));
2048+
}
2049+
20262050
content.push(Descriptive::Binding(
20272051
Box::new(Descriptive::Application(invocation)),
20282052
vec![variable],
@@ -2049,7 +2073,19 @@ impl<'i> Parser<'i> {
20492073
} else if parser.peek_next_char() == Some('~') {
20502074
parser.advance(1);
20512075
parser.trim_whitespace();
2076+
let start_pos = parser.offset;
20522077
let variable = parser.read_identifier()?;
2078+
2079+
parser.trim_whitespace();
2080+
if parser
2081+
.source
2082+
.starts_with(',')
2083+
{
2084+
return Err(ParsingError::MissingParenthesis(
2085+
start_pos,
2086+
));
2087+
}
2088+
20532089
content.push(Descriptive::Binding(
20542090
Box::new(Descriptive::Text(text)),
20552091
vec![variable],
@@ -2705,6 +2741,12 @@ fn is_binding(content: &str) -> bool {
27052741
re.is_match(content)
27062742
}
27072743

2744+
fn malformed_binding_pattern(content: &str) -> bool {
2745+
// Detect ~ identifier, identifier (missing parentheses)
2746+
let re = regex!(r"~\s+[a-z][a-z0-9_]*\s*,\s*[a-z]");
2747+
re.is_match(content)
2748+
}
2749+
27082750
fn is_step_dependent(content: &str) -> bool {
27092751
let re = regex!(r"^\s*\d+\.\s+");
27102752
re.is_match(content)

src/problem/messages.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,30 @@ there was no more input remaining in the current scope.
3535
.trim_ascii()
3636
.to_string(),
3737
),
38+
ParsingError::MissingParenthesis(_) => {
39+
let examples = vec![Descriptive::Binding(
40+
Box::new(Descriptive::Application(Invocation {
41+
target: Target::Local(Identifier("mix_pangalactic_gargle_blaster")),
42+
parameters: None,
43+
})),
44+
vec![Identifier("zaphod"), Identifier("trillian")],
45+
)];
46+
47+
(
48+
"Lists of binding variables must be enclosed in parentheses".to_string(),
49+
format!(
50+
r#"
51+
If you bind the result of an invocation to more than one variable, you must
52+
enclose those names in parenthesis. For example:
53+
54+
{}
55+
"#,
56+
examples[0].present(renderer),
57+
)
58+
.trim_ascii()
59+
.to_string(),
60+
)
61+
}
3862
ParsingError::UnclosedInterpolation(_) => (
3963
"Unclosed string interpolation".to_string(),
4064
r#"

src/problem/present.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,12 @@ impl Present for Invocation<'_> {
7070
}
7171
}
7272

73+
impl Present for Descriptive<'_> {
74+
fn present(&self, renderer: &dyn Render) -> String {
75+
formatter::render_descriptive(self, renderer)
76+
}
77+
}
78+
7379
impl Present for Function<'_> {
7480
fn present(&self, renderer: &dyn Render) -> String {
7581
formatter::render_function(self, renderer)

0 commit comments

Comments
 (0)