Skip to content

Commit

Permalink
components in text mode are now text
Browse files Browse the repository at this point in the history
  • Loading branch information
Zheoni committed Nov 19, 2023
1 parent 84b7291 commit 37493a8
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 106 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
vice versa.
- Added `ScaledRecipe::group_cookware`.
- Rework `GroupedQuantity` API and add `GroupedValue`.
- Ignored ingredients in text mode are now added as text.

### Fixed
- Text steps were ignored in `components` mode.
Expand Down
16 changes: 11 additions & 5 deletions bindings/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ pub fn parse_recipe(input: String) -> CooklangRecipe {
let converter = Converter::empty();

let mut parser = PullParser::new(&input, extensions);
let parsed = parse_events(&mut parser, extensions, &converter, None)
let parsed = parse_events(&mut parser, &input, extensions, &converter, None)
.take_output()
.unwrap();

Expand All @@ -32,10 +32,16 @@ pub fn parse_metadata(input: String) -> CooklangMetadata {

let parser = PullParser::new(&input, extensions);

let parsed = parse_events(parser.into_meta_iter(), extensions, &converter, None)
.map(|c| c.metadata.map)
.take_output()
.unwrap();
let parsed = parse_events(
parser.into_meta_iter(),
&input,
extensions,
&converter,
None,
)
.map(|c| c.metadata.map)
.take_output()
.unwrap();

// converting IndexMap into HashMap
let _ = &(parsed).iter().for_each(|(key, value)| {
Expand Down
210 changes: 111 additions & 99 deletions src/analysis/event_consumer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ macro_rules! warning {
#[tracing::instrument(level = "debug", skip_all, target = "cooklang::analysis")]
pub fn parse_events<'i>(
events: impl Iterator<Item = Event<'i>>,
input: &'i str,
extensions: Extensions,
converter: &Converter,
recipe_ref_checker: Option<RecipeRefChecker>,
Expand All @@ -57,6 +58,7 @@ pub fn parse_events<'i>(
.flatten();

let col = RecipeCollector {
input,
extensions,
temperature_regex,
converter,
Expand Down Expand Up @@ -85,6 +87,7 @@ pub fn parse_events<'i>(
}

struct RecipeCollector<'i, 'c> {
input: &'i str,
extensions: Extensions,
temperature_regex: Option<&'c Regex>,
converter: &'c Converter,
Expand Down Expand Up @@ -113,7 +116,12 @@ const IMPLICIT_REF_WARN: &str = "The reference (&) is implicit";

impl<'i, 'c> RecipeCollector<'i, 'c> {
fn parse_events(mut self, mut events: impl Iterator<Item = Event<'i>>) -> AnalysisResult {
let mut items = Vec::new();
enum BlockBuffer {
Step(Vec<Item>),
Text(String),
}
let mut current_block = None;

let events = events.by_ref();
while let Some(event) = events.next() {
match event {
Expand All @@ -126,14 +134,33 @@ impl<'i, 'c> RecipeCollector<'i, 'c> {
self.current_section =
Section::new(name.map(|t| t.text_trimmed().into_owned()));
}
Event::Start(_kind) => items.clear(),
Event::Start(kind) => {
let buffer = if self.define_mode == DefineMode::Text {
BlockBuffer::Text(String::new())
} else {
match kind {
BlockKind::Step => BlockBuffer::Step(Vec::new()),
BlockKind::Text => BlockBuffer::Text(String::new()),
}
};
current_block = Some(buffer)
}
Event::End(kind) => {
let new_content = match kind {
_ if self.define_mode == DefineMode::Text => {
Content::Text(self.text_block(items))
let new_content = match current_block {
Some(BlockBuffer::Step(items)) => {
assert_eq!(kind, BlockKind::Step);
Content::Step(Step {
items,
number: self.step_counter,
})
}
Some(BlockBuffer::Text(text)) => {
assert!(
kind == BlockKind::Text || self.define_mode == DefineMode::Text,
);
Content::Text(text)
}
BlockKind::Step => Content::Step(self.step(items)),
BlockKind::Text => Content::Text(self.text_block(items)),
None => panic!("End event without Start"),
};

// If define mode is ingredients, don't add the
Expand All @@ -146,12 +173,16 @@ impl<'i, 'c> RecipeCollector<'i, 'c> {
self.current_section.content.push(new_content);
}

items = Vec::new();
current_block = None;
}
item @ (Event::Text(_)
| Event::Ingredient(_)
| Event::Cookware(_)
| Event::Timer(_)) => items.push(item),
| Event::Timer(_)) => match &mut current_block {
Some(BlockBuffer::Step(items)) => self.in_step(item, items),
Some(BlockBuffer::Text(text)) => self.in_text(item, text),
None => panic!("Content outside block"),
},

Event::Error(e) => {
// on a parser error, collect all other parser errors and
Expand Down Expand Up @@ -316,110 +347,91 @@ impl<'i, 'c> RecipeCollector<'i, 'c> {
self.ctx.warn(warn);
}

fn step(&mut self, items: Vec<Event<'i>>) -> Step {
let mut new_items = Vec::new();

for item in items {
match item {
Event::Text(text) => {
let t = text.text();
if self.define_mode == DefineMode::Components {
// only issue warnings for alphanumeric characters
// so that the user can format the text with spaces,
// hypens or whatever.
if t.contains(|c: char| c.is_alphanumeric()) {
self.ctx.warn(warning!(
"Ignoring text in define components mode",
label!(text.span())
));
}
continue; // ignore text
fn in_step(&mut self, item: Event<'i>, items: &mut Vec<Item>) {
match item {
Event::Text(text) => {
let t = text.text();
if self.define_mode == DefineMode::Components {
// only issue warnings for alphanumeric characters
// so that the user can format the text with spaces,
// hypens or whatever.
if t.contains(|c: char| c.is_alphanumeric()) {
self.ctx.warn(warning!(
"Ignoring text in define components mode",
label!(text.span())
));
}
return; // ignore text
}

// it's only some if the extension is enabled
if let Some(re) = &self.temperature_regex {
debug_assert!(self.extensions.contains(Extensions::TEMPERATURE));

let mut haystack = t.as_ref();
while let Some((before, temperature, after)) =
find_temperature(haystack, re)
{
if !before.is_empty() {
new_items.push(Item::Text {
value: before.to_string(),
});
}
// it's only some if the extension is enabled
if let Some(re) = &self.temperature_regex {
debug_assert!(self.extensions.contains(Extensions::TEMPERATURE));

new_items.push(Item::InlineQuantity {
index: self.content.inline_quantities.len(),
});
self.content.inline_quantities.push(temperature);

haystack = after;
}
if !haystack.is_empty() {
new_items.push(Item::Text {
value: haystack.to_string(),
let mut haystack = t.as_ref();
while let Some((before, temperature, after)) = find_temperature(haystack, re) {
if !before.is_empty() {
items.push(Item::Text {
value: before.to_string(),
});
}
} else {
new_items.push(Item::Text {
value: t.into_owned(),

items.push(Item::InlineQuantity {
index: self.content.inline_quantities.len(),
});
}
}
self.content.inline_quantities.push(temperature);

Event::Ingredient(..) | Event::Cookware(..) | Event::Timer(..) => {
let new_component = match item {
Event::Ingredient(i) => Item::Ingredient {
index: self.ingredient(i),
},
Event::Cookware(c) => Item::Cookware {
index: self.cookware(c),
},
Event::Timer(t) => Item::Timer {
index: self.timer(t),
},
_ => unreachable!(),
};
new_items.push(new_component);
haystack = after;
}
if !haystack.is_empty() {
items.push(Item::Text {
value: haystack.to_string(),
});
}
} else {
items.push(Item::Text {
value: t.into_owned(),
});
}
_ => panic!("Unexpected event in step: {item:?}"),
};
}
}

Step {
items: new_items,
number: self.step_counter,
}
Event::Ingredient(i) => items.push(Item::Ingredient {
index: self.ingredient(i),
}),
Event::Cookware(i) => items.push(Item::Cookware {
index: self.cookware(i),
}),
Event::Timer(i) => items.push(Item::Timer {
index: self.timer(i),
}),

_ => panic!("Unexpected event in step: {item:?}"),
};
}

fn text_block(&mut self, items: Vec<Event<'i>>) -> String {
let mut s = String::new();
for ev in items {
match ev {
Event::Text(t) => s += t.text().as_ref(),
Event::Ingredient(_) | Event::Cookware(_) | Event::Timer(_) => {
assert_eq!(
self.define_mode,
DefineMode::Text,
"Non text event in text block outside define mode text"
);
fn in_text(&mut self, ev: Event<'i>, s: &mut String) {
match ev {
Event::Text(t) => s.push_str(t.text().as_ref()),
Event::Ingredient(_) | Event::Cookware(_) | Event::Timer(_) => {
assert_eq!(
self.define_mode,
DefineMode::Text,
"Non text event in text block outside define mode text"
);

// ignore component
let (c, s) = match ev {
Event::Ingredient(i) => ("ingredient", i.span()),
Event::Cookware(c) => ("cookware", c.span()),
Event::Timer(t) => ("timer", t.span()),
_ => unreachable!(),
};
self.ctx
.warn(warning!(format!("Ignoring {c} in text mode"), label!(s)));
}
_ => panic!("Unexpected event in text block: {ev:?}"),
// ignore component
let (c, span) = match ev {
Event::Ingredient(i) => ("ingredient", i.span()),
Event::Cookware(c) => ("cookware", c.span()),
Event::Timer(t) => ("timer", t.span()),
_ => unreachable!(),
};
self.ctx
.warn(warning!(format!("Ignoring {c} in text mode"), label!(span)));
s.push_str(&self.input[span.range()]);
}
_ => panic!("Unexpected event in text block: {ev:?}"),
}
s
}

fn ingredient(&mut self, ingredient: Located<parser::Ingredient<'i>>) -> usize {
Expand Down
11 changes: 9 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ impl CooklangParser {
let mut parser = parser::PullParser::new(input, self.extensions);
analysis::parse_events(
&mut parser,
input,
self.extensions,
&self.converter,
recipe_ref_checker,
Expand All @@ -276,8 +277,14 @@ impl CooklangParser {
pub fn parse_metadata(&self, input: &str) -> MetadataResult {
let parser = parser::PullParser::new(input, self.extensions);
let meta_events = parser.into_meta_iter();
analysis::parse_events(meta_events, Extensions::empty(), &self.converter, None)
.map(|c| c.metadata)
analysis::parse_events(
meta_events,
input,
Extensions::empty(),
&self.converter,
None,
)
.map(|c| c.metadata)
}
}

Expand Down

0 comments on commit 37493a8

Please sign in to comment.