Skip to content

Commit bde18b9

Browse files
committed
Prevent quotes in descriptive text being taken as literal
1 parent 4b03a04 commit bde18b9

3 files changed

Lines changed: 89 additions & 4 deletions

File tree

src/parsing/checks/errors.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,27 @@ making_coffee Ingredients -> Coffee
122122
);
123123
}
124124

125+
// Content a section cannot hold is reported, not quietly dropped on the
126+
// floor as it makes its way past
127+
#[test]
128+
fn unrecognized_content_in_section() {
129+
expect_error(
130+
r#"
131+
making_coffee :
132+
133+
1. Boil the water
134+
135+
I. Second Section
136+
137+
# Overview notes
138+
139+
1. Pour it out
140+
"#
141+
.trim_ascii(),
142+
ParsingError::Unrecognized(Span::new(64, 0)),
143+
);
144+
}
145+
125146
// A malformed declaration must end the procedure before it, rather than
126147
// being taken as description and swallowing the procedure that follows
127148
#[test]

src/parsing/checks/parser.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3328,6 +3328,40 @@ fn test_redundant_error_removal_unclosed_interpolation() {
33283328
}
33293329
}
33303330

3331+
// A quote in descriptive text is punctuation the author wrote, here an inch
3332+
// mark. It opens no literal, so it must not hide the code inline after it
3333+
#[test]
3334+
fn quote_in_descriptive_text() {
3335+
let mut input = Parser::new();
3336+
3337+
let source = r#"Measure the 6" pipe { record_it() } carefully."#;
3338+
3339+
input.initialize(source);
3340+
let result = input.read_descriptive();
3341+
3342+
let paragraphs = result.unwrap();
3343+
let descriptives = &paragraphs[0].0;
3344+
3345+
match &descriptives[0] {
3346+
Descriptive::Text(text) => assert_eq!(*text, "Measure the 6\" pipe"),
3347+
_ => panic!("First element should be text"),
3348+
}
3349+
3350+
match &descriptives[1] {
3351+
Descriptive::CodeInline(exprs) => {
3352+
let [Expression::Execution(func, _)] = exprs.as_slice() else {
3353+
panic!("Second element should be code inline with function execution");
3354+
};
3355+
assert_eq!(
3356+
func.target
3357+
.value,
3358+
"record_it"
3359+
);
3360+
}
3361+
_ => panic!("Second element should be code inline"),
3362+
}
3363+
}
3364+
33313365
#[test]
33323366
fn multiline_code_inline() {
33333367
let mut input = Parser::new();

src/parsing/parser.rs

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -598,7 +598,33 @@ impl<'i> Parser<'i> {
598598
.map(|(i, _)| i)
599599
.unwrap_or(content.len());
600600

601-
let block = &content[..end_pos];
601+
self.take_upto(end_pos, function)
602+
}
603+
604+
/// As take_until(), but for descriptive text, which holds no literals. A
605+
/// quote there is punctuation the author wrote, an old-fashioned mark for
606+
/// the inch unit of measurement, or a quotation; none of which indicate a
607+
/// run of text that needs to be masked.
608+
fn take_text_until<A, F>(&mut self, pattern: &[char], function: F) -> Result<A, ParsingError>
609+
where
610+
F: Fn(&mut Parser<'i>) -> Result<A, ParsingError>,
611+
{
612+
let end_pos = self
613+
.source
614+
.find(pattern)
615+
.unwrap_or(
616+
self.source
617+
.len(),
618+
);
619+
620+
self.take_upto(end_pos, function)
621+
}
622+
623+
fn take_upto<A, F>(&mut self, end_pos: usize, function: F) -> Result<A, ParsingError>
624+
where
625+
F: Fn(&mut Parser<'i>) -> Result<A, ParsingError>,
626+
{
627+
let block = &self.source[..end_pos];
602628
let mut parser = self.subparser(0, block);
603629

604630
// Pass to closure for processing
@@ -1405,7 +1431,9 @@ impl<'i> Parser<'i> {
14051431
.push(error),
14061432
}
14071433
} else {
1408-
// Skip non-procedure content line by line
1434+
outer
1435+
.problems
1436+
.push(ParsingError::Unrecognized(Span::new(outer.offset, 0)));
14091437
outer.skip_to_next_line();
14101438
}
14111439
}
@@ -1444,7 +1472,9 @@ impl<'i> Parser<'i> {
14441472
line.len(),
14451473
)));
14461474
} else {
1447-
// Skip unrecognized content line by line
1475+
outer
1476+
.problems
1477+
.push(ParsingError::Unrecognized(Span::new(outer.offset, 0)));
14481478
outer.skip_to_next_line();
14491479
}
14501480
}
@@ -2628,7 +2658,7 @@ impl<'i> Parser<'i> {
26282658
parser.advance(1);
26292659
content.push(Descriptive::Text("$"));
26302660
} else {
2631-
let text = parser.take_until(
2661+
let text = parser.take_text_until(
26322662
&['{', '<', '$', '~', '\n'],
26332663
|inner| {
26342664
let content = inner

0 commit comments

Comments
 (0)