Skip to content

Commit 90ec13e

Browse files
committed
Detect errors in function and invocation names
1 parent af7c271 commit 90ec13e

File tree

2 files changed

+47
-3
lines changed

2 files changed

+47
-3
lines changed

src/parsing/checks/errors.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,32 @@ making_coffee :
239239
);
240240
}
241241

242+
#[test]
243+
fn invalid_function_with_space_in_name() {
244+
expect_error(
245+
r#"
246+
making_coffee :
247+
248+
1. Do something { re peat() }
249+
"#
250+
.trim_ascii(),
251+
ParsingError::InvalidFunction(38, 0),
252+
);
253+
}
254+
255+
#[test]
256+
fn invalid_function_with_space_and_invocation() {
257+
expect_error(
258+
r#"
259+
making_coffee :
260+
261+
1. Do something { re peat <thing>() }
262+
"#
263+
.trim_ascii(),
264+
ParsingError::InvalidFunction(38, 0),
265+
);
266+
}
267+
242268
#[test]
243269
fn invalid_invocation_in_repeat() {
244270
expect_error(

src/parsing/parser.rs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1396,7 +1396,20 @@ impl<'i> Parser<'i> {
13961396
let invocation = self.read_invocation()?;
13971397
Ok(Expression::Application(invocation))
13981398
} else if is_function(content) {
1399-
let target = self.read_identifier()?;
1399+
// Extract the entire text before the opening parenthesis
1400+
self.trim_whitespace();
1401+
let content = self.source;
1402+
let paren = content
1403+
.find('(')
1404+
.unwrap(); // is_function() already checked
1405+
let text = &content[0..paren];
1406+
1407+
// Validate that the entire text is a valid identifier
1408+
let target = validate_identifier(text).ok_or(
1409+
ParsingError::InvalidFunction(self.offset, text.len()),
1410+
)?;
1411+
1412+
self.advance(text.len());
14001413
let parameters = self.read_parameters()?;
14011414

14021415
let function = Function { target, parameters };
@@ -1892,12 +1905,17 @@ impl<'i> Parser<'i> {
18921905

18931906
/// Parse a target like <procedure_name> or <https://example.com/proc>
18941907
fn read_target(&mut self) -> Result<Target<'i>, ParsingError> {
1908+
let start_offset = self.offset;
18951909
self.take_block_chars("an invocation", '<', '>', true, |inner| {
1896-
let content = inner.source;
1910+
let content = inner
1911+
.source
1912+
.trim();
18971913
if content.starts_with("https://") {
18981914
Ok(Target::Remote(External(content)))
18991915
} else {
1900-
let identifier = inner.read_identifier()?;
1916+
let identifier = validate_identifier(content).ok_or_else(|| {
1917+
ParsingError::InvalidInvocation(start_offset + 1, content.len())
1918+
})?;
19011919
Ok(Target::Local(identifier))
19021920
}
19031921
})

0 commit comments

Comments
 (0)