From 8f4fcea96c6be390ec7204ca416384c96eff17ea Mon Sep 17 00:00:00 2001 From: Rosa Stern Date: Sun, 9 Jun 2019 16:02:53 +0200 Subject: [PATCH 001/107] Add moment and values utils to improve grammar. --- moment/src/period.rs | 8 ++++++++ values/src/dimension.rs | 20 ++++++++++++++++++++ values/src/helpers.rs | 19 ++++++++++--------- 3 files changed, 38 insertions(+), 9 deletions(-) diff --git a/moment/src/period.rs b/moment/src/period.rs index 79412424..fa7a7683 100644 --- a/moment/src/period.rs +++ b/moment/src/period.rs @@ -151,6 +151,14 @@ impl Period { .and_then(|(g, _)| Grain::from_usize(g)) } + pub fn coarser_grain(&self) -> Option { + use enum_primitive::FromPrimitive; + self.0 + .iter() + .min_by_key(|&(g, _)| g) + .and_then(|(g, _)| Grain::from_usize(g)) + } + pub fn comps(&self) -> Vec { use enum_primitive::FromPrimitive; self.0.iter() diff --git a/values/src/dimension.rs b/values/src/dimension.rs index 84a5eb0f..1e66cf07 100644 --- a/values/src/dimension.rs +++ b/values/src/dimension.rs @@ -642,6 +642,22 @@ impl Form { &Form::Span => None, } } + + pub fn is_day(&self) -> bool { + match self { + &Form::Cycle(grain) => { + match grain { + Grain::Day => true, + _ => false, + } + } + &Form::MonthDay(_) => true, + &Form::DayOfWeek { .. } => true, + &Form::DayOfMonth => true, + &Form::Celebration => true, + _ => false, + } + } } #[derive(Debug, Clone, Copy, PartialEq)] @@ -837,6 +853,10 @@ impl DurationValue { self.period.finer_grain().unwrap_or(Grain::Second) } + pub fn get_coarser_grain(&self) -> Grain { + self.period.coarser_grain().unwrap_or(Grain::Second) + } + pub fn from_addition(self, from_addition: FromAddition) -> DurationValue { DurationValue { from_addition: Some(from_addition), .. self} } diff --git a/values/src/helpers.rs b/values/src/helpers.rs index 9e4181f6..4d759886 100644 --- a/values/src/helpers.rs +++ b/values/src/helpers.rs @@ -403,6 +403,7 @@ impl MomentToRuleError for MomentResult { } pub fn year(y: i32) -> RuleResult { + // year between 0 and 99 will be normalized after 1950, e.g. 45 => 2045, 60 => 1960, 99 => 1999 let y = normalize_year(y)?; Ok(DatetimeValue::constraint(Year::new(y)).form(Form::Year(y))) } @@ -522,13 +523,19 @@ pub fn cycle_n_not_immediate(grain: Grain, n: i64) -> RuleResult Ok(DatetimeValue::constraint(Cycle::rc(grain).take_not_immediate(n)).form(Form::Cycle(grain))) } +pub fn weekend() -> RuleResult { + let friday = day_of_week(Weekday::Fri)?.intersect(&hour(18, false)?)?; + let monday = day_of_week(Weekday::Mon)?.intersect(&hour(0, false)?)?; + Ok(friday.span_to(&monday, false)?.datetime_kind(DatetimeKind::DatePeriod)) +} pub fn easter() -> RuleResult { fn offset(i: &Interval, _: &Context) -> Option> { let (year, month, day) = computer_easter(i.start.year()); Some(Interval::ymd(year, month, day)) } - Ok(DatetimeValue::constraint(Month::new(3).invalid_if_err()?.translate_with(offset))) + Ok(DatetimeValue::constraint(Month::new(3).invalid_if_err()?.translate_with(offset)) + .datetime_kind(DatetimeKind::Date)) // otherwise grain is Month; not the cleanest but does the job } pub fn computer_easter(year: i32) -> (i32, u32, u32) { @@ -569,10 +576,7 @@ impl DurationValue { pub fn in_present(&self) -> RuleResult { self.check_period()?; let grain = self.get_grain(); - let datetime_kind = match grain.is_date_grain() { - true => DatetimeKind::Date, - false => DatetimeKind::Time, - }; + let datetime_kind = if grain.is_date_grain() { DatetimeKind::Date } else { DatetimeKind::Time }; Ok(DatetimeValue::constraint(Cycle::rc(Grain::Second) .take_the_nth(0) .shift_by(self.period.clone())).precision(self.precision) @@ -582,10 +586,7 @@ impl DurationValue { pub fn in_present_day(&self) -> RuleResult { self.check_period()?; let grain = self.get_grain(); - let datetime_kind = match grain.is_date_grain() { - true => DatetimeKind::Date, - false => DatetimeKind::Time, - }; + let datetime_kind = if grain.is_date_grain() { DatetimeKind::Date } else { DatetimeKind::Time }; Ok(DatetimeValue::constraint(Cycle::rc(Grain::Day) .take_the_nth(0) .shift_by(self.period.clone())).precision(self.precision) From 05244ccb9738cf2ef9c1fa0904360a482eb3b027 Mon Sep 17 00:00:00 2001 From: Rosa Stern Date: Sun, 9 Jun 2019 16:03:41 +0200 Subject: [PATCH 002/107] Improve French grammar for new Datetime specs (1/2). --- grammar/fr/src/rules.rs | 711 +++++++++++++++++++++++++------------ grammar/fr/src/training.rs | 24 +- 2 files changed, 489 insertions(+), 246 deletions(-) diff --git a/grammar/fr/src/rules.rs b/grammar/fr/src/rules.rs index edfc0fa4..2b7e90ab 100644 --- a/grammar/fr/src/rules.rs +++ b/grammar/fr/src/rules.rs @@ -292,35 +292,14 @@ pub fn rules_duration(b: &mut RuleSetBuilder) -> RustlingResult<()> { duration_check!(), |_, duration| Ok(duration.value().clone().precision(Precision::Approximate)) ); + // Ambiguous w/ time-of-day w/ "pour" + // Duration has less priority than Datetime types, therefore duration will be output only + // if the output kind filter is set for Duration b.rule_2("pendant ", b.reg(r#"pendant|durant|pour(?: une dur[eé]e? d['e])?"#)?, duration_check!(), |_, duration| Ok(duration.value().clone().prefixed()) ); - b.rule_2("il y a ", - b.reg(r#"il y a"#)?, - duration_check!(), - |_, duration| duration.value().ago() - ); - b.rule_2("depuis ", - b.reg(r#"depuis|[cç]a fait"#)?, - duration_check!(), - |_, duration| { - duration.value().ago()? - .span_to(&helpers::cycle_nth(Grain::Second, 0)?, false) - }); - b.rule_3(" apres ", - duration_check!(), - b.reg(r#"apr[eè]s"#)?, - datetime_check!(), - |duration, _, datetime| duration.value().after(datetime.value()) - ); - b.rule_3(" avant ", - duration_check!(), - b.reg(r#"avant"#)?, - datetime_check!(), - |duration, _, datetime| duration.value().before(datetime.value()) - ); Ok(()) } @@ -349,12 +328,17 @@ pub fn rules_cycle(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.reg(r#"mois"#)?, |_| CycleValue::new(Grain::Month) ); + b.rule_1_terminal("trimestre (cycle)", + b.reg(r#"trimestre"#)?, + |_| CycleValue::new(Grain::Quarter) + ); b.rule_1("année (cycle)", b.reg(r#"an(?:n[ée]e?)?s?"#)?, |_| CycleValue::new(Grain::Year) ); + // Cycle patterns relative to now b.rule_2("ce|dans le ", - b.reg(r#"(?:cet?t?e?s?)|(?:dans l[ae']? ?)"#)?, + b.reg(r#"(?:(?:dans )?l[ea' ]|cet?(?:te)?)"#)?, cycle_check!(), |_, cycle| helpers::cycle_nth(cycle.value().grain, 0) ); @@ -364,41 +348,158 @@ pub fn rules_cycle(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.reg(r#"-?ci"#)?, |_, cycle, _| helpers::cycle_nth(cycle.value().grain, 0) ); - + b.rule_2(" dernier", + cycle_check!(), + b.reg(r#"derni[èe]re?|pass[ée]e?|pr[eé]c[eé]dente?|(?:d')? ?avant"#)?, + |cycle, _| helpers::cycle_nth(cycle.value().grain, -1) + ); b.rule_3("le dernier", b.reg(r#"l[ae']? ?"#)?, cycle_check!(), b.reg(r#"derni[èe]re?|pass[ée]e?"#)?, |_, cycle, _| helpers::cycle_nth(cycle.value().grain, -1) ); + b.rule_3("n derniers ", + integer_check_by_range!(2, 9999), + b.reg(r#"derni.re?s?"#)?, + cycle_check!(), + |integer, _, cycle| { + let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, -1 * integer.value().value)?; + // All grains except Day will trigger the right datetime_kind + if cycle.value().grain == Grain::Day { + res = res.datetime_kind(DatetimeKind::DatePeriod); + } + Ok(res) + } + ); + b.rule_4("(pendant/durant/dans) les n derniers ", + b.reg(r#"(?:pendant |durant |dans )?[cld]es"#)?, + integer_check_by_range!(2, 9999), + b.reg(r#"derni.re?s?"#)?, + cycle_check!(), + |_, integer, _, cycle| { + let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, -1 * integer.value().value)?; + // All grains except Day will trigger the right datetime_kind + if cycle.value().grain == Grain::Day { + res = res.datetime_kind(DatetimeKind::DatePeriod); + } + Ok(res) + } + ); + b.rule_3("n passes|precedents", + integer_check_by_range!(2, 9999), + cycle_check!(), + b.reg(r#"pass[eèé][eèé]?s?|pr[eé]c[eé]dente?s?|(?:d')? ?avant|plus t[oô]t"#)?, + |integer, cycle, _| { + let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, -1 * integer.value().value)?; + // All grains except Day will trigger the right datetime_kind + if cycle.value().grain == Grain::Day { + res = res.datetime_kind(DatetimeKind::DatePeriod); + } + Ok(res) + } + ); + b.rule_4("(pendant/durant/dans) les n passes|precedents", + b.reg(r#"(?:pendant |durant |dans )?[cld]es"#)?, + integer_check_by_range!(2, 9999), + cycle_check!(), + b.reg(r#"pass[eèé][eèé]?s?|pr[eé]c[eé]dente?s?|(?:d')? ?avant|plus t[oô]t"#)?, + |_, integer, cycle, _| helpers::cycle_n_not_immediate(cycle.value().grain, -1 * integer.value().value) + ); + // Incorrect resolution if some follows the expression, + // e.g. "suivant le " (unsupported) + b.rule_2(" prochain|suivant|d'après", + cycle_check!(), + b.reg(r#"prochaine?|suivante?|qui suit|(?:d')? ?apr[eèé]s"#)?, + |cycle, _| helpers::cycle_nth(cycle.value().grain, 1) + ); b.rule_3("le prochain|suivant|d'après", b.reg(r#"l[ae']? ?|une? ?"#)?, cycle_check!(), b.reg(r#"prochaine?|suivante?|qui suit|(?:d'? ?)?apr[eèé]s"#)?, |_, cycle, _| helpers::cycle_nth(cycle.value().grain, 1) ); - b.rule_2(" dernier", + b.rule_3("n prochains ", + integer_check_by_range!(2, 9999), + b.reg(r#"prochaine?s?|suivante?s?|apr[eèé]s"#)?, cycle_check!(), - b.reg(r#"derni[èe]re?|pass[ée]e?|pr[eé]c[eé]dente?|(?:d')? ?avant"#)?, - |cycle, _| helpers::cycle_nth(cycle.value().grain, -1) + |integer, _, cycle| { + let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, integer.value().value)?; + // All grains except Day will trigger the right datetime_kind + if cycle.value().grain == Grain::Day { + res = res.datetime_kind(DatetimeKind::DatePeriod); + } + Ok(res) + } ); - b.rule_2(" prochain|suivant|d'après", + b.rule_4("(pendant/durant/dans) les n prochains ", + b.reg(r#"(?:pendant |durant |dans )?[cld]es"#)?, + integer_check_by_range!(2, 9999), + b.reg(r#"prochaine?s?|suivante?s?|apr[eèé]s"#)?, cycle_check!(), - b.reg(r#"prochaine?|suivante?|qui suit|(?:d')? ?apr[eèé]s"#)?, - |cycle, _| helpers::cycle_nth(cycle.value().grain, 1) + |_, integer, _, cycle| { + let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, integer.value().value)?; + // All grains except Day will trigger the right datetime_kind + if cycle.value().grain == Grain::Day { + res = res.datetime_kind(DatetimeKind::DatePeriod); + } + Ok(res) + } + ); + b.rule_3("n suivants", + integer_check_by_range!(2, 9999), + cycle_check!(), + b.reg(r#"prochaine?s?|suivante?s?|apr[eèé]s|qui sui(?:t|ves?)|plus tard"#)?, + |integer, cycle, _| { + let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, integer.value().value)?; + // All grains except Day will trigger the right datetime_kind + if cycle.value().grain == Grain::Day { + res = res.datetime_kind(DatetimeKind::DatePeriod); + } + Ok(res) + } + ); + b.rule_4("(pendant/durant/dans) les n suivants", + b.reg(r#"(?:pendant |durant |dans )?[cld]es"#)?, + integer_check_by_range!(2, 9999), + cycle_check!(), + b.reg(r#"prochaine?s?|suivante?s?|apr[eèé]s|qui sui(?:t|ves?)|plus tard"#)?, + |_, integer, cycle, _| { + let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, integer.value().value)?; + // All grains except Day will trigger the right datetime_kind + if cycle.value().grain == Grain::Day { + res = res.datetime_kind(DatetimeKind::DatePeriod); + } + Ok(res) + } ); b.rule_3("n avant", integer_check_by_range!(2, 9999), cycle_check!(), b.reg(r#"(?:d')? ?avant|plus t[oô]t"#)?, - |integer, cycle, _| helpers::cycle_nth(cycle.value().grain, -1 * integer.value().value) + |integer, cycle, _| { + let mut res = helpers::cycle_nth(cycle.value().grain, -1 * integer.value().value)?; + // All grains except Day will trigger the right datetime_kind + if cycle.value().grain == Grain::Day { + res = res.datetime_kind(DatetimeKind::DatePeriod); + } + Ok(res) + } ); b.rule_3("n après", integer_check_by_range!(2, 9999), cycle_check!(), b.reg(r#"(?:d')? ?apr[eèé]s|qui sui(?:t|ves?)|plus tard"#)?, - |integer, cycle, _| helpers::cycle_nth(cycle.value().grain, integer.value().value) + |integer, cycle, _| { + let mut res = helpers::cycle_nth(cycle.value().grain, integer.value().value)?; + // All grains except Day will trigger the right datetime_kind + if cycle.value().grain == Grain::Day { + res = res.datetime_kind(DatetimeKind::DatePeriod); + } + Ok(res) + } ); + // Cycle patterns relative to another datetime b.rule_4("le après|suivant ", b.reg(r#"l[ea']? ?"#)?, cycle_check!(), @@ -413,59 +514,6 @@ pub fn rules_cycle(b: &mut RuleSetBuilder) -> RustlingResult<()> { datetime_check!(), |_, cycle, _, datetime| helpers::cycle_nth_after(cycle.value().grain, -1, datetime.value()) ); - b.rule_4("les n derniers ", - b.reg(r#"[cld]es"#)?, - integer_check_by_range!(2, 9999), - b.reg(r#"derni.re?s?"#)?, - cycle_check!(), - |_, integer, _, cycle| helpers::cycle_n_not_immediate(cycle.value().grain, -1 * integer.value().value) - ); - b.rule_3("n derniers ", - integer_check_by_range!(2, 9999), - b.reg(r#"derni.re?s?"#)?, - cycle_check!(), - |integer, _, cycle| helpers::cycle_n_not_immediate(cycle.value().grain, -1 * integer.value().value) - ); - - b.rule_4("les n prochains ", - b.reg(r#"[cld]es"#)?, - integer_check_by_range!(2, 9999), - b.reg(r#"prochaine?s?|suivante?s?|apr[eèé]s"#)?, - cycle_check!(), - |_, integer, _, cycle| helpers::cycle_n_not_immediate(cycle.value().grain, integer.value().value) - ); - b.rule_3("n prochains ", - integer_check_by_range!(2, 9999), - b.reg(r#"prochaine?s?|suivante?s?|apr[eèé]s"#)?, - cycle_check!(), - |integer, _, cycle| helpers::cycle_n_not_immediate(cycle.value().grain, integer.value().value) - ); - b.rule_4("les n passes|precedents", - b.reg(r#"[cld]es"#)?, - integer_check_by_range!(2, 9999), - cycle_check!(), - b.reg(r#"pass[eèé][eèé]?s?|pr[eé]c[eé]dente?s?|(?:d')? ?avant|plus t[oô]t"#)?, - |_, integer, cycle, _| helpers::cycle_n_not_immediate(cycle.value().grain, -1 * integer.value().value) - ); - b.rule_3("n passes|precedents", - integer_check_by_range!(2, 9999), - cycle_check!(), - b.reg(r#"pass[eèé][eèé]?s?|pr[eé]c[eé]dente?s?|(?:d')? ?avant|plus t[oô]t"#)?, - |integer, cycle, _| helpers::cycle_n_not_immediate(cycle.value().grain, -1 * integer.value().value) - ); - b.rule_4("les n suivants", - b.reg(r#"[cld]es"#)?, - integer_check_by_range!(2, 9999), - cycle_check!(), - b.reg(r#"prochaine?s?|suivante?s?|apr[eèé]s|qui sui(?:t|ves?)|plus tard"#)?, - |_, integer, cycle, _| helpers::cycle_n_not_immediate(cycle.value().grain, integer.value().value) - ); - b.rule_3("n suivants", - integer_check_by_range!(2, 9999), - cycle_check!(), - b.reg(r#"prochaine?s?|suivante?s?|apr[eèé]s|qui sui(?:t|ves?)|plus tard"#)?, - |integer, cycle, _| helpers::cycle_n_not_immediate(cycle.value().grain, integer.value().value) - ); b.rule_4(" de ", ordinal_check!(), cycle_check!(), @@ -493,7 +541,7 @@ pub fn rules_cycle(b: &mut RuleSetBuilder) -> RustlingResult<()> { datetime_check!(), |_, datetime| helpers::cycle_nth_after_not_immediate(Grain::Day, 1, datetime.value()) ); - b.rule_2("le veille du ", + b.rule_2("la veille du ", b.reg(r#"(la )?veille du"#)?, datetime_check!(), |_, datetime| helpers::cycle_nth_after_not_immediate(Grain::Day, -1, datetime.value()) @@ -590,7 +638,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { |_| helpers::month(8) ); b.rule_1_terminal("named-month", - b.reg(r#"septembre|sept?\.?"#)?, + b.reg(r#"septembre|sept\.|sep\.?"#)?, |_| helpers::month(9) ); b.rule_1_terminal("named-month", @@ -606,11 +654,11 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { |_| helpers::month(12) ); b.rule_1_terminal("noel", - b.reg(r#"(?:jour de )?no[eë]l"#)?, + b.reg(r#"(?:(?:le )?jour de )?no[eë]l"#)?, |_| Ok(helpers::month_day(12, 25)?.form(Form::Celebration)) ); b.rule_1_terminal("soir de noël", - b.reg(r#"(soir(?:ée)?|veille) de no[eë]l"#)?, + b.reg(r#"(?:l[ea] )?(?:soir(?:ée)?|veille|r[eé]veillon) de no[eë]l"#)?, |_| { let start = helpers::month_day(12, 24)?.intersect(&helpers::hour(18, false)?)?; let end = helpers::month_day(12, 25)?.intersect(&helpers::hour(0, false)?)?; @@ -618,6 +666,10 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { .form(Form::Celebration)) } ); + b.rule_1_terminal("saint sylvestre", + b.reg(r#"(?:l[ea] )?(?:saint[- ]sylvestre|r[eé]veillon)"#)?, + |_| Ok(helpers::month_day(12, 31)?.form(Form::Celebration)) + ); b.rule_1_terminal("jour de l'an", b.reg(r#"(?:le )?(?:jour de l'|nouvel )an"#)?, |_| Ok(helpers::month_day(1, 1)?.form(Form::Celebration)) @@ -664,8 +716,8 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { .form(Form::Celebration)) ); - b.rule_1_terminal("pencôte", - b.reg(r#"(?:la f[eê]te de la |la |le lundi de la )?penc[oô]te"#)?, + b.rule_1_terminal("pentecôte", + b.reg(r#"(?:la f[eê]te de |(?:le )?lundi de )?(?:la )?pentec[oô]te"#)?, |_| Ok(helpers::cycle_nth_after(Grain::Day, 49, &helpers::easter()?)? .form(Form::Celebration)) ); @@ -702,14 +754,21 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { |_| Ok(helpers::month_day(8, 15)? .form(Form::Celebration)) ); + b.rule_2("à ", + b.reg(r#"au|[aà](?:l['a])?"#)?, + datetime_check!(form!(Form::Celebration)), + |_, a| Ok(a.value().clone()) + ); b.rule_1_terminal("maintenant", - b.reg(r#"maintenant|(?:tout de suite)"#)?, + b.reg(r#"maintenant|tout de suite|en ce moment"#)?, |_| helpers::cycle_nth(Grain::Second, 0) ); b.rule_1_terminal("aujourd'hui", - b.reg(r#"(?:aujourd'? ?hui)|(?:ce jour)|(?:dans la journ[ée]e?)|(?:en ce moment)"#)?, + b.reg(r#"(?:aujourd'? ?hui)|(?:ce jour)|(?:dans la journ[ée]e?)"#)?, |_| helpers::cycle_nth(Grain::Day, 0) ); + // FIXME: "le lendemain" interpreted as demain, not as relative to another date + // but there is a rule "le lendemain du " - inconsistent b.rule_1_terminal("demain", b.reg(r#"(?:demain)|(?:le lendemain)"#)?, |_| helpers::cycle_nth(Grain::Day, 1) @@ -744,17 +803,30 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.rule_2("ce ", b.reg(r#"ce"#)?, datetime_check!(), - |_, datetime| datetime.value().the_nth(0) + |_, datetime| Ok(datetime.value().the_nth(0)? + .datetime_kind(datetime.value().datetime_kind.clone())) ); b.rule_2(" prochain", datetime_check!(form!(Form::DayOfWeek{..})), b.reg(r#"prochain"#)?, |datetime, _| datetime.value().the_nth_not_immediate(0) ); + b.rule_2(" prochain", + datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), + b.reg(r#"prochaine?"#)?, + |datetime, _| datetime.value().the_nth_not_immediate(0) + ); + b.rule_2("au prochain ", + b.reg(r#"(au|[aà] la) prochaine?"#)?, + datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), + |_, datetime| datetime.value().the_nth_not_immediate(0) + ); b.rule_2(" prochain", - datetime_check!(), + // The direction check is to avoid application of datetime_check(month) on rule result + // "avant " + datetime_check!(|datetime: &DatetimeValue| form!(Form::Month(_))(datetime) && !datetime.direction.is_some()), b.reg(r#"prochain"#)?, - |datetime, _| datetime.value().the_nth(1) + |datetime, _| datetime.value().the_nth_not_immediate(0) ); b.rule_2(" suivant|d'après", datetime_check!(), @@ -804,45 +876,41 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.reg(r#"week(?:\s|-)?end (?:d['eu]|en|du mois de)"#)?, datetime_check!(form!(Form::Month(_))), |ordinal, _, datetime| { - let week_day_start = helpers::day_of_week(Weekday::Fri)?.intersect(&helpers::hour(18, false)?)?; - let week_day_end = helpers::day_of_week(Weekday::Mon)?.intersect(&helpers::hour(0, false)?)?; - let week_day = week_day_start.span_to(&week_day_end, false)?; - let week_ends_of_time = datetime.value().intersect(&week_day)?; - week_ends_of_time.the_nth(ordinal.value().value - 1) + let weekend = helpers::weekend()?; + let nth_week_end = datetime.value().intersect(&weekend)?; + nth_week_end.the_nth(ordinal.value().value - 1) } ); b.rule_2("dernier week-end de ", b.reg(r#"(?:le )?dernier week(?:\s|-)?end (?:du mois d[e']|d['eu]|en)"#)?, datetime_check!(form!(Form::Month(_))), |_, datetime| { - let week_day_start = helpers::day_of_week(Weekday::Fri)?.intersect(&helpers::hour(18, false)?)?; - let week_day_end = helpers::day_of_week(Weekday::Mon)?.intersect(&helpers::hour(0, false)?)?; - let week_day = week_day_start.span_to(&week_day_end, false)?; - week_day.last_of(datetime.value()) + let weekend = helpers::weekend()?; + weekend.last_of(datetime.value()) } ); + // FIXME: change latency ranges for years? E.g. latent until 1900? b.rule_1("year", integer_check_by_range!(1000, 2100), - |integer| { - helpers::year(integer.value().value as i32) - } + |integer| helpers::year(integer.value().value as i32) ); b.rule_1("year (latent)", integer_check_by_range!(-1000, 999), - |integer| { - Ok(helpers::year(integer.value().value as i32)?.latent()) - } + |integer| Ok(helpers::year(integer.value().value as i32)?.latent()) + ); + b.rule_2("l'année ", + b.reg(r#"l[' ]an(?:n[eé]+)?"#)?, + integer_check!(), + |_, integer| helpers::year(integer.value().value as i32) ); b.rule_2("en ", - b.reg(r#"(?:en(?: l'an)?|de l'ann[eé])"#)?, + b.reg(r#"en"#)?, datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::Year(_))(datetime)), |_, year| Ok(year.value().clone()) ); b.rule_1("year (latent)", integer_check_by_range!(2101, 3000), - |integer| { - Ok(helpers::year(integer.value().value as i32)?.latent()) - } + |integer| Ok(helpers::year(integer.value().value as i32)?.latent()) ); b.rule_1_terminal("day of month (premier)", b.reg(r#"premier|prem\.?|1er|1 er"#)?, @@ -853,11 +921,11 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { integer_check_by_range!(1, 31), |_, integer| helpers::day_of_month(integer.value().value as u32) ); - b.rule_4("le à ", + b.rule_4("le à ", b.reg(r#"le"#)?, integer_check_by_range!(1, 31), b.reg(r#"[aà]"#)?, - datetime_check!(), + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(_))(datetime)), |_, integer, _, datetime| { let day_of_month = helpers::day_of_month(integer.value().value as u32)?; day_of_month.intersect(&datetime.value()) @@ -866,86 +934,44 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.rule_2(" ", integer_check_by_range!(1, 31), datetime_check!(form!(Form::Month(_))), - |integer, month| month.value().intersect(&helpers::day_of_month(integer.value().value as u32)?) + |integer, month| Ok(month.value() + .intersect(&helpers::day_of_month(integer.value().value as u32)?)? + .form(Form::DayOfMonth)) ); b.rule_2(" ", datetime_check!(form!(Form::DayOfWeek{..})), // Weird it is not used in the production of the rule integer_check_by_range!(1, 31), |_, integer| helpers::day_of_month(integer.value().value as u32) ); - b.rule_3(" à )", + b.rule_4(" à )", datetime_check!(form!(Form::DayOfWeek{..})), // Weird it is not used in the production of the rule integer_check_by_range!(1, 31), - datetime_check!(form!(Form::TimeOfDay(_))), - |_, integer, tod| helpers::day_of_month(integer.value().value as u32) + b.reg(r#"[aà]"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(_))(datetime)), + |_, integer, _, tod| helpers::day_of_month(integer.value().value as u32) ?.intersect(tod.value()) ); - b.rule_1("time-of-day (latent)", + b.rule_1(" (latent)", integer_check_by_range!(1, 23), |integer| Ok(helpers::hour(integer.value().value as u32, integer.value().value < 12)?.latent()) ); - b.rule_1("time-of-day (latent)", + b.rule_1(" (latent)", integer_check_by_range!(0, 0), |_| Ok(helpers::hour(0, false)?.latent()) ); b.rule_1_terminal("midi", - b.reg(r#"midi"#)?, + b.reg(r#"midi(?: pile| exactement| pr[eé]cises)?"#)?, |_| helpers::hour(12, false) ); b.rule_1_terminal("minuit", - b.reg(r#"minuit"#)?, + b.reg(r#"minuit(?: pile| exactement| pr[eé]cises)?"#)?, |_| helpers::hour(0, false) ); b.rule_2(" heures", datetime_check!(form!(Form::TimeOfDay(TimeOfDayForm::Hour { .. }))), - b.reg(r#"h\.?(?:eure)?s?"#)?, + b.reg(r#"h\.?(?:eure)?s?(?: pile| exactement| pr[eé]cises?)?"#)?, |a, _| Ok(a.value().clone().not_latent()) ); - b.rule_2("à|vers ", - b.reg(r#"(?:vers|autour de|[aà] environ|aux alentours de|[aà])"#)?, - datetime_check!(form!(Form::TimeOfDay(_))), - |_, a| Ok(a.value().clone().not_latent()) - ); - b.rule_1_terminal("hh(:|h)mm (time-of-day)", - b.reg(r#"((?:[01]?\d)|(?:2[0-3]))[:h]([0-5]\d)"#)?, - |text_match| { - let hour: u32 = text_match.group(1).parse()?; - let minute: u32 = text_match.group(2).parse()?; - helpers::hour_minute(hour, minute, hour < 12) - } - ); - b.rule_3_terminal("hh(:|h)mm - hh(:|h)mm (time-of-day interval)", - b.reg(r#"((?:[01]?\d)|(?:2[0-3]))[:h]([0-5]\d)"#)?, - b.reg(r#" ?\- ?"#)?, - b.reg(r#"((?:[01]?\d)|(?:2[0-3]))[:h]([0-5]\d)"#)?, - |a, _, b| { - let hour_start: u32 = a.group(1).parse()?; - let minute_start: u32 = a.group(2).parse()?; - let hour_end: u32 = b.group(1).parse()?; - let minute_end: u32 = b.group(2).parse()?; - let start = helpers::hour_minute(hour_start, minute_start, hour_start < 12)?; - let end = helpers::hour_minute(hour_end, minute_end, hour_end < 12)?; - start.smart_span_to(&end, false) - } - ); - b.rule_1_terminal("hh:mm:ss", - b.reg(r#"((?:[01]?\d)|(?:2[0-3]))[:.]([0-5]\d)[:.]([0-5]\d)"#)?, - |text_match| helpers::hour_minute_second( - text_match.group(1).parse()?, - text_match.group(2).parse()?, - text_match.group(3).parse()?, - false - ) - - ); - b.rule_1_terminal("hhmm (military time-of-day)", - b.reg(r#"((?:[01]?\d)|(?:2[0-3]))([0-5]\d)"#)?, - |text_match| Ok(helpers::hour_minute( - text_match.group(1).parse()?, - text_match.group(2).parse()?, - false - )?.latent()) - ); b.rule_1_terminal("quart (relative minutes)", b.reg(r#"(?:un )?quart"#)?, |_| Ok(RelativeMinuteValue(15)) @@ -976,6 +1002,16 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { datetime.value().form_time_of_day()?.is_12_clock() ) ); + b.rule_3(" (as relative minutes) exactly", + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(TimeOfDayForm::Hour { .. }))(datetime)), + relative_minute_check!(), + b.reg(r#"pile|exactement|pr[ée]cises?"#)?, + |datetime, minutes, _| helpers::hour_relative_minute( + datetime.value().form_time_of_day()?.full_hour(), + minutes.value().0, + datetime.value().form_time_of_day()?.is_12_clock() + ) + ); b.rule_3(" moins (as relative minutes)", datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(TimeOfDayForm::Hour { .. }))(datetime)), b.reg(r#"moins(?: le)?"#)?, @@ -986,6 +1022,17 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { datetime.value().form_time_of_day()?.is_12_clock() ) ); + b.rule_4(" moins (as relative minutes) exactly ", + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(TimeOfDayForm::Hour { .. }))(datetime)), + b.reg(r#"moins(?: le)?"#)?, + relative_minute_check!(), + b.reg(r#"pile|exactement|pr[ée]cises?"#)?, + |datetime, _, minutes, _| helpers::hour_relative_minute( + datetime.value().form_time_of_day()?.full_hour(), + -1 * minutes.value().0, + datetime.value().form_time_of_day()?.is_12_clock() + ) + ); b.rule_3(" et|passé de ", datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(TimeOfDayForm::Hour { .. }))(datetime)), b.reg(r#"et|pass[ée]e?s? de"#)?, @@ -996,7 +1043,78 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { datetime.value().form_time_of_day()?.is_12_clock() ) ); - // Written dates in numeric formats + b.rule_4(" et|passé de exactly", + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(TimeOfDayForm::Hour { .. }))(datetime)), + b.reg(r#"et|pass[ée]e?s? de"#)?, + relative_minute_check!(), + b.reg(r#"pile|exactement|pr[ée]cises?"#)?, + |datetime, _, minutes, _| helpers::hour_relative_minute( + datetime.value().form_time_of_day()?.full_hour(), + minutes.value().0, + datetime.value().form_time_of_day()?.is_12_clock() + ) + ); + b.rule_4(" de exactly", + datetime_check!(form!(Form::TimeOfDay(_))), + b.reg(r#"(?:d[eu] |dans )(?:l[ 'a])?"#)?, + datetime_check!(form!(Form::PartOfDay(_))), + b.reg(r#"pile|exactement|pr[eé]cises?"#)?, + |a, _, b, _| Ok(a.value().intersect(b.value())?.form(a.value().form.clone())) + ); + // Adding "pour" here makes time-of-day ambiguous w/ Duration + // Duration has less priority than Datetime types, therefore duration will be output only + // if the output kind filter is set for Duration + b.rule_2("à ", + b.reg(r#"[aà]|pour"#)?, + datetime_check!(form!(Form::TimeOfDay(_))), + |_, a| Ok(a.value().clone().not_latent()) + ); + b.rule_2("vers ", + b.reg(r#"(?:plut[ôo]t )?(?:vers|autour de|[aà] environ|aux alentours de)"#)?, + datetime_check!(form!(Form::TimeOfDay(_))), + |_, a| Ok(a.value().clone().not_latent().precision(Precision::Approximate)) + ); + // Written time/date in numeric formats + b.rule_1_terminal("hh(:|h)mm (time-of-day)", + b.reg(r#"((?:[01]?\d)|(?:2[0-3]))[:h]([0-5]\d)"#)?, + |text_match| { + let hour: u32 = text_match.group(1).parse()?; + let minute: u32 = text_match.group(2).parse()?; + helpers::hour_minute(hour, minute, hour < 12) + } + ); + b.rule_3_terminal("hh(:|h)mm - hh(:|h)mm (time-of-day interval)", + b.reg(r#"((?:[01]?\d)|(?:2[0-3]))[:h]([0-5]\d)"#)?, + b.reg(r#" ?\- ?"#)?, + b.reg(r#"((?:[01]?\d)|(?:2[0-3]))[:h]([0-5]\d)"#)?, + |a, _, b| { + let hour_start: u32 = a.group(1).parse()?; + let minute_start: u32 = a.group(2).parse()?; + let hour_end: u32 = b.group(1).parse()?; + let minute_end: u32 = b.group(2).parse()?; + let start = helpers::hour_minute(hour_start, minute_start, hour_start < 12)?; + let end = helpers::hour_minute(hour_end, minute_end, hour_end < 12)?; + start.smart_span_to(&end, false) + } + ); + b.rule_1_terminal("hh:mm:ss", + b.reg(r#"((?:[01]?\d)|(?:2[0-3]))[:.]([0-5]\d)[:.]([0-5]\d)"#)?, + |text_match| helpers::hour_minute_second( + text_match.group(1).parse()?, + text_match.group(2).parse()?, + text_match.group(3).parse()?, + false + ) + + ); + b.rule_1_terminal("hhmm (military time-of-day)", + b.reg(r#"((?:[01]\d)|(?:2[0-3]))([0-5]\d)"#)?, + |text_match| Ok(helpers::hour_minute( + text_match.group(1).parse()?, + text_match.group(2).parse()?, + false + )?.latent()) + ); b.rule_1_terminal("yyyy-mm-dd - ISO", b.reg(r#"(\d{4})[-/](0?[1-9]|1[0-2])[-/](3[01]|[12]\d|0?[1-9])"#)?, |text_match| helpers::year_month_day( @@ -1020,7 +1138,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { text_match.group(2).parse()?, text_match.group(1).parse()?) ); - // End of Written dates in numeric formats + // End of Written time/date in numeric formats b.rule_1_terminal("matin", b.reg(r#"mat(?:in[ée]?e?)?"#)?, |_| Ok(helpers::hour(4, false)? @@ -1225,7 +1343,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { .span_to(&helpers::hour(23, false)?, false)? .form(Form::Meal)) ); - b.rule_1_terminal("nuit", + b.rule_1_terminal("nuit", b.reg(r#"nuit"#)?, |_| { Ok(helpers::hour(22, false)? @@ -1234,63 +1352,83 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { .form(Form::PartOfDay(PartOfDayForm::Night))) } ); - b.rule_2("a l'heure ", + b.rule_2("a l'heure de ", b.reg(r#"(?:[àa] )?l[' ]heure du|au moment du|pendant l[ea']|au|pour l[ea']|l[ea']"#)?, - datetime_check!(|datetime: &DatetimeValue| datetime.latent && form!(Form::Meal)(datetime)), + datetime_check!(|datetime: &DatetimeValue| form!(Form::Meal)(datetime)), |_, a| Ok(a.value().clone().not_latent()) ); - b.rule_2(" ", - datetime_check!(), + b.rule_2(" ", + datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), datetime_check!(form!(Form::Meal)), |a, b| a.value().intersect(b.value()) ); - b.rule_2("du|dans le ", - b.reg(r#"pendant(?: l[ae']?)?|durant(?: l[ae']?)?|du|(?:[aà]|dans) l[ae']?|au|en|l[ae']|d[èe]s(?: l[ae']?)?"#)?, - datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), + b.rule_2("prep? & article ", // This is very catch-all/junky + b.reg(r#"(?:pendant |durant |dans |d[eè]s )?l[ae']?|en|au"#)?, + datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime)), |_, a| Ok(a.value().clone().not_latent()) ); b.rule_2("ce ", b.reg(r#"cet?t?e?"#)?, datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), - |_, a| Ok(helpers::cycle_nth(Grain::Day, 0)?.intersect(a.value())?.form(a.value().form.clone())) + |_, datetime| Ok(helpers::cycle_nth(Grain::Day, 0)? + .intersect(datetime.value())? + .form(datetime.value().form.clone()) + .datetime_kind(DatetimeKind::DatetimeComplement { date_and_time: true, today: true })) ); - b.rule_2(" ", - datetime_check!(excluding_form!(Form::TimeOfDay(_))), + b.rule_2(" ", + datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), |a, b| a.value().intersect(b.value()) ); - b.rule_2(" du matin", + b.rule_2(" du matin", datetime_check!(form!(Form::TimeOfDay(_))), - b.reg(r#"(?:(?:du|dans|de) )?(?:(?:au|le|la) )?mat(?:in[ée]?e?)?"#)?, + b.reg(r#"(?:(?:du|dans|de) )?(?:(?:au|le|la) )?mat(?:in[ée]?e?)?(?: pile| exactement| pr[eé]cises?)?"#)?, |a, _| { let period = helpers::hour(0, false)? .span_to(&helpers::hour(12, false)?, false)?; - a.value().intersect(&period) + Ok(a.value().intersect(&period)?.form(a.value().form.clone())) + } + ); + b.rule_2(" de l'apres-midi", + datetime_check!(form!(Form::TimeOfDay(_))), + b.reg(r#"(?:dans |de )?l[' ]apr[eè]s[\- ]midi(?: pile| exactement| pr[eé]cises?)?"#)?, + |a, _| { + let period = helpers::hour(12, false)? + .span_to(&helpers::hour(19, false)?, false)?; + Ok(a.value().intersect(&period)?.form(a.value().form.clone())) } ); - b.rule_2(" du soir", + b.rule_2(" du soir", datetime_check!(form!(Form::TimeOfDay(_))), - b.reg(r#"(?:(?:du|dans|de) )?(?:(?:au|le|la) )?soir[ée]?e?"#)?, + b.reg(r#"(?:(?:du|dans|de) )?(?:(?:au|le|la) )?soir[ée]?e?(?: pile| exactement| pr[eé]cises?)?"#)?, |a, _| { let period = helpers::hour(16, false)? .span_to(&helpers::hour(0, false)?, false)?; - a.value().intersect(&period) + Ok(a.value().intersect(&period)?.form(a.value().form.clone())) } ); - b.rule_3(" du ", + b.rule_3(" du ", datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), b.reg(r#"du"#)?, - datetime_check!(), + datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), |a, _, b| b.value().intersect(a.value()) ); - b.rule_1_terminal("week-end", - b.reg(r#"week(?:\s|-)?end"#)?, + b.rule_1_terminal("(ce/le) week-end", + b.reg(r#"(?:[cl]e )?week(?:\s|-)?end"#)?, + |_| helpers::weekend() + ); + b.rule_1_terminal("le week-end dernier", + b.reg(r#"le week(?:\s|-)?end dernier"#)?, + |_| { + let weekend = helpers::weekend()?; + Ok(weekend.the_nth(-1)?.datetime_kind(DatetimeKind::DatePeriod)) + } + ); + b.rule_1_terminal("le week-end prochain", + b.reg(r#"le week(?:\s|-)?end prochain|le prochain week(?:\s|-)?end"#)?, |_| { - let friday = helpers::day_of_week(Weekday::Fri)? - .intersect(&helpers::hour(18, false)?)?; - let monday = helpers::day_of_week(Weekday::Mon)? - .intersect(&helpers::hour(0, false)?)?; - friday.span_to(&monday, false) + let weekend = helpers::weekend()?; + Ok(weekend.the_nth(1)?.datetime_kind(DatetimeKind::DatePeriod)) } ); b.rule_1_terminal("début de semaine", @@ -1336,7 +1474,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { ); b.rule_4("dd-dd (interval)", b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, - b.reg(r#"\-|au|jusqu'au"#)?, + b.reg(r#"\-|(?:jusqu')?au"#)?, b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, datetime_check!(form!(Form::Month(_))), |a, _, b, month| { @@ -1347,7 +1485,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { ); b.rule_4("-dd (interval)", datetime_check!(), - b.reg(r#"\-|au|jusqu'au"#)?, + b.reg(r#"\-|(?:jusqu')?au"#)?, b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, datetime_check!(form!(Form::Month(_))), |datetime, _, text_match, month| { @@ -1358,7 +1496,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { ); b.rule_5("- dd (interval)", datetime_check!(), - b.reg(r#"\-|au|jusqu'au"#)?, + b.reg(r#"\-|(?:jusqu')?au"#)?, datetime_check!(form!(Form::DayOfWeek{..})), b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, datetime_check!(form!(Form::Month(_))), @@ -1371,7 +1509,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.rule_6(" 1er- dd (interval)", datetime_check!(form!(Form::DayOfWeek{..})), b.reg(r#"premier|prem\.?|1er|1 er"#)?, - b.reg(r#"\-|au|jusqu'au"#)?, + b.reg(r#"\-|(?:jusqu')?au"#)?, datetime_check!(form!(Form::DayOfWeek{..})), b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, datetime_check!(form!(Form::Month(_))), @@ -1384,7 +1522,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.rule_6("du dd- dd (interval)", b.reg(r#"du"#)?, b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, - b.reg(r#"\-|au|jusqu'au"#)?, + b.reg(r#"\-|(?:jusqu')?au"#)?, datetime_check!(form!(Form::DayOfWeek{..})), b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, datetime_check!(form!(Form::Month(_))), @@ -1397,7 +1535,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.rule_6("du dd- dd (interval)", b.reg(r#"du"#)?, datetime_check!(), - b.reg(r#"\-|au|jusqu'au"#)?, + b.reg(r#"\-|(?:jusqu')?au"#)?, datetime_check!(form!(Form::DayOfWeek{..})), b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, datetime_check!(form!(Form::Month(_))), @@ -1410,7 +1548,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.rule_4("la nuit ", b.reg(r#"(dans|pendant|durant) la nuit (?:du|de)"#)?, datetime_check!(form!(Form::DayOfWeek{..})), - b.reg(r#"\-|au|jusqu'au"#)?, + b.reg(r#"\-|(?:jusqu')?au"#)?, datetime_check!(form!(Form::DayOfWeek{..})), |_, start, _, end| { let start = start.value().intersect(&helpers::hour(22, false)?)?; @@ -1433,7 +1571,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.rule_4_terminal("du dd au dd(interval)", b.reg(r#"du"#)?, b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, - b.reg(r#"au|jusqu'au"#)?, + b.reg(r#"(?:jusqu')?au"#)?, b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, |_, a, _, b| { let start = helpers::day_of_month(a.group(1).parse()?)?; @@ -1486,9 +1624,11 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { start.span_to(&end, true) } ); + + /* Intervals */ b.rule_3(" - (interval)", datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), - b.reg(r#"\-|au|[aà]|jusqu'(?:au|[aà])"#)?, + b.reg(r#" \- |(?:jusqu')?(?:au|[aà])"#)?, datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), |a, _, b| a.value().span_to(b.value(), true) ); @@ -1501,7 +1641,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.rule_4("de - (interval)", b.reg(r#"depuis|d[e'u]?"#)?, datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), - b.reg(r#"\-|au|[aà]|jusqu'(?:au|[aà])"#)?, + b.reg(r#" \- |(?:jusqu')?(?:au|[aà])"#)?, datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), |_, a, _, b| a.value().span_to(b.value(), true) ); @@ -1516,7 +1656,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.rule_5("de - (interval)", b.reg(r#"depuis|d[e'u]?"#)?, datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), - b.reg(r#"\-|au|[aà]|jusqu'(?:au|[aà])"#)?, + b.reg(r#" \- |(?:jusqu')?(?:au|[aà])"#)?, datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime) && datetime.is_coarse_grain_greater_than(Grain::Year)), datetime_check!(form!(Form::Year(_))), |_, a, _, b, year| a.value().span_to(b.value(), true)?.intersect(year.value()) @@ -1531,17 +1671,31 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { ); b.rule_3(" - (interval)", datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(_))(datetime)), - b.reg(r#" \- |[aà]|au|jusqu'(?:au|[aà])"#)?, + b.reg(r#" \- |(?:jusqu')?(?:au|[aà])"#)?, datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(_))(datetime)), |a, _, b| a.value().smart_span_to(b.value(), false) ); b.rule_4("de - (interval)", - b.reg(r#"(?:midi )?de"#)?, + b.reg(r#"(?:[aà] partir )?d['e]"#)?, datetime_check!(form!(Form::TimeOfDay(_))), - b.reg(r#"[aà]|au|jusqu'(?:au|[aà])"#)?, + b.reg(r#"(?:jusqu')?(?:au|[aà])"#)?, datetime_check!(form!(Form::TimeOfDay(_))), |_, a, _, b| a.value().smart_span_to(b.value(), false) ); + b.rule_2("de maintenant - (interval)", + b.reg(r#"(?:[aà] partir )?de maintenant (?:jusqu')?(?:au|[aà])"#)?, + datetime_check!(form!(Form::TimeOfDay(_))), + |_, a| helpers::cycle_nth(Grain::Second, 0)?.smart_span_to(a.value(), false) + ); + b.rule_3("de - maintenant (interval)", + b.reg(r#"(?:[aà] partir )?d['e]"#)?, + datetime_check!(form!(Form::TimeOfDay(_))), + b.reg(r#"(?:jusqu')?(?:au|[aà]) maintenant"#)?, + |_, a, _| { + let now = helpers::cycle_nth(Grain::Second, 0)?; + a.value().smart_span_to(&now, false) + } + ); b.rule_4("entre et (interval)", b.reg(r#"entre"#)?, datetime_check!(form!(Form::TimeOfDay(_))), @@ -1549,33 +1703,29 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { datetime_check!(form!(Form::TimeOfDay(_))), |_, a, _, b| a.value().smart_span_to(b.value(), false) ); - b.rule_2("d'ici ", - b.reg(r#"d'ici|dans l(?:'|es?)"#)?, - duration_check!(), - |_, duration| { - let start = helpers::cycle_nth(Grain::Second, 0)?; - let end = duration.value().in_present()?; - start.span_to(&end, false) - } - ); - b.rule_2("avant ", - b.reg(r#"(?:n[ ']importe quand )?jusqu'(?:a|à)"#)?, - datetime_check!(), + b.rule_2("jusqu'à ", + b.reg(r#"(?:n[ ']importe quand )?jusqu'(?:au|[aà]|en)"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent), |_, datetime| Ok(datetime.value().clone().mark_before_end()) ); - b.rule_2("avant ", + b.rule_2("avant ", b.reg(r#"(?:n[ ']importe quand )?avant"#)?, - datetime_check!(), + datetime_check!(|datetime: &DatetimeValue| !datetime.latent), |_, datetime| Ok(datetime.value().clone().mark_before_start()) ); - b.rule_2("après ", - b.reg(r#"apr(?:e|è)s"#)?, - datetime_check!(), + b.rule_2("après ", + b.reg(r#"apr[eè]s"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent), |_, datetime| Ok(datetime.value().clone().mark_after_end()) ); - b.rule_2("après ", - b.reg(r#"(?:a|à) partir de"#)?, - datetime_check!(), + b.rule_2("à partir de ", + b.reg(r#"[aà] partir d['eu]"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent), + |_, datetime| Ok(datetime.value().clone().mark_after_start()) + ); + b.rule_2("à partir de ", + b.reg(r#"[aà] partir d['eu](?:l[ 'a])?"#)?, + datetime_check!(form!(Form::PartOfDay(_))), |_, datetime| Ok(datetime.value().clone().mark_after_start()) ); b.rule_2("après le ", @@ -1584,10 +1734,95 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { |_, integer| Ok(helpers::day_of_month(integer.value().value as u32)?.mark_after_end()) ); b.rule_2("après le ", - b.reg(r#"(?:a|à) partir du"#)?, + b.reg(r#"[aà] partir d['eu]"#)?, integer_check_by_range!(1, 31), |_, integer| Ok(helpers::day_of_month(integer.value().value as u32)?.mark_after_start()) ); + b.rule_2("il y a ", + b.reg(r#"il y a"#)?, + duration_check!(), + |_, duration| duration.value().ago() + ); + // With "depuis/d'ici", interpretation of duration ambiguous with time-of-day we choose time-of-day + // I.e. "x heures (y)", but not "x heures et y minutes", "x minutes", etc. + // FIXME: some time-of-day patterns should be removed, e.g. "x heures y minutes" - they are not + // proper time-of-day, but they can be duration expressions + // TODO: check if Grain::Second here breaks DatePeriod - cf. implem. of equivalent in English? + b.rule_2("depuis ", + b.reg(r#"depuis|[cç]a fait"#)?, + duration_check!(), + |_, duration| { + if duration.value().get_coarser_grain() == Grain::Hour { + return Err(RuleError::Invalid.into()) + } + duration.value().ago()? + .span_to(&helpers::cycle_nth(Grain::Second, 0)?, false) + }); + b.rule_2("depuis ", + b.reg(r#"depuis"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent), + |_, datetime| Ok(datetime.value().the_nth(-1)?.mark_after_start()) + ); + b.rule_2("d'ici ", + b.reg(r#"d'ici|dans l(?:'|es?)"#)?, + duration_check!(), + |_, duration| { + let duration_grain = duration.value().get_coarser_grain(); + // Priority to d'ici + if duration_grain == Grain::Hour && + // FIXME: There must be a better way to do this check! + duration.value().period.0.get(Grain::Hour as usize).unwrap_or(&0) <= &23 { + return Err(RuleError::Invalid.into()) + } + let grain = if duration_grain.is_date_grain() { Grain::Day } else { Grain::Second }; + let start = helpers::cycle_nth(grain, 0)?; + let end = if grain == Grain::Day { duration.value().in_present_day()? } else { duration.value().in_present()? }; + start.span_to(&end, true) + } + ); + b.rule_2("dans le ", + b.reg(r#"dans l(?:'|es?)"#)?, + duration_check!(), + |_, duration| { + let duration_grain = duration.value().get_coarser_grain(); + let grain = if duration_grain.is_date_grain() { Grain::Day } else { Grain::Second }; + let start = helpers::cycle_nth(grain, 0)?; + let end = if grain == Grain::Day { duration.value().in_present_day()? } else { duration.value().in_present()? }; + start.span_to(&end, false) + } + ); + b.rule_2("d'ici ", + b.reg(r#"d'ici"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(_))(datetime)), + |_, tod| { + // FIXME: This adds one second to the value of now+then + let now = helpers::cycle_nth(Grain::Second, 0)?; + let then = tod.value().clone().mark_before_start(); + now.span_to(&then, false) + } + ); + b.rule_2("d'ici ", + b.reg(r#"d'ici"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), + |_, date| { + // FIXME: This adds one second to the value of now+then + let today = helpers::cycle_nth(Grain::Day, 0)?; + let then = date.value().clone().mark_before_start(); + today.span_to(&then, false) + } + ); + b.rule_3(" apres ", + duration_check!(), + b.reg(r#"apr[eè]s"#)?, + datetime_check!(), + |duration, _, datetime| duration.value().after(datetime.value()) + ); + b.rule_3(" avant ", + duration_check!(), + b.reg(r#"avant"#)?, + datetime_check!(), + |duration, _, datetime| duration.value().before(datetime.value()) + ); Ok(()) } @@ -1687,6 +1922,10 @@ pub fn rules_numbers(b: &mut RuleSetBuilder) -> RustlingResult<()> { }; IntegerValue::new(value) }); + b.rule_1_terminal("quelques", + b.reg(r#"quelques"#)?, + |_| IntegerValue::new_with_grain(3, 1) + ); b.rule_1_terminal("number (20..60)", b.reg(r#"(vingt|trente|quarante|cinquante|soixante)"#)?, |text_match| { diff --git a/grammar/fr/src/training.rs b/grammar/fr/src/training.rs index 60bce085..1314a0f5 100644 --- a/grammar/fr/src/training.rs +++ b/grammar/fr/src/training.rs @@ -48,8 +48,8 @@ pub fn examples_finance(v: &mut Vec<::rustling::train::Example>) { pub fn examples_datetime(v: &mut Vec<::rustling::train::Example>) { let c = ResolverContext::new(Interval::starting_at(Moment(Local.ymd(2013, 2, 12).and_hms(4, 30, 0)), Grain::Second)); - example!(v, check_moment!(c, [2013, 2, 12, 4, 30, 00]), "maintenant", "tout de suite"); - example!(v, check_moment!(c, [2013, 2, 12]), "aujourd'hui", "ce jour", "dans la journée", "en ce moment"); + example!(v, check_moment!(c, [2013, 2, 12, 4, 30, 00]), "maintenant", "tout de suite", "en ce moment"); + example!(v, check_moment!(c, [2013, 2, 12]), "aujourd'hui", "ce jour", "dans la journée"); example!(v, check_moment!(c, [2013, 2, 11]), "hier", "le jour d'avant", "le jour précédent", "la veille"); example!(v, check_moment!(c, [2013, 2, 10]), "avant-hier"); example!(v, check_moment!(c, [2013, 2, 13]), "demain", "jour suivant", "le jour d'après", "le lendemain", "un jour après"); @@ -65,16 +65,19 @@ pub fn examples_datetime(v: &mut Vec<::rustling::train::Example>) { example!(v, check_moment!(c, [2013, 3, 1]), "le 1er mars", "premier mars", "le 1 mars", "vendredi 1er mars"); example!(v, check_moment!(c, [2013, 3, 1]), "le premier mars 2013", "1/3/2013", "2013-03-01"); example!(v, check_moment!(c, [2013, 3, 2]), "le 2 mars", "2 mars", "le 2/3"); - example!(v, check_moment!(c, [2013, 3, 2, 5]), "le 2 mars à 5h", "2 mars à 5h", "le 2/3 à 5h", "le 2 mars à 5h du matin", "le 2 mars vers 5h", "2 mars vers 5h", "2 mars à environ 5h", "2 mars aux alentours de 5h", "2 mars autour de 5h", "le 2/3 vers 5h"); + example!(v, check_moment!(c, [2013, 3, 2, 5]), "le 2 mars à 5h", "2 mars à 5h", "le 2/3 à 5h", "le 2 mars à 5h du matin"); + example!(v, check_moment_with_precision!(c, [2013, 3, 2, 5], Precision::Approximate), "le 2 mars vers 5h", "2 mars vers 5h", "2 mars à environ 5h", "2 mars aux alentours de 5h", "2 mars autour de 5h", "le 2/3 vers 5h"); example!(v, check_moment!(c, [2013, 3, 2]), "le 2"); - example!(v, check_moment!(c, [2013, 3, 2, 5]), "le 2 à 5h", "le 2 vers 5h", "le 2 à 5h du mat"); + example!(v, check_moment!(c, [2013, 3, 2, 5]), "le 2 à 5h", "le 2 à 5h du mat"); + example!(v, check_moment_with_precision!(c, [2013, 3, 2, 5], Precision::Approximate), "le 2 vers 5h"); example!(v, check_moment!(c, [2013, 3, 3]), "le 3 mars", "3 mars", "le 3/3"); example!(v, check_moment!(c, [2013, 4, 5]), "le 5 avril", "5 avril"); example!(v, check_moment!(c, [2015, 3, 3]), "le 3 mars 2015", "3 mars 2015", "3/3/2015", "2015-3-3", "2015-03-03"); example!(v, check_moment!(c, [2013, 2, 15]), "le 15 février", "15 février"); example!(v, check_moment!(c, [2013, 2, 15]), "15/02/2013", "15 fev 2013"); example!(v, check_moment!(c, [2013, 2, 16]), "le 16"); - example!(v, check_moment!(c, [2013, 2, 16, 18]), "le 16 à 18h", "le 16 vers 18h", "le 16 plutôt vers 18h", "le 16 à 6h du soir", "le 16 vers 6h du soir", "le 16 vers 6h dans la soirée", "samedi 16 à 18h"); + example!(v, check_moment!(c, [2013, 2, 16, 18]), "le 16 à 18h", "le 16 à 6h du soir", "samedi 16 à 18h"); + example!(v, check_moment_with_precision!(c, [2013, 2, 16, 18], Precision::Approximate), "le 16 vers 18h", "le 16 plutôt vers 18h", "le 16 vers 6h du soir", "le 16 vers 6h dans la soirée"); example!(v, check_moment!(c, [2013, 2, 17]), "17 février", "le 17 février", "17/2", "17/02", "le 17/02", "17 02", "17 2", "le 17 02", "le 17 2"); example!(v, check_moment!(c, [2013, 2, 13]), "mercredi 13"); //when today is Tuesday 12, "mercredi 13" should be tomorrow example!(v, check_moment!(c, [2014, 2, 20]), "20/02/2014", "20/2/2014", "20/02/14", "le 20/02/14", "le 20/2/14", "20 02 2014", "20 02 14", "20 2 2014", "20 2 14", "le 20 02 2014", "le 20 02 14", "le 20 2 2014", "le 20 2 14"); @@ -115,7 +118,8 @@ pub fn examples_datetime(v: &mut Vec<::rustling::train::Example>) { example!(v, check_moment!(c, [2015, 10, 31]), "dernier jour d'octobre 2015", "le dernier jour d'octobre 2015"); example!(v, check_moment!(c, [2014, 9, 22], Grain::Week), "dernière semaine de septembre 2014", "la dernière semaine de septembre 2014"); //Hours - example!(v, check_moment!(c, [2013, 2, 12, 15]), "à quinze heures", "à 15 heures", "à 3 heures cet après-midi", "15h", "15H", "vers 15 heures", "à environ 15 heures"); + example!(v, check_moment!(c, [2013, 2, 12, 15]), "à quinze heures", "à 15 heures", "à 3 heures cet après-midi", "15h", "15H"); + example!(v, check_moment_with_precision!(c, [2013, 2, 12, 15], Precision::Approximate), "vers 15 heures", "à environ 15 heures"); example!(v, check_moment!(c, [2013, 2, 12, 15, 0]), "15:00", "15h00", "15H00"); example!(v, check_moment!(c, [2013, 2, 13, 00]), "minuit"); example!(v, check_moment!(c, [2013, 2, 12, 12]), "midi", "aujourd'hui à midi"); @@ -224,11 +228,11 @@ pub fn examples_datetime(v: &mut Vec<::rustling::train::Example>) { example!(v, check_moment_with_direction!(c, [2013, 2, 20, 10], Direction::After), "le 20 à partir de 10h"); example!(v, check_moment_with_direction!(c, [2013, 2, 15, 12], Direction::After), "vendredi à partir de midi"); example!(v, check_moment_span!(c, [2013, 2, 20], [2013, 2, 20, 18]), "le 20 jusqu'à 18h"); - example!(v, check_moment_span!(c, [2014, 9, 14], [2014, 9, 21]), "14 - 20 sept. 2014"); - example!(v, check_moment_span!(c, [2013, 2, 12, 4, 30, 0], [2013, 2, 26]), "d'ici 2 semaines"); + example!(v, check_moment_span!(c, [2014, 9, 14], [2014, 9, 21]), "14 - 20 sept. 2014", "14 - 20 sep 2014"); // but not "14 - 20 sept 2014" + example!(v, check_moment_span!(c, [2013, 2, 12], [2013, 2, 27]), "d'ici 2 semaines"); //15j != 2 semaines - example!(v, check_moment_span!(c, [2013, 2, 12, 4, 30, 0], [2013, 5, 12]), "d'ici 3 mois"); - example!(v, check_moment_span!(c, [2013, 2, 12, 4, 30, 0], [2013, 2, 27]), "dans les 15 jours"); + example!(v, check_moment_span!(c, [2013, 2, 12], [2013, 5, 13]), "d'ici 3 mois"); + example!(v, check_moment_span!(c, [2013, 2, 12], [2013, 2, 28]), "dans les 15 jours"); example!(v, check_moment_span!(c, [2013, 2, 12, 5], [2013, 2, 12, 7]), "de 5 à 7"); example!(v, check_moment_span!(c, [2013, 2, 14, 9], [2013, 2, 14, 11]), "jeudi de 9h à 11h"); example!(v, check_moment_span!(c, [2013, 2, 12, 12], [2013, 2, 12, 14]), "entre midi et 2"); From 07205b9bc2dc5c0b03aa93c39c25011c9c348bb7 Mon Sep 17 00:00:00 2001 From: Rosa Stern Date: Sun, 9 Jun 2019 16:42:16 +0200 Subject: [PATCH 003/107] Detail --- grammar/en/src/rules_datetime.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/grammar/en/src/rules_datetime.rs b/grammar/en/src/rules_datetime.rs index 29be3408..ea34207c 100644 --- a/grammar/en/src/rules_datetime.rs +++ b/grammar/en/src/rules_datetime.rs @@ -1260,6 +1260,7 @@ pub fn rules_datetime_with_duration(b: &mut RuleSetBuilder) -> Rustli } +// FIXME: rename "rules_datetime_with_cycle" pub fn rules_datetime_with_nth_cycle(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.rule_2("this ", From 4e9c3f4ec129db5fde772beefac4c5730c96942d Mon Sep 17 00:00:00 2001 From: Rosa Stern Date: Sun, 9 Jun 2019 16:43:04 +0200 Subject: [PATCH 004/107] Split French grammar per entity type. --- grammar/fr/src/lib.rs | 23 +- grammar/fr/src/rules.rs | 2446 -------------------------- grammar/fr/src/rules_amount.rs | 243 +++ grammar/fr/src/rules_celebrations.rs | 114 ++ grammar/fr/src/rules_datetime.rs | 1443 +++++++++++++++ grammar/fr/src/rules_duration.rs | 117 ++ grammar/fr/src/rules_number.rs | 561 ++++++ 7 files changed, 2493 insertions(+), 2454 deletions(-) delete mode 100644 grammar/fr/src/rules.rs create mode 100644 grammar/fr/src/rules_amount.rs create mode 100644 grammar/fr/src/rules_celebrations.rs create mode 100644 grammar/fr/src/rules_datetime.rs create mode 100644 grammar/fr/src/rules_duration.rs create mode 100644 grammar/fr/src/rules_number.rs diff --git a/grammar/fr/src/lib.rs b/grammar/fr/src/lib.rs index 00c89d87..1672f388 100644 --- a/grammar/fr/src/lib.rs +++ b/grammar/fr/src/lib.rs @@ -3,7 +3,11 @@ extern crate rustling; extern crate rustling_ontology_values; extern crate rustling_ontology_moment; -pub mod rules; +mod rules_datetime; +mod rules_celebrations; +mod rules_duration; +mod rules_number; +mod rules_amount; pub mod training; use rustling_ontology_values::DimensionKind::*; @@ -12,13 +16,16 @@ pub fn rule_set() -> ::rustling::RustlingResult<::rustling::RuleSet) -> RustlingResult<()> { - b.rule_2(" per cent", - number_check!(), - b.reg(r"(?:%|p\.c\.|p. cents?|pour[ -]?cents?)")?, - |number, _| Ok(PercentageValue(number.value().value())) - ); - Ok(()) -} - -pub fn rules_finance(b: &mut RuleSetBuilder) -> RustlingResult<()> { - b.rule_2("intersect (X cents)", - amount_of_money_check!(), - amount_of_money_check!(|money: &AmountOfMoneyValue| money.unit == Some("cent")), - |a, b| helpers::compose_money(a.value(), b.value())); - b.rule_3("intersect (and X cents)", - amount_of_money_check!(), - b.reg(r#"et"#)?, - amount_of_money_check!(|money: &AmountOfMoneyValue| money.unit == Some("cent")), - |a, _, b| helpers::compose_money(&a.value(), &b.value())); - b.rule_2("intersect", - amount_of_money_check!(|money: &AmountOfMoneyValue| money.unit != Some("cent")), - number_check!(), - |a, b| helpers::compose_money_number(&a.value(), &b.value())); - b.rule_1_terminal("$", - b.reg(r#"\$|dollars?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("$") }) - ); - b.rule_1_terminal("EUR", - b.reg(r#"€|(?:[e€]uro?s?)"#)?, - |_| Ok(MoneyUnitValue { unit: Some("EUR") }) - ); - b.rule_1_terminal("£", - b.reg(r#"£|livres?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("£") }) - ); - b.rule_1_terminal("USD", - b.reg(r#"us[d\$]|dollars? am[eé]ricains?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("USD") }) - ); - b.rule_1_terminal("AUD", - b.reg(r#"au[d\$]|dollars? australiens?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("AUD") }) - ); - b.rule_1_terminal("CAD", - b.reg(r#"cad|dollars? canadiens?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("CAD") }) - ); - b.rule_1_terminal("HKD", - b.reg(r#"hkd|dollars? de hong[- ]kong"#)?, - |_| Ok(MoneyUnitValue { unit: Some("HKD") }) - ); - b.rule_1_terminal("KR", - b.reg(r#"kr|couronnes?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("KR") }) - ); - b.rule_1_terminal("DKK", - b.reg(r#"dkk|couronnes? danoises?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("DKK") }) - ); - b.rule_1_terminal("NOK", - b.reg(r#"nok|couronnes? norv[ée]giennes?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("NOK") }) - ); - b.rule_1_terminal("SEK", - b.reg(r#"sek|couronnes? su[ée]doises?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("SEK") }) - ); - b.rule_1_terminal("CHF", - b.reg(r#"chf|francs? suisses?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("CHF") }) - ); - b.rule_1_terminal("RUB", - b.reg(r#"rub|roubles?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("RUB") }) - ); - b.rule_1_terminal("INR", - b.reg(r#"inr|roupies?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("INR") }) - ); - b.rule_1_terminal("JPY", - b.reg(r#"jpy|yens?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("JPY") }) - ); - b.rule_1_terminal("RMB|CNH|CNY", - b.reg(r#"cny|cnh|rmb|yuans?|renmimbis?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("CNY") }) - ); - b.rule_1_terminal("¥", - b.reg(r#"¥"#)?, - |_| Ok(MoneyUnitValue { unit: Some("¥") }) - ); - b.rule_1_terminal("KRW", - b.reg(r#"₩|krw|wons? (?:sud[- ])?cor[ée]ns?|wons?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("KRW") }) - ); - b.rule_1_terminal("Bitcoin", - b.reg(r#"฿|bitcoins?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("฿") }) - ); - b.rule_1_terminal("GBP", - b.reg(r#"gbp|livres? sterlings?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("GBP") }) - ); - b.rule_1_terminal("cent", - b.reg(r#"centimes?|cents?|penn(?:y|ies)|fens?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("cent") }) - ); - b.rule_1_terminal("unnamed currency", - b.reg(r#"(?:balle)s?"#)?, - |_| Ok(MoneyUnitValue { unit: None }) - ); - b.rule_2(" ", - number_check!(), - money_unit!(), - |a, b| { - Ok(AmountOfMoneyValue { - value: a.value().value(), - unit: b.value().unit, - ..AmountOfMoneyValue::default() - }) - }); - b.rule_3(" de ", // "un million de dollars" - integer_check!(|integer: &IntegerValue| !integer.group), - b.reg(r#"d[e']"#)?, - money_unit!(), - |a, _, b| { - Ok(AmountOfMoneyValue { - value: a.value().value as f32, - precision: Exact, - unit: b.value().unit, - ..AmountOfMoneyValue::default() - }) - }); - b.rule_3(" de ", // "une douzaine de dollars" - integer_check!(|integer: &IntegerValue| integer.group), - b.reg(r#"d[e']"#)?, - money_unit!(), - |a, _, b| { - Ok(AmountOfMoneyValue { - value: a.value().value as f32, - precision: Approximate, - unit: b.value().unit, - ..AmountOfMoneyValue::default() - }) - }); - b.rule_2("about ", - b.reg(r#"(?:autour|pas loin|pr[eè]s|aux alentours) d[e']|environ|presque|(?:approximative|quasi)ment"#)?, - amount_of_money_check!(), - |_, a| { - Ok(AmountOfMoneyValue { - precision: Approximate, - ..a.value().clone() - }) - }); - b.rule_2("exactly ", - b.reg(r#"(?:tr[eè]s )?exactement|pr[eé]cis[eé]ment|pile(?: poil)?"#)?, - amount_of_money_check!(), - |_, a| { - Ok(AmountOfMoneyValue { - precision: Exact, - ..a.value().clone() - }) - }); - b.rule_2("exactly ", - amount_of_money_check!(), - b.reg(r#"pile(?: poil)?|tout rond"#)?, - |a, _| { - Ok(AmountOfMoneyValue { - precision: Exact, - ..a.value().clone() - }) - } - ); - Ok(()) -} - -pub fn rules_duration(b: &mut RuleSetBuilder) -> RustlingResult<()> { - b.rule_1_terminal("seconde (unit-of-duration)", - b.reg(r#"sec(?:onde)?s?"#)?, - |_| Ok(UnitOfDurationValue::new(Grain::Second)) - ); - b.rule_1_terminal("minute (unit-of-duration)", - b.reg(r#"min(?:ute)?s?"#)?, - |_| Ok(UnitOfDurationValue::new(Grain::Minute)) - ); - b.rule_1_terminal("heure (unit-of-duration)", - b.reg(r#"h(?:eure)?s?"#)?, - |_| Ok(UnitOfDurationValue::new(Grain::Hour)) - ); - b.rule_1_terminal("jour (unit-of-duration)", - b.reg(r#"jour(?:n[ée]e?)?s?"#)?, - |_| Ok(UnitOfDurationValue::new(Grain::Day)) - ); - b.rule_1_terminal("semaine (unit-of-duration)", - b.reg(r#"semaines?"#)?, - |_| Ok(UnitOfDurationValue::new(Grain::Week)) - ); - b.rule_1_terminal("mois (unit-of-duration)", - b.reg(r#"mois?"#)?, - |_| Ok(UnitOfDurationValue::new(Grain::Month)) - ); - b.rule_1_terminal("année (unit-of-duration)", - b.reg(r#"an(?:n[ée]e?)?s?"#)?, - |_| Ok(UnitOfDurationValue::new(Grain::Year)) - ); - b.rule_1_terminal("un quart heure", - b.reg(r#"(1/4\s?h(?:eure)?|(?:un|1) quart d'heure)"#)?, - |_| Ok(DurationValue::new(PeriodComp::minutes(15).into())) - ); - b.rule_1_terminal("une demi heure", - b.reg(r#"(?:1/2\s?h(?:eure)?|(?:1|une) demi(?:e)?(?:\s|-)heure)"#)?, - |_| Ok(DurationValue::new(PeriodComp::minutes(30).into())) - ); - b.rule_1_terminal("trois quarts d'heure", - b.reg(r#"(?:3/4\s?h(?:eure)?|(?:3|trois) quart(?:s)? d'heure)"#)?, - |_| Ok(DurationValue::new(PeriodComp::minutes(45).into())) - ); - b.rule_2(" ", - integer_check_by_range!(0), - unit_of_duration_check!(), - |integer, unit| Ok(DurationValue::new(PeriodComp::new(unit.value().grain, integer.value().value).into())) - ); - b.rule_3(" de ", - integer_check!(|integer: &IntegerValue| integer.value >= 0 && integer.group), - b.reg(r#"d[e']"#)?, - unit_of_duration_check!(), - |integer, _, unit| Ok(DurationValue::new(PeriodComp::new(unit.value().grain, integer.value().value).into())) - ); - b.rule_4(" h ", - integer_check_by_range!(0), - b.reg(r#"h(?:eures?)?"#)?, - integer_check_by_range!(0,59), - b.reg(r#"m(?:inutes?)?"#)?, - |hour, _, minute, _| { - let hour_period = Period::from(PeriodComp::new(Grain::Hour, hour.value().clone().value)); - let minute_period = Period::from(PeriodComp::new(Grain::Minute, minute.value().clone().value)); - Ok(DurationValue::new(hour_period + minute_period)) - } - ); - b.rule_3(" et quart", - integer_check_by_range!(0), - unit_of_duration_check!(), - b.reg(r#"et quart"#)?, - |integer, uod, _| { - let quarter_period: Period = uod.value().grain.quarter_period().map(|a| a.into()).ok_or_else(|| RuleError::Invalid)?; - Ok(DurationValue::new(quarter_period + PeriodComp::new(uod.value().grain, integer.value().value))) - } - ); - b.rule_3(" et demie", - integer_check_by_range!(0), - unit_of_duration_check!(), - b.reg(r#"et demie?"#)?, - |integer, uod, _| { - let half_period: Period = uod.value().grain.half_period().map(|a| a.into()).ok_or_else(|| RuleError::Invalid)?; - Ok(DurationValue::new(half_period + PeriodComp::new(uod.value().grain, integer.value().value))) - } - ); - b.rule_3(" et ", - duration_check!(|duration: &DurationValue| !duration.suffixed), - b.reg(r#"et"#)?, - duration_check!(|duration: &DurationValue| !duration.prefixed), - |a, _, b| Ok(a.value() + b.value()) - ); - b.rule_2(" ", - duration_check!(|duration: &DurationValue| !duration.suffixed), - duration_check!(|duration: &DurationValue| !duration.prefixed), - |a, b| Ok(a.value() + b.value()) - ); - b.rule_2(" ", - duration_check!(|duration: &DurationValue| !duration.prefixed), - integer_check_by_range!(0), - |duration, integer| helpers::compose_duration_with_integer(duration.value(), integer.value()) - ); - b.rule_2("dans ", - b.reg(r#"dans"#)?, - duration_check!(), - |_, duration| duration.value().in_present() - ); - b.rule_2(" plus tard", - duration_check!(), - b.reg(r"plus tard")?, - |duration, _| duration.value().in_present() - ); - b.rule_2("environ ", - b.reg(r#"environ"#)?, - duration_check!(), - |_, duration| Ok(duration.value().clone().precision(Precision::Approximate)) - ); - // Ambiguous w/ time-of-day w/ "pour" - // Duration has less priority than Datetime types, therefore duration will be output only - // if the output kind filter is set for Duration - b.rule_2("pendant ", - b.reg(r#"pendant|durant|pour(?: une dur[eé]e? d['e])?"#)?, - duration_check!(), - |_, duration| Ok(duration.value().clone().prefixed()) - ); - Ok(()) -} - -pub fn rules_cycle(b: &mut RuleSetBuilder) -> RustlingResult<()> { - b.rule_1_terminal("seconde (cycle)", - b.reg(r#"secondes?"#)?, - |_| CycleValue::new(Grain::Second) - ); - b.rule_1_terminal("minute (cycle)", - b.reg(r#"minutes?"#)?, - |_| CycleValue::new(Grain::Minute) - ); - b.rule_1_terminal("heure (cycle)", - b.reg(r#"heures?"#)?, - |_| CycleValue::new(Grain::Hour) - ); - b.rule_1_terminal("jour (cycle)", - b.reg(r#"jour(?:n[ée]e?)?s?"#)?, - |_| CycleValue::new(Grain::Day) - ); - b.rule_1_terminal("semaine (cycle)", - b.reg(r#"semaines?"#)?, - |_| CycleValue::new(Grain::Week) - ); - b.rule_1("mois (cycle)", - b.reg(r#"mois"#)?, - |_| CycleValue::new(Grain::Month) - ); - b.rule_1_terminal("trimestre (cycle)", - b.reg(r#"trimestre"#)?, - |_| CycleValue::new(Grain::Quarter) - ); - b.rule_1("année (cycle)", - b.reg(r#"an(?:n[ée]e?)?s?"#)?, - |_| CycleValue::new(Grain::Year) - ); - // Cycle patterns relative to now - b.rule_2("ce|dans le ", - b.reg(r#"(?:(?:dans )?l[ea' ]|cet?(?:te)?)"#)?, - cycle_check!(), - |_, cycle| helpers::cycle_nth(cycle.value().grain, 0) - ); - b.rule_3("ce (la ou ci)", - b.reg(r#"cet?t?e?s?"#)?, - cycle_check!(), - b.reg(r#"-?ci"#)?, - |_, cycle, _| helpers::cycle_nth(cycle.value().grain, 0) - ); - b.rule_2(" dernier", - cycle_check!(), - b.reg(r#"derni[èe]re?|pass[ée]e?|pr[eé]c[eé]dente?|(?:d')? ?avant"#)?, - |cycle, _| helpers::cycle_nth(cycle.value().grain, -1) - ); - b.rule_3("le dernier", - b.reg(r#"l[ae']? ?"#)?, - cycle_check!(), - b.reg(r#"derni[èe]re?|pass[ée]e?"#)?, - |_, cycle, _| helpers::cycle_nth(cycle.value().grain, -1) - ); - b.rule_3("n derniers ", - integer_check_by_range!(2, 9999), - b.reg(r#"derni.re?s?"#)?, - cycle_check!(), - |integer, _, cycle| { - let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, -1 * integer.value().value)?; - // All grains except Day will trigger the right datetime_kind - if cycle.value().grain == Grain::Day { - res = res.datetime_kind(DatetimeKind::DatePeriod); - } - Ok(res) - } - ); - b.rule_4("(pendant/durant/dans) les n derniers ", - b.reg(r#"(?:pendant |durant |dans )?[cld]es"#)?, - integer_check_by_range!(2, 9999), - b.reg(r#"derni.re?s?"#)?, - cycle_check!(), - |_, integer, _, cycle| { - let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, -1 * integer.value().value)?; - // All grains except Day will trigger the right datetime_kind - if cycle.value().grain == Grain::Day { - res = res.datetime_kind(DatetimeKind::DatePeriod); - } - Ok(res) - } - ); - b.rule_3("n passes|precedents", - integer_check_by_range!(2, 9999), - cycle_check!(), - b.reg(r#"pass[eèé][eèé]?s?|pr[eé]c[eé]dente?s?|(?:d')? ?avant|plus t[oô]t"#)?, - |integer, cycle, _| { - let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, -1 * integer.value().value)?; - // All grains except Day will trigger the right datetime_kind - if cycle.value().grain == Grain::Day { - res = res.datetime_kind(DatetimeKind::DatePeriod); - } - Ok(res) - } - ); - b.rule_4("(pendant/durant/dans) les n passes|precedents", - b.reg(r#"(?:pendant |durant |dans )?[cld]es"#)?, - integer_check_by_range!(2, 9999), - cycle_check!(), - b.reg(r#"pass[eèé][eèé]?s?|pr[eé]c[eé]dente?s?|(?:d')? ?avant|plus t[oô]t"#)?, - |_, integer, cycle, _| helpers::cycle_n_not_immediate(cycle.value().grain, -1 * integer.value().value) - ); - // Incorrect resolution if some follows the expression, - // e.g. "suivant le " (unsupported) - b.rule_2(" prochain|suivant|d'après", - cycle_check!(), - b.reg(r#"prochaine?|suivante?|qui suit|(?:d')? ?apr[eèé]s"#)?, - |cycle, _| helpers::cycle_nth(cycle.value().grain, 1) - ); - b.rule_3("le prochain|suivant|d'après", - b.reg(r#"l[ae']? ?|une? ?"#)?, - cycle_check!(), - b.reg(r#"prochaine?|suivante?|qui suit|(?:d'? ?)?apr[eèé]s"#)?, - |_, cycle, _| helpers::cycle_nth(cycle.value().grain, 1) - ); - b.rule_3("n prochains ", - integer_check_by_range!(2, 9999), - b.reg(r#"prochaine?s?|suivante?s?|apr[eèé]s"#)?, - cycle_check!(), - |integer, _, cycle| { - let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, integer.value().value)?; - // All grains except Day will trigger the right datetime_kind - if cycle.value().grain == Grain::Day { - res = res.datetime_kind(DatetimeKind::DatePeriod); - } - Ok(res) - } - ); - b.rule_4("(pendant/durant/dans) les n prochains ", - b.reg(r#"(?:pendant |durant |dans )?[cld]es"#)?, - integer_check_by_range!(2, 9999), - b.reg(r#"prochaine?s?|suivante?s?|apr[eèé]s"#)?, - cycle_check!(), - |_, integer, _, cycle| { - let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, integer.value().value)?; - // All grains except Day will trigger the right datetime_kind - if cycle.value().grain == Grain::Day { - res = res.datetime_kind(DatetimeKind::DatePeriod); - } - Ok(res) - } - ); - b.rule_3("n suivants", - integer_check_by_range!(2, 9999), - cycle_check!(), - b.reg(r#"prochaine?s?|suivante?s?|apr[eèé]s|qui sui(?:t|ves?)|plus tard"#)?, - |integer, cycle, _| { - let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, integer.value().value)?; - // All grains except Day will trigger the right datetime_kind - if cycle.value().grain == Grain::Day { - res = res.datetime_kind(DatetimeKind::DatePeriod); - } - Ok(res) - } - ); - b.rule_4("(pendant/durant/dans) les n suivants", - b.reg(r#"(?:pendant |durant |dans )?[cld]es"#)?, - integer_check_by_range!(2, 9999), - cycle_check!(), - b.reg(r#"prochaine?s?|suivante?s?|apr[eèé]s|qui sui(?:t|ves?)|plus tard"#)?, - |_, integer, cycle, _| { - let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, integer.value().value)?; - // All grains except Day will trigger the right datetime_kind - if cycle.value().grain == Grain::Day { - res = res.datetime_kind(DatetimeKind::DatePeriod); - } - Ok(res) - } - ); - b.rule_3("n avant", - integer_check_by_range!(2, 9999), - cycle_check!(), - b.reg(r#"(?:d')? ?avant|plus t[oô]t"#)?, - |integer, cycle, _| { - let mut res = helpers::cycle_nth(cycle.value().grain, -1 * integer.value().value)?; - // All grains except Day will trigger the right datetime_kind - if cycle.value().grain == Grain::Day { - res = res.datetime_kind(DatetimeKind::DatePeriod); - } - Ok(res) - } - ); - b.rule_3("n après", - integer_check_by_range!(2, 9999), - cycle_check!(), - b.reg(r#"(?:d')? ?apr[eèé]s|qui sui(?:t|ves?)|plus tard"#)?, - |integer, cycle, _| { - let mut res = helpers::cycle_nth(cycle.value().grain, integer.value().value)?; - // All grains except Day will trigger the right datetime_kind - if cycle.value().grain == Grain::Day { - res = res.datetime_kind(DatetimeKind::DatePeriod); - } - Ok(res) - } - ); - // Cycle patterns relative to another datetime - b.rule_4("le après|suivant ", - b.reg(r#"l[ea']? ?"#)?, - cycle_check!(), - b.reg(r#"suivante?|apr[eèé]s"#)?, - datetime_check!(), - |_, cycle, _, datetime| helpers::cycle_nth_after(cycle.value().grain, 1, datetime.value()) - ); - b.rule_4("le avant|précédent ", - b.reg(r#"l[ea']? ?"#)?, - cycle_check!(), - b.reg(r#"avant|pr[ée]c[ée]dent"#)?, - datetime_check!(), - |_, cycle, _, datetime| helpers::cycle_nth_after(cycle.value().grain, -1, datetime.value()) - ); - b.rule_4(" de ", - ordinal_check!(), - cycle_check!(), - b.reg(r#"d['eu]|en"#)?, - datetime_check!(), - |ordinal, cycle, _, datetime| helpers::cycle_nth_after_not_immediate(cycle.value().grain, ordinal.value().value - 1, datetime.value()) - ); - b.rule_5("le de ", - b.reg(r#"l[ea]"#)?, - ordinal_check!(), - cycle_check!(), - b.reg(r#"d['eu]|en"#)?, - datetime_check!(), - |_, ordinal, cycle, _, datetime| helpers::cycle_nth_after_not_immediate(cycle.value().grain, ordinal.value().value - 1, datetime.value()) - ); - b.rule_4("le de ", - b.reg(r#"l[ea]"#)?, - cycle_check!(), - b.reg(r#"d['eu]|en"#)?, - datetime_check!(), - |_, cycle, _, datetime| helpers::cycle_nth_after_not_immediate(cycle.value().grain, 0, datetime.value()) - ); - b.rule_2("le lendemain du ", - b.reg(r#"(?:le|au)? ?lendemain du"#)?, - datetime_check!(), - |_, datetime| helpers::cycle_nth_after_not_immediate(Grain::Day, 1, datetime.value()) - ); - b.rule_2("la veille du ", - b.reg(r#"(la )?veille du"#)?, - datetime_check!(), - |_, datetime| helpers::cycle_nth_after_not_immediate(Grain::Day, -1, datetime.value()) - ); - Ok(()) -} - -pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { - b.rule_2("intersect", - datetime_check!(|datetime: &DatetimeValue| !datetime.latent), - datetime_check!(|datetime: &DatetimeValue| !datetime.latent), - |a, b| a.value().intersect(b.value()) - ); - b.rule_3("intersect by 'de' or ','", - datetime_check!(|datetime: &DatetimeValue| !datetime.latent), - b.reg(r#"de|,"#)?, - datetime_check!(|datetime: &DatetimeValue| !datetime.latent), - |a, _, b| a.value().intersect(b.value()) - ); - b.rule_3("intersect by 'mais/par exemple/plutôt'", - datetime_check!(|datetime: &DatetimeValue| !datetime.latent), - b.reg(r#"mais|par exemple|plutôt|plutot"#)?, - datetime_check!(|datetime: &DatetimeValue| !datetime.latent), - |a, _, b| a.value().intersect(b.value()) - ); - b.rule_2("en ", - b.reg(r#"en|au mois d[e']|du mois d[e']"#)?, - datetime_check!(form!(Form::Month(_))), - |_, a| Ok(a.value().clone()) - ); - b.rule_2("pour ", - b.reg(r#"pour"#)?, - datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), - |_, a| Ok(a.value().clone()) - ); - b.rule_1_terminal("named-day", - b.reg(r#"lun\.?(?:di)?"#)?, - |_| helpers::day_of_week(Weekday::Mon) - ); - b.rule_1_terminal("named-day", - b.reg(r#"mar\.?(?:di)?"#)?, - |_| helpers::day_of_week(Weekday::Tue) - ); - b.rule_1_terminal("named-day", - b.reg(r#"mer\.?(?:credi)?"#)?, - |_| helpers::day_of_week(Weekday::Wed) - ); - b.rule_1_terminal("named-day", - b.reg(r#"jeu\.?(?:di)?"#)?, - |_| helpers::day_of_week(Weekday::Thu) - ); - b.rule_1_terminal("named-day", - b.reg(r#"ven\.?(?:dredi)?"#)?, - |_| helpers::day_of_week(Weekday::Fri) - ); - b.rule_1_terminal("named-day", - b.reg(r#"sam\.?(?:edi)?"#)?, - |_| helpers::day_of_week(Weekday::Sat) - ); - b.rule_1_terminal("named-day", - b.reg(r#"dim\.?(?:anche)?"#)?, - |_| helpers::day_of_week(Weekday::Sun) - ); - b.rule_1_terminal("named-month", - b.reg(r#"janvier|janv\.?"#)?, - |_| helpers::month(1) - ); - b.rule_1_terminal("named-month", - b.reg(r#"fevrier|février|fev|fév\.?"#)?, - |_| helpers::month(2) - ); - b.rule_1_terminal("named-month", - b.reg(r#"mars|mar\.?"#)?, - |_| helpers::month(3) - ); - b.rule_1_terminal("named-month", - b.reg(r#"avril|avr\.?"#)?, - |_| helpers::month(4) - ); - b.rule_1_terminal("named-month", - b.reg(r#"mai"#)?, - |_| helpers::month(5) - ); - b.rule_1_terminal("named-month", - b.reg(r#"juin|jun\.?"#)?, - |_| helpers::month(6) - ); - b.rule_1_terminal("named-month", - b.reg(r#"juillet|juil?\."#)?, - |_| helpers::month(7) - ); - b.rule_1_terminal("named-month", - b.reg(r#"aout|août|aou\.?"#)?, - |_| helpers::month(8) - ); - b.rule_1_terminal("named-month", - b.reg(r#"septembre|sept\.|sep\.?"#)?, - |_| helpers::month(9) - ); - b.rule_1_terminal("named-month", - b.reg(r#"octobre|oct\.?"#)?, - |_| helpers::month(10) - ); - b.rule_1_terminal("named-month", - b.reg(r#"novembre|nov\.?"#)?, - |_| helpers::month(11) - ); - b.rule_1_terminal("named-month", - b.reg(r#"décembre|decembre|déc\.?|dec\.?"#)?, - |_| helpers::month(12) - ); - b.rule_1_terminal("noel", - b.reg(r#"(?:(?:le )?jour de )?no[eë]l"#)?, - |_| Ok(helpers::month_day(12, 25)?.form(Form::Celebration)) - ); - b.rule_1_terminal("soir de noël", - b.reg(r#"(?:l[ea] )?(?:soir(?:ée)?|veille|r[eé]veillon) de no[eë]l"#)?, - |_| { - let start = helpers::month_day(12, 24)?.intersect(&helpers::hour(18, false)?)?; - let end = helpers::month_day(12, 25)?.intersect(&helpers::hour(0, false)?)?; - Ok(start.span_to(&end, false)? - .form(Form::Celebration)) - } - ); - b.rule_1_terminal("saint sylvestre", - b.reg(r#"(?:l[ea] )?(?:saint[- ]sylvestre|r[eé]veillon)"#)?, - |_| Ok(helpers::month_day(12, 31)?.form(Form::Celebration)) - ); - b.rule_1_terminal("jour de l'an", - b.reg(r#"(?:le )?(?:jour de l'|nouvel )an"#)?, - |_| Ok(helpers::month_day(1, 1)?.form(Form::Celebration)) - ); - b.rule_1_terminal("toussaint", - b.reg(r#"(?:(?:la |la journée de la |jour de la )?toussaint|jour des morts)"#)?, - |_| Ok(helpers::month_day(11, 1)?.form(Form::Celebration)) - ); - b.rule_1_terminal("Armistice", - b.reg(r#"(?:pour )?l'armistice"#)?, - |_| Ok(helpers::month_day(11, 11)?.form(Form::Celebration)) - ); - b.rule_1_terminal("Saint Etienne (Alsace)", - b.reg(r#"(?:(?:le jour|la f[eê]te) de )?la (?:saint|st) [eé]tienne"#)?, - |_| Ok(helpers::month_day(12, 26)?.form(Form::Celebration)) - ); - b.rule_1_terminal("jeudi saint", - b.reg(r#"(?:le )?jeudi saint"#)?, - |_| Ok(helpers::cycle_nth_after(Grain::Day, -3, &helpers::easter()?)? - .form(Form::Celebration)) - ); - b.rule_1_terminal("vendredi saint", - b.reg(r#"(?:le )?vendredi saint"#)?, - |_| Ok(helpers::cycle_nth_after(Grain::Day, -2, &helpers::easter()?)? - .form(Form::Celebration)) - ); - b.rule_1_terminal("samedi saint", - b.reg(r#"(?:le )?samedi saint"#)?, - |_| Ok(helpers::cycle_nth_after(Grain::Day, -1, &helpers::easter()?)? - .form(Form::Celebration)) - ); - b.rule_1_terminal("pâques", - b.reg(r#"(?:la f[eê]te de |le jour de |le dimanche de )?p[âa]ques"#)?, - |_| Ok(helpers::easter()?.form(Form::Celebration)) - ); - b.rule_1_terminal("le lundi de pâques", - b.reg(r#"le lundi de p[âa]ques"#)?, - |_| Ok(helpers::cycle_nth_after(Grain::Day, 1, &helpers::easter()?)? - .form(Form::Celebration)) - ); - b.rule_1_terminal("ascension", - b.reg(r#"(?:la f[eê]te de l'|le jeudi de l'|l'|le jour de l')ascension"#)?, - |_| Ok(helpers::cycle_nth_after(Grain::Day, 39, &helpers::easter()?)? - .form(Form::Celebration)) - - ); - b.rule_1_terminal("pentecôte", - b.reg(r#"(?:la f[eê]te de |(?:le )?lundi de )?(?:la )?pentec[oô]te"#)?, - |_| Ok(helpers::cycle_nth_after(Grain::Day, 49, &helpers::easter()?)? - .form(Form::Celebration)) - ); - b.rule_1_terminal("1er mai", - b.reg(r#"(?:la )?f(e|ê)te du travail"#)?, - |_| Ok(helpers::month_day(5, 1)?.form(Form::Celebration)) - ); - b.rule_1_terminal("fêtes des pères", - b.reg(r#"(?:la )?f[eê]te des p[eè]res"#)?, - |_| { - let sundays_of_june = helpers::month(6)?.intersect(&helpers::day_of_week(Weekday::Sun)?)?; - let second_week_of_june = helpers::cycle_nth_after(Grain::Week, 2, &helpers::month_day(6, 1)?)?; - Ok(sundays_of_june.intersect(&second_week_of_june)? // third sunday of June - .form(Form::Celebration)) - } - ); - b.rule_1_terminal("fêtes des mères", - b.reg(r#"(?:la )?f[eê]te des m[eè]res"#)?, - |_| { - // It is the last last sunday of may - // If it is the same day as the Pentecost, it is the first sunday of june - // This case is not supported for now - Ok(helpers::day_of_week(Weekday::Sun)?.last_of(&helpers::month(5)?)? - .form(Form::Celebration)) - } - ); - b.rule_1_terminal("fête nationale", - b.reg(r#"(?:la )?f[eê]te (?:nationale|du (?:14|quatorze) juillet)"#)?, - |_| Ok(helpers::month_day(7, 14)? - .form(Form::Celebration)) - ); - b.rule_1_terminal("assomption", - b.reg(r#"(?:la f[eê]te de |le jour de )?l'assomption"#)?, - |_| Ok(helpers::month_day(8, 15)? - .form(Form::Celebration)) - ); - b.rule_2("à ", - b.reg(r#"au|[aà](?:l['a])?"#)?, - datetime_check!(form!(Form::Celebration)), - |_, a| Ok(a.value().clone()) - ); - b.rule_1_terminal("maintenant", - b.reg(r#"maintenant|tout de suite|en ce moment"#)?, - |_| helpers::cycle_nth(Grain::Second, 0) - ); - b.rule_1_terminal("aujourd'hui", - b.reg(r#"(?:aujourd'? ?hui)|(?:ce jour)|(?:dans la journ[ée]e?)"#)?, - |_| helpers::cycle_nth(Grain::Day, 0) - ); - // FIXME: "le lendemain" interpreted as demain, not as relative to another date - // but there is a rule "le lendemain du " - inconsistent - b.rule_1_terminal("demain", - b.reg(r#"(?:demain)|(?:le lendemain)"#)?, - |_| helpers::cycle_nth(Grain::Day, 1) - ); - b.rule_1_terminal("hier", - b.reg(r#"hier|la veille"#)?, - |_| helpers::cycle_nth(Grain::Day, -1) - ); - b.rule_1_terminal("fin du mois", - b.reg(r#"(?:(?:[aà] )?la )?fin (?:du|de) mois"#)?, - |_| { - let month = helpers::cycle_nth(Grain::Month, 1)?; - Ok(helpers::cycle_nth_after(Grain::Day, -10, &month)? - .span_to(&month, false)? - .latent() - .form(Form::PartOfMonth)) - } - ); - b.rule_1_terminal("après-demain", - b.reg(r#"apr(?:e|è)s[- ]?demain"#)?, - |_| helpers::cycle_nth(Grain::Day, 2) - ); - b.rule_1_terminal("avant-hier", - b.reg(r#"avant[- ]?hier"#)?, - |_| helpers::cycle_nth(Grain::Day, -2) - ); - b.rule_2("ce ", - b.reg(r#"ce"#)?, - datetime_check!(form!(Form::DayOfWeek{..})), - |_, datetime| datetime.value().the_nth_not_immediate(0) - ); - b.rule_2("ce ", - b.reg(r#"ce"#)?, - datetime_check!(), - |_, datetime| Ok(datetime.value().the_nth(0)? - .datetime_kind(datetime.value().datetime_kind.clone())) - ); - b.rule_2(" prochain", - datetime_check!(form!(Form::DayOfWeek{..})), - b.reg(r#"prochain"#)?, - |datetime, _| datetime.value().the_nth_not_immediate(0) - ); - b.rule_2(" prochain", - datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), - b.reg(r#"prochaine?"#)?, - |datetime, _| datetime.value().the_nth_not_immediate(0) - ); - b.rule_2("au prochain ", - b.reg(r#"(au|[aà] la) prochaine?"#)?, - datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), - |_, datetime| datetime.value().the_nth_not_immediate(0) - ); - b.rule_2(" prochain", - // The direction check is to avoid application of datetime_check(month) on rule result - // "avant " - datetime_check!(|datetime: &DatetimeValue| form!(Form::Month(_))(datetime) && !datetime.direction.is_some()), - b.reg(r#"prochain"#)?, - |datetime, _| datetime.value().the_nth_not_immediate(0) - ); - b.rule_2(" suivant|d'après", - datetime_check!(), - b.reg(r#"suivante?s?|d'apr[eéè]s"#)?, - |datetime, _| datetime.value().the_nth(1) - ); - b.rule_2(" dernier|passé", - datetime_check!(), - b.reg(r#"derni[eéè]re?|pass[ée]e?"#)?, - |datetime, _| datetime.value().the_nth(-1) - ); - b.rule_2(" en huit", - datetime_check!(form!(Form::DayOfWeek{..})), - b.reg(r#"en (?:huit|8)"#)?, - |datetime, _| datetime.value().the_nth(1) - ); - b.rule_2(" en quinze", - datetime_check!(form!(Form::DayOfWeek{..})), - b.reg(r#"en (quinze|15)"#)?, - |datetime, _| datetime.value().the_nth(2) - ); - b.rule_4("dernier de (latent)", - b.reg(r#"derni[eéè]re?"#)?, - datetime_check!(form!(Form::DayOfWeek{..})), - b.reg(r#"d['e]"#)?, - datetime_check!(), - |_, dow, _, datetime| dow.value().last_of(datetime.value()) - ); - b.rule_4("dernier de (latent)", - b.reg(r#"derni[eéè]re?"#)?, - cycle_check!(), - b.reg(r#"d['e]"#)?, - datetime_check!(), - |_, cycle, _, datetime| cycle.value().last_of(datetime.value()) - ); - b.rule_4(" de ", - ordinal_check!(), // the first - datetime_check!(), // Thursday - b.reg(r#"d[e']"#)?, // of - datetime_check!(), // march - |ordinal, a, _, b| { - b.value().intersect(a.value())?.the_nth(ordinal.value().value - 1) - } - ); - b.rule_3(" week-end de ", - ordinal_check!(), - b.reg(r#"week(?:\s|-)?end (?:d['eu]|en|du mois de)"#)?, - datetime_check!(form!(Form::Month(_))), - |ordinal, _, datetime| { - let weekend = helpers::weekend()?; - let nth_week_end = datetime.value().intersect(&weekend)?; - nth_week_end.the_nth(ordinal.value().value - 1) - } - ); - b.rule_2("dernier week-end de ", - b.reg(r#"(?:le )?dernier week(?:\s|-)?end (?:du mois d[e']|d['eu]|en)"#)?, - datetime_check!(form!(Form::Month(_))), - |_, datetime| { - let weekend = helpers::weekend()?; - weekend.last_of(datetime.value()) - } - ); - // FIXME: change latency ranges for years? E.g. latent until 1900? - b.rule_1("year", - integer_check_by_range!(1000, 2100), - |integer| helpers::year(integer.value().value as i32) - ); - b.rule_1("year (latent)", - integer_check_by_range!(-1000, 999), - |integer| Ok(helpers::year(integer.value().value as i32)?.latent()) - ); - b.rule_2("l'année ", - b.reg(r#"l[' ]an(?:n[eé]+)?"#)?, - integer_check!(), - |_, integer| helpers::year(integer.value().value as i32) - ); - b.rule_2("en ", - b.reg(r#"en"#)?, - datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::Year(_))(datetime)), - |_, year| Ok(year.value().clone()) - ); - b.rule_1("year (latent)", - integer_check_by_range!(2101, 3000), - |integer| Ok(helpers::year(integer.value().value as i32)?.latent()) - ); - b.rule_1_terminal("day of month (premier)", - b.reg(r#"premier|prem\.?|1er|1 er"#)?, - |_| helpers::day_of_month(1) - ); - b.rule_2("le (non ordinal)", - b.reg(r#"le"#)?, - integer_check_by_range!(1, 31), - |_, integer| helpers::day_of_month(integer.value().value as u32) - ); - b.rule_4("le à ", - b.reg(r#"le"#)?, - integer_check_by_range!(1, 31), - b.reg(r#"[aà]"#)?, - datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(_))(datetime)), - |_, integer, _, datetime| { - let day_of_month = helpers::day_of_month(integer.value().value as u32)?; - day_of_month.intersect(&datetime.value()) - } - ); - b.rule_2(" ", - integer_check_by_range!(1, 31), - datetime_check!(form!(Form::Month(_))), - |integer, month| Ok(month.value() - .intersect(&helpers::day_of_month(integer.value().value as u32)?)? - .form(Form::DayOfMonth)) - ); - b.rule_2(" ", - datetime_check!(form!(Form::DayOfWeek{..})), // Weird it is not used in the production of the rule - integer_check_by_range!(1, 31), - |_, integer| helpers::day_of_month(integer.value().value as u32) - ); - b.rule_4(" à )", - datetime_check!(form!(Form::DayOfWeek{..})), // Weird it is not used in the production of the rule - integer_check_by_range!(1, 31), - b.reg(r#"[aà]"#)?, - datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(_))(datetime)), - |_, integer, _, tod| helpers::day_of_month(integer.value().value as u32) - ?.intersect(tod.value()) - ); - b.rule_1(" (latent)", - integer_check_by_range!(1, 23), - |integer| Ok(helpers::hour(integer.value().value as u32, integer.value().value < 12)?.latent()) - ); - b.rule_1(" (latent)", - integer_check_by_range!(0, 0), - |_| Ok(helpers::hour(0, false)?.latent()) - ); - b.rule_1_terminal("midi", - b.reg(r#"midi(?: pile| exactement| pr[eé]cises)?"#)?, - |_| helpers::hour(12, false) - ); - b.rule_1_terminal("minuit", - b.reg(r#"minuit(?: pile| exactement| pr[eé]cises)?"#)?, - |_| helpers::hour(0, false) - ); - b.rule_2(" heures", - datetime_check!(form!(Form::TimeOfDay(TimeOfDayForm::Hour { .. }))), - b.reg(r#"h\.?(?:eure)?s?(?: pile| exactement| pr[eé]cises?)?"#)?, - |a, _| Ok(a.value().clone().not_latent()) - ); - b.rule_1_terminal("quart (relative minutes)", - b.reg(r#"(?:un )?quart"#)?, - |_| Ok(RelativeMinuteValue(15)) - ); - b.rule_1_terminal("demi (relative minutes)", - b.reg(r#"demie?"#)?, - |_| Ok(RelativeMinuteValue(30)) - ); - b.rule_1_terminal("trois quarts (relative minutes)", - b.reg(r#"(?:3|trois) quarts?"#)?, - |_| Ok(RelativeMinuteValue(45)) - ); - b.rule_1("number (as relative minutes)", - integer_check_by_range!(1, 59), - |a| Ok(RelativeMinuteValue(a.value().value as i32)) - ); - b.rule_2("number minutes (as relative minutes)", - integer_check_by_range!(1, 59), - b.reg(r#"min\.?(?:ute)?s?"#)?, - |a, _| Ok(RelativeMinuteValue(a.value().value as i32)) - ); - b.rule_2(" (as relative minutes)", - datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(TimeOfDayForm::Hour { .. }))(datetime)), - relative_minute_check!(), - |datetime, minutes| helpers::hour_relative_minute( - datetime.value().form_time_of_day()?.full_hour(), - minutes.value().0, - datetime.value().form_time_of_day()?.is_12_clock() - ) - ); - b.rule_3(" (as relative minutes) exactly", - datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(TimeOfDayForm::Hour { .. }))(datetime)), - relative_minute_check!(), - b.reg(r#"pile|exactement|pr[ée]cises?"#)?, - |datetime, minutes, _| helpers::hour_relative_minute( - datetime.value().form_time_of_day()?.full_hour(), - minutes.value().0, - datetime.value().form_time_of_day()?.is_12_clock() - ) - ); - b.rule_3(" moins (as relative minutes)", - datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(TimeOfDayForm::Hour { .. }))(datetime)), - b.reg(r#"moins(?: le)?"#)?, - relative_minute_check!(), - |datetime, _, minutes| helpers::hour_relative_minute( - datetime.value().form_time_of_day()?.full_hour(), - -1 * minutes.value().0, - datetime.value().form_time_of_day()?.is_12_clock() - ) - ); - b.rule_4(" moins (as relative minutes) exactly ", - datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(TimeOfDayForm::Hour { .. }))(datetime)), - b.reg(r#"moins(?: le)?"#)?, - relative_minute_check!(), - b.reg(r#"pile|exactement|pr[ée]cises?"#)?, - |datetime, _, minutes, _| helpers::hour_relative_minute( - datetime.value().form_time_of_day()?.full_hour(), - -1 * minutes.value().0, - datetime.value().form_time_of_day()?.is_12_clock() - ) - ); - b.rule_3(" et|passé de ", - datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(TimeOfDayForm::Hour { .. }))(datetime)), - b.reg(r#"et|pass[ée]e?s? de"#)?, - relative_minute_check!(), - |datetime, _, minutes| helpers::hour_relative_minute( - datetime.value().form_time_of_day()?.full_hour(), - minutes.value().0, - datetime.value().form_time_of_day()?.is_12_clock() - ) - ); - b.rule_4(" et|passé de exactly", - datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(TimeOfDayForm::Hour { .. }))(datetime)), - b.reg(r#"et|pass[ée]e?s? de"#)?, - relative_minute_check!(), - b.reg(r#"pile|exactement|pr[ée]cises?"#)?, - |datetime, _, minutes, _| helpers::hour_relative_minute( - datetime.value().form_time_of_day()?.full_hour(), - minutes.value().0, - datetime.value().form_time_of_day()?.is_12_clock() - ) - ); - b.rule_4(" de exactly", - datetime_check!(form!(Form::TimeOfDay(_))), - b.reg(r#"(?:d[eu] |dans )(?:l[ 'a])?"#)?, - datetime_check!(form!(Form::PartOfDay(_))), - b.reg(r#"pile|exactement|pr[eé]cises?"#)?, - |a, _, b, _| Ok(a.value().intersect(b.value())?.form(a.value().form.clone())) - ); - // Adding "pour" here makes time-of-day ambiguous w/ Duration - // Duration has less priority than Datetime types, therefore duration will be output only - // if the output kind filter is set for Duration - b.rule_2("à ", - b.reg(r#"[aà]|pour"#)?, - datetime_check!(form!(Form::TimeOfDay(_))), - |_, a| Ok(a.value().clone().not_latent()) - ); - b.rule_2("vers ", - b.reg(r#"(?:plut[ôo]t )?(?:vers|autour de|[aà] environ|aux alentours de)"#)?, - datetime_check!(form!(Form::TimeOfDay(_))), - |_, a| Ok(a.value().clone().not_latent().precision(Precision::Approximate)) - ); - // Written time/date in numeric formats - b.rule_1_terminal("hh(:|h)mm (time-of-day)", - b.reg(r#"((?:[01]?\d)|(?:2[0-3]))[:h]([0-5]\d)"#)?, - |text_match| { - let hour: u32 = text_match.group(1).parse()?; - let minute: u32 = text_match.group(2).parse()?; - helpers::hour_minute(hour, minute, hour < 12) - } - ); - b.rule_3_terminal("hh(:|h)mm - hh(:|h)mm (time-of-day interval)", - b.reg(r#"((?:[01]?\d)|(?:2[0-3]))[:h]([0-5]\d)"#)?, - b.reg(r#" ?\- ?"#)?, - b.reg(r#"((?:[01]?\d)|(?:2[0-3]))[:h]([0-5]\d)"#)?, - |a, _, b| { - let hour_start: u32 = a.group(1).parse()?; - let minute_start: u32 = a.group(2).parse()?; - let hour_end: u32 = b.group(1).parse()?; - let minute_end: u32 = b.group(2).parse()?; - let start = helpers::hour_minute(hour_start, minute_start, hour_start < 12)?; - let end = helpers::hour_minute(hour_end, minute_end, hour_end < 12)?; - start.smart_span_to(&end, false) - } - ); - b.rule_1_terminal("hh:mm:ss", - b.reg(r#"((?:[01]?\d)|(?:2[0-3]))[:.]([0-5]\d)[:.]([0-5]\d)"#)?, - |text_match| helpers::hour_minute_second( - text_match.group(1).parse()?, - text_match.group(2).parse()?, - text_match.group(3).parse()?, - false - ) - - ); - b.rule_1_terminal("hhmm (military time-of-day)", - b.reg(r#"((?:[01]\d)|(?:2[0-3]))([0-5]\d)"#)?, - |text_match| Ok(helpers::hour_minute( - text_match.group(1).parse()?, - text_match.group(2).parse()?, - false - )?.latent()) - ); - b.rule_1_terminal("yyyy-mm-dd - ISO", - b.reg(r#"(\d{4})[-/](0?[1-9]|1[0-2])[-/](3[01]|[12]\d|0?[1-9])"#)?, - |text_match| helpers::year_month_day( - text_match.group(1).parse()?, - text_match.group(2).parse()?, - text_match.group(3).parse()?) - ); - // Supporting these date formats also with whitespace as a separator for legacy - // But this seems too permissive? - b.rule_1_terminal("dd/mm/yy or dd/mm/yyyy", - b.reg(r#"(0?[1-9]|[12]\d|3[01])[-\./ ](0?[1-9]|1[0-2])[-\./ ](\d{2,4})"#)?, - |text_match| helpers::year_month_day( - text_match.group(3).parse()?, - text_match.group(2).parse()?, - text_match.group(1).parse()?, - ) - ); - b.rule_1_terminal("dd/mm", - b.reg(r#"(0?[1-9]|[12]\d|3[01])[\./ ](1[0-2]|0?[1-9])"#)?, - |text_match| helpers::month_day( - text_match.group(2).parse()?, - text_match.group(1).parse()?) - ); - // End of Written time/date in numeric formats - b.rule_1_terminal("matin", - b.reg(r#"mat(?:in[ée]?e?)?"#)?, - |_| Ok(helpers::hour(4, false)? - .span_to(&helpers::hour(12, false)?, false)? - .latent() - .form(Form::PartOfDay(PartOfDayForm::Morning))) - ); - b.rule_1_terminal("début de matinée", - b.reg(r#"(?:le matin (?:tr[eè]s )?t[ôo]t|(?:tr[eè]s )?t[ôo]t le matin|d[ée]but de matin[ée]e)"#)?, - |_| Ok(helpers::hour(4, false)? - .span_to(&helpers::hour(9, false)?, false)? - .latent() - .form(Form::PartOfDay(PartOfDayForm::Morning))) - ); - b.rule_1_terminal("petit dejeuner", - b.reg(r#"petit[- ]d[ée]jeuner"#)?, - |_| Ok(helpers::hour(5, false)? - .span_to(&helpers::hour(10, false)?, false)? - .latent() - .form(Form::Meal)) - ); - b.rule_1_terminal("milieu de matinée", - b.reg(r#"milieu de matin[ée]e"#)?, - |_| Ok(helpers::hour(9, false)? - .span_to(&helpers::hour(11, false)?, false)? - .latent() - .form(Form::PartOfDay(PartOfDayForm::Morning))) - ); - b.rule_1_terminal("brunch", - b.reg(r#"brunch"#)?, - |_| Ok(helpers::hour(10, false)? - .span_to(&helpers::hour(15, false)?, false)? - .latent() - .form(Form::Meal)) - ); - b.rule_1_terminal("fin de matinée", - b.reg(r#"fin de matin[ée]e"#)?, - |_| Ok(helpers::hour(10, false)? - .span_to(&helpers::hour(12, false)?, false)? - .latent() - .form(Form::PartOfDay(PartOfDayForm::Morning))) - ); - b.rule_1_terminal("déjeuner", - b.reg(r#"d[eéè]jeuner"#)?, - |_| Ok(helpers::hour(12, false)? - .span_to(&helpers::hour(14, false)?, false)? - .latent() - .form(Form::Meal)) - ); - b.rule_1_terminal("après le déjeuner", - b.reg(r#"apr[eè]s (?:le )?d[eéè]jeuner"#)?, - |_| { - let period = helpers::hour(13, false)? - .span_to(&helpers::hour(17, false)?, false)?; - Ok(helpers::cycle_nth(Grain::Day, 0)?.intersect(&period)?.form(Form::PartOfDay(PartOfDayForm::Afternoon))) - } - ); - b.rule_1_terminal("avant le déjeuner", - b.reg(r#"avant (?:le )?d[eéè]jeuner"#)?, - |_| { - let period = helpers::hour(10, false)? - .span_to(&helpers::hour(12, false)?, false)?; - Ok(helpers::cycle_nth(Grain::Day, 0)?.intersect(&period)?.form(Form::PartOfDay(PartOfDayForm::Morning))) - } - ); - b.rule_1_terminal("avant le travail", - b.reg(r#"avant le travail"#)?, - |_| { - let period = helpers::hour(7, false)? - .span_to(&helpers::hour(10, false)?, false)?; - Ok(helpers::cycle_nth(Grain::Day, 0)?.intersect(&period)?.form(Form::PartOfDay(PartOfDayForm::Morning))) - } - ); - b.rule_1_terminal("pendant le travail", - b.reg(r#"pendant le travail"#)?, - |_| { - let period = helpers::hour(9, false)? - .span_to(&helpers::hour(19, false)?, false)?; - Ok(helpers::cycle_nth(Grain::Day, 0)?.intersect(&period)?.form(Form::PartOfDay(PartOfDayForm::None))) - } - ); - b.rule_1_terminal("après le travail", - b.reg(r#"apr[eè]s (?:le )?travail"#)?, - |_| { - let period = helpers::hour(17, false)? - .span_to(&helpers::hour(21, false)?, false)?; - Ok(helpers::cycle_nth(Grain::Day, 0)?.intersect(&period)?.form(Form::PartOfDay(PartOfDayForm::Evening))) - } - ); - b.rule_1_terminal("après-midi", - b.reg(r#"apr[eéè]s?[ \-]?midi|aprem"#)?, - |_| { - Ok(helpers::hour(12, false)? - .span_to(&helpers::hour(19, false)?, false)? - .latent() - .form(Form::PartOfDay(PartOfDayForm::Afternoon))) - } - ); - b.rule_1_terminal("début d'après-midi", - b.reg(r#"d[ée]but (?:d'|de l')(?:apr[eéè]s?[ \-]?midi|aprem)"#)?, - |_| { - Ok(helpers::hour(12, false)? - .span_to(&helpers::hour(15, false)?, false)? - .latent() - .form(Form::PartOfDay(PartOfDayForm::Afternoon))) - } - ); - b.rule_1_terminal("milieu d'après-midi", - b.reg(r#"milieu (?:d'|de l')(?:apr[eéè]s?[ \-]?midi|aprem)"#)?, - |_| { - Ok(helpers::hour(15, false)? - .span_to(&helpers::hour(17, false)?, false)? - .latent() - .form(Form::PartOfDay(PartOfDayForm::Afternoon))) - } - ); - b.rule_1_terminal("gouter", - b.reg(r#"(?:(?:[àa] )?l[' ]heure du|au moment du|pendant le|pour le) go[uû]ter"#)?, - |_| Ok(helpers::hour(16, false)? - .span_to(&helpers::hour(18, false)?, false)? - .form(Form::Meal)) - ); - b.rule_1_terminal("thé", - b.reg(r#"(?:(?:[àa] )?l[' ]heure du|au moment du|pendant le|pour le) th[eé]"#)?, - |_| Ok(helpers::hour(15, false)? - .span_to(&helpers::hour(17, false)?, false)? - .form(Form::Meal)) - ); - b.rule_1_terminal("cafe", - b.reg(r#"(?:(?:[àa] )?l[' ]heure du|au moment du|pendant le|pour le) caf[eé]"#)?, - |_| Ok(helpers::hour(14, false)? - .span_to(&helpers::hour(16, false)?, false)? - .form(Form::Meal)) - ); - b.rule_1_terminal("fin d'après-midi", - b.reg(r#"fin (?:d'|de l')(?:apr[eéè]s?[ \-]?midi|aprem)"#)?, - |_| { - Ok(helpers::hour(17, false)? - .span_to(&helpers::hour(19, false)?, false)? - .latent() - .form(Form::PartOfDay(PartOfDayForm::Afternoon))) - } - ); - // TODO: APERO - b.rule_1_terminal("début de journée", - b.reg(r#"d[ée]but de (?:la )?journ[ée]e"#)?, - |_| { - Ok(helpers::hour(6, false)? - .span_to(&helpers::hour(10, false)?, false)? - .latent() - .form(Form::PartOfDay(PartOfDayForm::Morning))) - } - ); - b.rule_1_terminal("milieu de journée", - b.reg(r#"milieu de (?:la )?journ[ée]e"#)?, - |_| { - Ok(helpers::hour(11, false)? - .span_to(&helpers::hour(16, false)?, false)? - .latent() - .form(Form::PartOfDay(PartOfDayForm::None))) - } - ); - b.rule_1_terminal("fin de journée", - b.reg(r#"fin de (?:la )?journ[ée]e"#)?, - |_| { - Ok(helpers::hour(17, false)? - .span_to(&helpers::hour(21, false)?, false)? - .latent() - .form(Form::PartOfDay(PartOfDayForm::Evening))) - } - ); - b.rule_1_terminal("soir", - b.reg(r#"soir[ée]?e?"#)?, - |_| { - Ok(helpers::hour(18, false)? - .span_to(&helpers::hour(0, false)?, false)? - .latent() - .form(Form::PartOfDay(PartOfDayForm::Evening))) - } - ); - b.rule_1_terminal("début de soirée", - b.reg(r#"d[ée]but de (?:la )?soir[ée]e?"#)?, - |_| { - Ok(helpers::hour(18, false)? - .span_to(&helpers::hour(21, false)?, false)? - .latent() - .form(Form::PartOfDay(PartOfDayForm::Evening))) - } - ); - b.rule_1_terminal("fin de soirée", - b.reg(r#"fin de (?:la )?soir[ée]e?"#)?, - |_| { - Ok(helpers::hour(21, false)? - .span_to(&helpers::hour(0, false)?, false)? - .latent() - .form(Form::PartOfDay(PartOfDayForm::Evening))) - } - ); - b.rule_1_terminal("diner", - b.reg(r#"d[iî]ner|souper"#)?, - |_| Ok(helpers::hour(18, false)? - .span_to(&helpers::hour(23, false)?, false)? - .form(Form::Meal)) - ); - b.rule_1_terminal("nuit", - b.reg(r#"nuit"#)?, - |_| { - Ok(helpers::hour(22, false)? - .span_to(&helpers::hour(6, false)?, false)? - .latent() - .form(Form::PartOfDay(PartOfDayForm::Night))) - } - ); - b.rule_2("a l'heure de ", - b.reg(r#"(?:[àa] )?l[' ]heure du|au moment du|pendant l[ea']|au|pour l[ea']|l[ea']"#)?, - datetime_check!(|datetime: &DatetimeValue| form!(Form::Meal)(datetime)), - |_, a| Ok(a.value().clone().not_latent()) - ); - b.rule_2(" ", - datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), - datetime_check!(form!(Form::Meal)), - |a, b| a.value().intersect(b.value()) - ); - b.rule_2("prep? & article ", // This is very catch-all/junky - b.reg(r#"(?:pendant |durant |dans |d[eè]s )?l[ae']?|en|au"#)?, - datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime)), - |_, a| Ok(a.value().clone().not_latent()) - ); - b.rule_2("ce ", - b.reg(r#"cet?t?e?"#)?, - datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), - |_, datetime| Ok(helpers::cycle_nth(Grain::Day, 0)? - .intersect(datetime.value())? - .form(datetime.value().form.clone()) - .datetime_kind(DatetimeKind::DatetimeComplement { date_and_time: true, today: true })) - ); - b.rule_2(" ", - datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), - datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), - |a, b| a.value().intersect(b.value()) - ); - b.rule_2(" du matin", - datetime_check!(form!(Form::TimeOfDay(_))), - b.reg(r#"(?:(?:du|dans|de) )?(?:(?:au|le|la) )?mat(?:in[ée]?e?)?(?: pile| exactement| pr[eé]cises?)?"#)?, - |a, _| { - let period = helpers::hour(0, false)? - .span_to(&helpers::hour(12, false)?, false)?; - Ok(a.value().intersect(&period)?.form(a.value().form.clone())) - } - ); - b.rule_2(" de l'apres-midi", - datetime_check!(form!(Form::TimeOfDay(_))), - b.reg(r#"(?:dans |de )?l[' ]apr[eè]s[\- ]midi(?: pile| exactement| pr[eé]cises?)?"#)?, - |a, _| { - let period = helpers::hour(12, false)? - .span_to(&helpers::hour(19, false)?, false)?; - Ok(a.value().intersect(&period)?.form(a.value().form.clone())) - } - ); - b.rule_2(" du soir", - datetime_check!(form!(Form::TimeOfDay(_))), - b.reg(r#"(?:(?:du|dans|de) )?(?:(?:au|le|la) )?soir[ée]?e?(?: pile| exactement| pr[eé]cises?)?"#)?, - |a, _| { - let period = helpers::hour(16, false)? - .span_to(&helpers::hour(0, false)?, false)?; - Ok(a.value().intersect(&period)?.form(a.value().form.clone())) - } - ); - b.rule_3(" du ", - datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), - b.reg(r#"du"#)?, - datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), - |a, _, b| b.value().intersect(a.value()) - ); - b.rule_1_terminal("(ce/le) week-end", - b.reg(r#"(?:[cl]e )?week(?:\s|-)?end"#)?, - |_| helpers::weekend() - ); - b.rule_1_terminal("le week-end dernier", - b.reg(r#"le week(?:\s|-)?end dernier"#)?, - |_| { - let weekend = helpers::weekend()?; - Ok(weekend.the_nth(-1)?.datetime_kind(DatetimeKind::DatePeriod)) - } - ); - b.rule_1_terminal("le week-end prochain", - b.reg(r#"le week(?:\s|-)?end prochain|le prochain week(?:\s|-)?end"#)?, - |_| { - let weekend = helpers::weekend()?; - Ok(weekend.the_nth(1)?.datetime_kind(DatetimeKind::DatePeriod)) - } - ); - b.rule_1_terminal("début de semaine", - b.reg(r#"(?:en |au )?d[ée]but de (?:cette |la )?semaine"#)?, - |_| helpers::day_of_week(Weekday::Mon) - ?.span_to(&helpers::day_of_week(Weekday::Tue)?, false) - ); - b.rule_1_terminal("milieu de semaine", - b.reg(r#"(?:en |au )?milieu de (?:cette |la )?semaine"#)?, - |_| helpers::day_of_week(Weekday::Wed) - ?.span_to(&helpers::day_of_week(Weekday::Thu)?, false) - ); - b.rule_1_terminal("fin de semaine (Warning: this is the weekend in Quebec)", - b.reg(r#"(?:en |à la )?fin de (?:cette |la )?semaine"#)?, - |_| helpers::day_of_week(Weekday::Thu) - ?.span_to(&helpers::day_of_week(Weekday::Sun)?, false) - ); - b.rule_1_terminal("en semaine", - b.reg(r#"(?:pendant la |en )semaine"#)?, - |_| helpers::day_of_week(Weekday::Mon) - ?.span_to(&helpers::day_of_week(Weekday::Fri)?, false) - ); - b.rule_1_terminal("season", - b.reg(r#"(?:cet )?(?:été|ete)"#)?, - |_| helpers::month_day(6, 21)?.span_to(&helpers::month_day(9, 23)?, false) - ); - b.rule_1_terminal("season", - b.reg(r#"(?:cet )?automne"#)?, - |_| helpers::month_day(9, 23)?.span_to(&helpers::month_day(12, 21)?, false) - ); - b.rule_1_terminal("season", - b.reg(r#"(?:cet )?hiver"#)?, - |_| helpers::month_day(12, 21)?.span_to(&helpers::month_day(3, 20)?, false) - ); - b.rule_1_terminal("season", - b.reg(r#"(?:ce )?printemps"#)?, - |_| helpers::month_day(3, 20)?.span_to(&helpers::month_day(6, 21)?, false) - ); - b.rule_2("le ", - b.reg(r#"l[ea]"#)?, - datetime_check!(|datetime: &DatetimeValue| !datetime.latent), - |_, a| Ok(a.value().clone()) - ); - b.rule_4("dd-dd (interval)", - b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, - b.reg(r#"\-|(?:jusqu')?au"#)?, - b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, - datetime_check!(form!(Form::Month(_))), - |a, _, b, month| { - let start = month.value().intersect(&helpers::day_of_month(a.group(1).parse()?)?)?; - let end = month.value().intersect(&helpers::day_of_month(b.group(1).parse()?)?)?; - start.span_to(&end, true) - } - ); - b.rule_4("-dd (interval)", - datetime_check!(), - b.reg(r#"\-|(?:jusqu')?au"#)?, - b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, - datetime_check!(form!(Form::Month(_))), - |datetime, _, text_match, month| { - let start = month.value().intersect(datetime.value())?; - let end = month.value().intersect(&helpers::day_of_month(text_match.group(1).parse()?)?)?; - start.span_to(&end, true) - } - ); - b.rule_5("- dd (interval)", - datetime_check!(), - b.reg(r#"\-|(?:jusqu')?au"#)?, - datetime_check!(form!(Form::DayOfWeek{..})), - b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, - datetime_check!(form!(Form::Month(_))), - |datetime, _, _, text_match, month| { - let start = month.value().intersect(datetime.value())?; - let end = month.value().intersect(&helpers::day_of_month(text_match.group(1).parse()?)?)?; - start.span_to(&end, true) - } - ); - b.rule_6(" 1er- dd (interval)", - datetime_check!(form!(Form::DayOfWeek{..})), - b.reg(r#"premier|prem\.?|1er|1 er"#)?, - b.reg(r#"\-|(?:jusqu')?au"#)?, - datetime_check!(form!(Form::DayOfWeek{..})), - b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, - datetime_check!(form!(Form::Month(_))), - |_, _, _, _, text_match, month| { - let start = month.value().intersect(&helpers::day_of_month(1)?)?; - let end = month.value().intersect(&helpers::day_of_month(text_match.group(1).parse()?)?)?; - start.span_to(&end, true) - } - ); - b.rule_6("du dd- dd (interval)", - b.reg(r#"du"#)?, - b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, - b.reg(r#"\-|(?:jusqu')?au"#)?, - datetime_check!(form!(Form::DayOfWeek{..})), - b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, - datetime_check!(form!(Form::Month(_))), - |_, a, _, _, b, month| { - let start = month.value().intersect(&helpers::day_of_month(a.group(1).parse()?)?)?; - let end = month.value().intersect(&helpers::day_of_month(b.group(1).parse()?)?)?; - start.span_to(&end, true) - } - ); - b.rule_6("du dd- dd (interval)", - b.reg(r#"du"#)?, - datetime_check!(), - b.reg(r#"\-|(?:jusqu')?au"#)?, - datetime_check!(form!(Form::DayOfWeek{..})), - b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, - datetime_check!(form!(Form::Month(_))), - |_, datetime, _, _, text_match, month| { - let start = month.value().intersect(datetime.value())?; - let end = month.value().intersect(&helpers::day_of_month(text_match.group(1).parse()?)?)?; - start.span_to(&end, true) - } - ); - b.rule_4("la nuit ", - b.reg(r#"(dans|pendant|durant) la nuit (?:du|de)"#)?, - datetime_check!(form!(Form::DayOfWeek{..})), - b.reg(r#"\-|(?:jusqu')?au"#)?, - datetime_check!(form!(Form::DayOfWeek{..})), - |_, start, _, end| { - let start = start.value().intersect(&helpers::hour(22, false)?)?; - let end = end.value().intersect(&helpers::hour(6, false)?)?; - start.span_to(&end, false) - } - ); - b.rule_5("entre dd et dd (interval)", - b.reg(r#"entre(?: le)?"#)?, - b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, - b.reg(r#"et(?: le)?"#)?, - b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, - datetime_check!(form!(Form::Month(_))), - |_, a, _, b, month| { - let start = month.value().intersect(&helpers::day_of_month(a.group(1).parse()?)?)?; - let end = month.value().intersect(&helpers::day_of_month(b.group(1).parse()?)?)?; - start.span_to(&end, true) - } - ); - b.rule_4_terminal("du dd au dd(interval)", - b.reg(r#"du"#)?, - b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, - b.reg(r#"(?:jusqu')?au"#)?, - b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, - |_, a, _, b| { - let start = helpers::day_of_month(a.group(1).parse()?)?; - let end = helpers::day_of_month(b.group(1).parse()?)?; - start.span_to(&end, true) - } - ); - b.rule_2("fin (interval)", - b.reg(r#"fin(?: du mois d[e']? ?)?"#)?, - datetime_check!(form!(Form::Month(_))), - |_, month| { - let start = month.value().intersect(&helpers::day_of_month(25)?)?; - let end = helpers::cycle(Grain::Day)?.last_of(month.value())?; - start.span_to(&end, true) - } - ); - b.rule_2("début (interval)", - b.reg(r#"d[ée]but(?: du mois d[e'] ?)?"#)?, - datetime_check!(form!(Form::Month(_))), - |_, month| { - let start = month.value().intersect(&helpers::day_of_month(1)?)?; - let end = month.value().intersect(&helpers::day_of_month(5)?)?; - start.span_to(&end, true) - } - ); - b.rule_2("première quinzaine de (interval)", - b.reg(r#"(?:premi[èe]re|1 ?[èe]re) (?:quinzaine|15 ?aine) d[e']"#)?, - datetime_check!(form!(Form::Month(_))), - |_, month| { - let start = month.value().intersect(&helpers::day_of_month(1)?)?; - let end = month.value().intersect(&helpers::day_of_month(14)?)?; - start.span_to(&end, true) - } - ); - b.rule_2("deuxième quinzaine de (interval)", - b.reg(r#"(?:deuxi[èe]me|2 ?[èe]me) (?:quinzaine|15 ?aine) d[e']"#)?, - datetime_check!(form!(Form::Month(_))), - |_, month| { - let start = month.value().intersect(&helpers::day_of_month(15)?)?; - let end = helpers::cycle(Grain::Day)?.last_of(month.value())?; - start.span_to(&end, true) - } - ); - b.rule_2("", - b.reg(r#"mi[- ]"#)?, - datetime_check!(form!(Form::Month(_))), - |_, month| { - let start = month.value().intersect(&helpers::day_of_month(10)?)?; - let end = month.value().intersect(&helpers::day_of_month(19)?)?; - start.span_to(&end, true) - } - ); - - /* Intervals */ - b.rule_3(" - (interval)", - datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), - b.reg(r#" \- |(?:jusqu')?(?:au|[aà])"#)?, - datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), - |a, _, b| a.value().span_to(b.value(), true) - ); - b.rule_3(" avant (interval)", - datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), - b.reg(r#"jusqu'(?:au|[aà])|avant"#)?, - datetime_check!(form!(Form::TimeOfDay(_))), - |a, _, b| a.value().span_to(b.value(), false) - ); - b.rule_4("de - (interval)", - b.reg(r#"depuis|d[e'u]?"#)?, - datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), - b.reg(r#" \- |(?:jusqu')?(?:au|[aà])"#)?, - datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), - |_, a, _, b| a.value().span_to(b.value(), true) - ); - b.rule_4("entre et (interval)", - b.reg(r#"entre"#)?, - datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), - b.reg(r#"et"#)?, - datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), - |_, a, _, b| a.value().span_to(b.value(), true) - ); - // Specific case with years - b.rule_5("de - (interval)", - b.reg(r#"depuis|d[e'u]?"#)?, - datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), - b.reg(r#" \- |(?:jusqu')?(?:au|[aà])"#)?, - datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime) && datetime.is_coarse_grain_greater_than(Grain::Year)), - datetime_check!(form!(Form::Year(_))), - |_, a, _, b, year| a.value().span_to(b.value(), true)?.intersect(year.value()) - ); - b.rule_5("entre et (interval)", - b.reg(r#"entre"#)?, - datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), - b.reg(r#"et"#)?, - datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime) && datetime.is_coarse_grain_greater_than(Grain::Year)), - datetime_check!(form!(Form::Year(_))), - |_, a, _, b, year| a.value().span_to(b.value(), true)?.intersect(year.value()) - ); - b.rule_3(" - (interval)", - datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(_))(datetime)), - b.reg(r#" \- |(?:jusqu')?(?:au|[aà])"#)?, - datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(_))(datetime)), - |a, _, b| a.value().smart_span_to(b.value(), false) - ); - b.rule_4("de - (interval)", - b.reg(r#"(?:[aà] partir )?d['e]"#)?, - datetime_check!(form!(Form::TimeOfDay(_))), - b.reg(r#"(?:jusqu')?(?:au|[aà])"#)?, - datetime_check!(form!(Form::TimeOfDay(_))), - |_, a, _, b| a.value().smart_span_to(b.value(), false) - ); - b.rule_2("de maintenant - (interval)", - b.reg(r#"(?:[aà] partir )?de maintenant (?:jusqu')?(?:au|[aà])"#)?, - datetime_check!(form!(Form::TimeOfDay(_))), - |_, a| helpers::cycle_nth(Grain::Second, 0)?.smart_span_to(a.value(), false) - ); - b.rule_3("de - maintenant (interval)", - b.reg(r#"(?:[aà] partir )?d['e]"#)?, - datetime_check!(form!(Form::TimeOfDay(_))), - b.reg(r#"(?:jusqu')?(?:au|[aà]) maintenant"#)?, - |_, a, _| { - let now = helpers::cycle_nth(Grain::Second, 0)?; - a.value().smart_span_to(&now, false) - } - ); - b.rule_4("entre et (interval)", - b.reg(r#"entre"#)?, - datetime_check!(form!(Form::TimeOfDay(_))), - b.reg(r#"et"#)?, - datetime_check!(form!(Form::TimeOfDay(_))), - |_, a, _, b| a.value().smart_span_to(b.value(), false) - ); - b.rule_2("jusqu'à ", - b.reg(r#"(?:n[ ']importe quand )?jusqu'(?:au|[aà]|en)"#)?, - datetime_check!(|datetime: &DatetimeValue| !datetime.latent), - |_, datetime| Ok(datetime.value().clone().mark_before_end()) - ); - b.rule_2("avant ", - b.reg(r#"(?:n[ ']importe quand )?avant"#)?, - datetime_check!(|datetime: &DatetimeValue| !datetime.latent), - |_, datetime| Ok(datetime.value().clone().mark_before_start()) - ); - b.rule_2("après ", - b.reg(r#"apr[eè]s"#)?, - datetime_check!(|datetime: &DatetimeValue| !datetime.latent), - |_, datetime| Ok(datetime.value().clone().mark_after_end()) - ); - b.rule_2("à partir de ", - b.reg(r#"[aà] partir d['eu]"#)?, - datetime_check!(|datetime: &DatetimeValue| !datetime.latent), - |_, datetime| Ok(datetime.value().clone().mark_after_start()) - ); - b.rule_2("à partir de ", - b.reg(r#"[aà] partir d['eu](?:l[ 'a])?"#)?, - datetime_check!(form!(Form::PartOfDay(_))), - |_, datetime| Ok(datetime.value().clone().mark_after_start()) - ); - b.rule_2("après le ", - b.reg(r#"apr(?:e|è)s le"#)?, - integer_check_by_range!(1, 31), - |_, integer| Ok(helpers::day_of_month(integer.value().value as u32)?.mark_after_end()) - ); - b.rule_2("après le ", - b.reg(r#"[aà] partir d['eu]"#)?, - integer_check_by_range!(1, 31), - |_, integer| Ok(helpers::day_of_month(integer.value().value as u32)?.mark_after_start()) - ); - b.rule_2("il y a ", - b.reg(r#"il y a"#)?, - duration_check!(), - |_, duration| duration.value().ago() - ); - // With "depuis/d'ici", interpretation of duration ambiguous with time-of-day we choose time-of-day - // I.e. "x heures (y)", but not "x heures et y minutes", "x minutes", etc. - // FIXME: some time-of-day patterns should be removed, e.g. "x heures y minutes" - they are not - // proper time-of-day, but they can be duration expressions - // TODO: check if Grain::Second here breaks DatePeriod - cf. implem. of equivalent in English? - b.rule_2("depuis ", - b.reg(r#"depuis|[cç]a fait"#)?, - duration_check!(), - |_, duration| { - if duration.value().get_coarser_grain() == Grain::Hour { - return Err(RuleError::Invalid.into()) - } - duration.value().ago()? - .span_to(&helpers::cycle_nth(Grain::Second, 0)?, false) - }); - b.rule_2("depuis ", - b.reg(r#"depuis"#)?, - datetime_check!(|datetime: &DatetimeValue| !datetime.latent), - |_, datetime| Ok(datetime.value().the_nth(-1)?.mark_after_start()) - ); - b.rule_2("d'ici ", - b.reg(r#"d'ici|dans l(?:'|es?)"#)?, - duration_check!(), - |_, duration| { - let duration_grain = duration.value().get_coarser_grain(); - // Priority to d'ici - if duration_grain == Grain::Hour && - // FIXME: There must be a better way to do this check! - duration.value().period.0.get(Grain::Hour as usize).unwrap_or(&0) <= &23 { - return Err(RuleError::Invalid.into()) - } - let grain = if duration_grain.is_date_grain() { Grain::Day } else { Grain::Second }; - let start = helpers::cycle_nth(grain, 0)?; - let end = if grain == Grain::Day { duration.value().in_present_day()? } else { duration.value().in_present()? }; - start.span_to(&end, true) - } - ); - b.rule_2("dans le ", - b.reg(r#"dans l(?:'|es?)"#)?, - duration_check!(), - |_, duration| { - let duration_grain = duration.value().get_coarser_grain(); - let grain = if duration_grain.is_date_grain() { Grain::Day } else { Grain::Second }; - let start = helpers::cycle_nth(grain, 0)?; - let end = if grain == Grain::Day { duration.value().in_present_day()? } else { duration.value().in_present()? }; - start.span_to(&end, false) - } - ); - b.rule_2("d'ici ", - b.reg(r#"d'ici"#)?, - datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(_))(datetime)), - |_, tod| { - // FIXME: This adds one second to the value of now+then - let now = helpers::cycle_nth(Grain::Second, 0)?; - let then = tod.value().clone().mark_before_start(); - now.span_to(&then, false) - } - ); - b.rule_2("d'ici ", - b.reg(r#"d'ici"#)?, - datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), - |_, date| { - // FIXME: This adds one second to the value of now+then - let today = helpers::cycle_nth(Grain::Day, 0)?; - let then = date.value().clone().mark_before_start(); - today.span_to(&then, false) - } - ); - b.rule_3(" apres ", - duration_check!(), - b.reg(r#"apr[eè]s"#)?, - datetime_check!(), - |duration, _, datetime| duration.value().after(datetime.value()) - ); - b.rule_3(" avant ", - duration_check!(), - b.reg(r#"avant"#)?, - datetime_check!(), - |duration, _, datetime| duration.value().before(datetime.value()) - ); - Ok(()) -} - -pub fn rules_temperature(b: &mut RuleSetBuilder) -> RustlingResult<()> { - b.rule_1("number as temp", - number_check!(), - |a| { - Ok(TemperatureValue { - value: a.value().value(), - unit: None, - latent: true, - }) - }); - b.rule_2(" degrees", - temperature_check!(), - b.reg(r#"(?:deg(?:r[éeè])?s?\.?)|°"#)?, - |a, _| { - Ok(TemperatureValue { - value: a.value().value, - unit: Some("degree"), - latent: false, - }) - }); - b.rule_2(" Celcius", - temperature_check!(), - b.reg(r#"centigrades?|c(?:el[cs]?(?:ius)?)?\.?"#)?, - |a, _| { - Ok(TemperatureValue { - value: a.value().value, - unit: Some("celsius"), - latent: false, - }) - }); - b.rule_2(" Fahrenheit", - temperature_check!(), - b.reg(r#"f(?:ah?reh?n(?:h?eit)?)?\.?"#)?, - |a, _| { - Ok(TemperatureValue { - value: a.value().value, - unit: Some("fahrenheit"), - latent: false, - }) - }); - b.rule_2(" Kelvin", - temperature_check!(), - b.reg(r#"k(?:elvin)?\.?"#)?, - |a, _| { - Ok(TemperatureValue { - value: a.value().value, - unit: Some("kelvin"), - latent: false, - }) - }); - b.rule_2(" en dessous de zero", - temperature_check!(|temp: &TemperatureValue| !temp.latent), - b.reg(r#"en dessous de (?:0|z[ée]ro)"#)?, - |a, _| { - Ok(TemperatureValue { - value: -1.0 * a.value().value, - latent: false, - ..*a.value() - }) - }); - Ok(()) -} - -pub fn rules_numbers(b: &mut RuleSetBuilder) -> RustlingResult<()> { - b.rule_2("intersect", - number_check!(|number: &NumberValue| number.grain().unwrap_or(0) > 1), - number_check!(), - |a, b| helpers::compose_numbers(&a.value(), &b.value())); - b.rule_1_terminal( - "number (0..16)", - b.reg(r#"(z[eé]ro|une?|deux|trois|quatre|cinq|six|sept|huit|neuf|dix|onze|douze|treize|quatorze|quinze|seize)"#)?, - |text_match| { - let value = match text_match.group(1).as_ref() { - "zéro" => 0, - "zero" => 0, - "un" => 1, - "une" => 1, - "deux" => 2, - "trois" => 3, - "quatre" => 4, - "cinq" => 5, - "six" => 6, - "sept" => 7, - "huit" => 8, - "neuf" => 9, - "dix" => 10, - "onze" => 11, - "douze" => 12, - "treize" => 13, - "quatorze" => 14, - "quinze" => 15, - "seize" => 16, - _ => return Err(RuleError::Invalid.into()), - }; - IntegerValue::new(value) - }); - b.rule_1_terminal("quelques", - b.reg(r#"quelques"#)?, - |_| IntegerValue::new_with_grain(3, 1) - ); - b.rule_1_terminal("number (20..60)", - b.reg(r#"(vingt|trente|quarante|cinquante|soixante)"#)?, - |text_match| { - let value = match text_match.group(1).as_ref() { - "vingt" => 20, - "trente" => 30, - "quarante" => 40, - "cinquante" => 50, - "soixante" => 60, - _ => return Err(RuleError::Invalid.into()), - }; - IntegerValue::new(value) - }); - b.rule_2("number (17..19)", - integer_check_by_range!(10, 10), - integer_check_by_range!(7, 9), - |_, b| IntegerValue::new(b.value().value + 10)); - b.rule_3("number (17..19)", - integer_check_by_range!(10, 10), - b.reg(r"-")?, - integer_check_by_range!(7, 9), - |_, _, b| IntegerValue::new(b.value().value + 10)); - b.rule_2_terminal("number 80", - b.reg(r#"quatre"#)?, - b.reg(r#"vingts?"#)?, - |_, _| IntegerValue::new(80)); - b.rule_3_terminal("number 80", - b.reg(r#"quatre"#)?, - b.reg(r"-")?, - b.reg(r#"vingts?"#)?, - |_, _, _| IntegerValue::new(80)); - b.rule_3("numbers 21 31 41 51", - integer_check_by_range!(20, 50, |integer: &IntegerValue| integer.value % 10 == 0), - b.reg(r#"-?et-?"#)?, - integer_check_by_range!(1, 1), - |a, _, b| IntegerValue::new(a.value().value + b.value().value)); - b.rule_2("numbers 22..29 32..39 .. 52..59", - integer_check_by_range!(20, 50, |integer: &IntegerValue| integer.value % 10 == 0), - integer_check_by_range!(2, 9), - |a, b| IntegerValue::new(a.value().value + b.value().value)); - b.rule_3("numbers 22..29 32..39 .. 52..59", - integer_check_by_range!(20, 50, |integer: &IntegerValue| integer.value % 10 == 0), - b.reg(r"-")?, - integer_check_by_range!(2, 9), - |a, _, b| IntegerValue::new(a.value().value + b.value().value)); - b.rule_3("numbers 61 71", - integer_check_by_range!(60, 60), - b.reg(r#"-?et-?"#)?, - integer_check_by_range!(1, - 11, - |integer: &IntegerValue| integer.value == 1 || integer.value == 11), - |a, _, b| IntegerValue::new(a.value().value + b.value().value)); - b.rule_2("numbers 81 91", - integer_check_by_range!(80, 80), - integer_check_by_range!(1, - 11, - |integer: &IntegerValue| integer.value == 1 || integer.value == 11), - |a, b| IntegerValue::new(a.value().value + b.value().value)); - b.rule_3("numbers 81 91", - integer_check_by_range!(80, 80), - b.reg(r#"-"#)?, - integer_check_by_range!(1, - 11, - |integer: &IntegerValue| integer.value == 1 || integer.value == 11), - |a, _, b| IntegerValue::new(a.value().value + b.value().value)); - b.rule_2("numbers 62..69 .. 92..99", - integer_check_by_range!(60, - 80, - |integer: &IntegerValue| integer.value == 60 || integer.value == 80), - integer_check_by_range!(2, 19), - |a, b| IntegerValue::new(a.value().value + b.value().value)); - b.rule_3("numbers 62..69 .. 92..99", - integer_check_by_range!(60, - 80, - |integer: &IntegerValue| integer.value == 60 || integer.value == 80), - b.reg(r"-")?, - integer_check_by_range!(2, 19), - |a, _, b| IntegerValue::new(a.value().value + b.value().value)); - b.rule_1_terminal("hundred", - b.reg(r#"cents?"#)?, - |_| IntegerValue::new_with_grain(100, 2) - ); - b.rule_1_terminal("thousand", - b.reg(r#"milles?"#)?, - |_| IntegerValue::new_with_grain(1000, 3) - ); - b.rule_1_terminal("million", - b.reg(r#"millions?"#)?, - |_| IntegerValue::new_with_grain(1000000, 6) - ); - b.rule_1_terminal("billion", - b.reg(r#"milliards?"#)?, - |_| IntegerValue::new_with_grain(1000000000, 9) - ); - b.rule_2("number hundreds", - integer_check_by_range!(1, 99), - b.reg(r#"cents?"#)?, - |a, _| { - Ok(IntegerValue { - value: a.value().value * 100, - grain: Some(2), - ..IntegerValue::default() - }) - }); - b.rule_2("number thousands", - integer_check_by_range!(1, 999), - b.reg(r#"milles?"#)?, - |a, _| { - Ok(IntegerValue { - value: a.value().value * 1000, - grain: Some(3), - ..IntegerValue::default() - }) - }); - b.rule_2("number millions", - integer_check_by_range!(1, 999), - b.reg(r#"millions?"#)?, - |a, _| { - Ok(IntegerValue { - value: a.value().value * 1000000, - grain: Some(6), - ..IntegerValue::default() - }) - }); - b.rule_2("number billions", - integer_check_by_range!(1, 999), - b.reg(r#"milliards?"#)?, - |a, _| { - Ok(IntegerValue { - value: a.value().value * 1000000000, - grain: Some(9), - ..IntegerValue::default() - }) - }); - b.rule_1_terminal("integer (numeric)", - b.reg(r#"(\d{1,18})"#)?, - |text_match| { - let value: i64 = text_match.group(1).parse()?; - IntegerValue::new(value) - }); - b.rule_1_terminal("integer with thousands separator .", - b.reg(r#"(\d{1,3}(\.\d\d\d){1,5})"#)?, - |text_match| { - let reformatted_string = text_match.group(1).replace(".", ""); - let value: i64 = reformatted_string.parse()?; - IntegerValue::new(value) - }); - b.rule_1_terminal("decimal number", - b.reg(r#"(\d*,\d+)"#)?, - |text_match| { - let reformatted_string = text_match.group(1).replace(",", "."); - let value: f32 = reformatted_string.parse()?; - FloatValue::new(value) - }); - b.rule_3("number dot number", - number_check!(|number: &NumberValue| !number.prefixed()), - b.reg(r#"virgule|point"#)?, - number_check!(|number: &NumberValue| !number.suffixed()), - |a, _, b| { - let power = b.value().value().to_string().chars().count(); - let coeff = 10.0_f32.powf(-1.0 * power as f32); - Ok(FloatValue { - value: b.value().value() * coeff + a.value().value(), - ..FloatValue::default() - }) - }); - b.rule_4("number dot zero ... number", - number_check!(|number: &NumberValue| !number.prefixed()), - b.reg(r#"virgule|point"#)?, - b.reg(r#"(?:(?:z[eé]ro )*(?:z[eé]ro))"#)?, - number_check!(|number: &NumberValue| !number.suffixed()), - |a, _, zeros, b| { - let power = zeros.group(0).split_whitespace().count() + b.value().value().to_string().chars().count(); - let coeff = 10.0_f32.powf(-1.0 * power as f32); - Ok(FloatValue { - value: b.value().value() * coeff + a.value().value(), - ..FloatValue::default() - }) - }); - b.rule_1_terminal("decimal with thousands separator", - b.reg(r#"(\d+(\.\d\d\d)+,\d+)"#)?, - |text_match| { - let reformatted_string = text_match.group(1).replace(".", "").replace(",", "."); - let value: f32 = reformatted_string.parse()?; - FloatValue::new(value) - }); - b.rule_2("numbers prefix with -, negative or minus", - b.reg(r#"-|moins"#)?, - number_check!(|number: &NumberValue| !number.prefixed()), - |_, a| -> RuleResult { - Ok(match a.value().clone() { - // checked - NumberValue::Integer(integer) => { - IntegerValue { - value: integer.value * -1, - prefixed: true, - ..integer - } - .into() - } - NumberValue::Float(float) => { - FloatValue { - value: float.value * -1.0, - prefixed: true, - ..float - } - .into() - } - }) - }); - b.rule_2("numbers suffixes (K, M, G)", - number_check!(|number: &NumberValue| !number.suffixed()), - b.reg_neg_lh(r#"([kmg])"#, r#"^[\W\$€]"#)?, - |a, text_match| -> RuleResult { - let multiplier = match text_match.group(0).as_ref() { - "k" => 1000, - "m" => 1000000, - "g" => 1000000000, - _ => return Err(RuleError::Invalid.into()), - }; - Ok(match a.value().clone() { // checked - NumberValue::Integer(integer) => { - IntegerValue { - value: integer.value * multiplier, - suffixed: true, - ..integer - } - .into() - } - NumberValue::Float(float) => { - let product = float.value * (multiplier as f32); - if product.floor() == product { - IntegerValue { - value: product as i64, - suffixed: true, - ..IntegerValue::default() - } - .into() - } else { - FloatValue { - value: product, - suffixed: true, - ..float - } - .into() - } - } - }) - }); - b.rule_1_terminal("(douzaine ... soixantaine)", - b.reg(r#"(demi[ -]douz|diz|douz|quinz|vingt|trent|quarant|cinquant|soixant|cent)aines?"#)?, - |text_match| { - let value = match text_match.group(1).as_ref() { - "demi douz" => 6, - "demi-douz" => 6, - "diz" => 10, - "douz" => 12, - "quinz" => 15, - "vingt" => 20, - "trent" => 30, - "quarant" => 40, - "cinquant" => 50, - "soixant" => 60, - "cent" => 100, - _ => return Err(RuleError::Invalid.into()), - }; - Ok(IntegerValue { - value, - group: true, - .. IntegerValue::default() - }) - } - ); - b.rule_2("number dozen", - integer_check_by_range!(1, 9), - integer_check!(|integer: &IntegerValue| integer.group), - |a, b| { - Ok(IntegerValue { - value: a.value().value * b.value().value, - grain: b.value().grain, - group: true, - ..IntegerValue::default() - }) - }); - b.rule_1_terminal("ordinal 0", - b.reg(r#"z[eé]rot?i[eè]me"#)?, - |_| { - Ok(OrdinalValue::new(0)) - } - ); - b.rule_1_terminal("ordinal 1", - b.reg(r#"premi[eè]re?"#)?, - |_| { - Ok(OrdinalValue::new(1)) - } - ); - b.rule_1_terminal("ordinal 2", - b.reg(r#"seconde?|deuxi[eè]me"#)?, - |_| { - Ok(OrdinalValue::new(2)) - } - ); - b.rule_1_terminal( - "ordinals (premier..seizieme)", - b.reg(r#"(trois|quatr|cinqu|six|sept|huit|neuv|dix|onz|douz|treiz|quatorz|quinz|seiz)i[eè]me"#)?, - |text_match| { - let value = match text_match.group(1).as_ref() { - "trois" => 3, - "quatr" => 4, - "cinqu" => 5, - "six" => 6, - "sept" => 7, - "huit" => 8, - "neuv" => 9, - "dix" => 10, - "onz" => 11, - "douz" => 12, - "treiz" => 13, - "quatorz" => 14, - "quinz" => 15, - "seiz" => 16, - _ => return Err(RuleError::Invalid.into()), - }; - Ok(OrdinalValue::new(value)) - }); - b.rule_2("17ieme, 18ieme, 19ieme", - b.reg(r#"dix-?"#)?, - ordinal_check_by_range!(7, 9), - |_, ordinal| { - Ok(OrdinalValue::new(10 + ordinal.value().value)) - } - ); - b.rule_1_terminal("20ieme, 30ieme, 40ieme, 50ieme, 60ieme", - b.reg(r#"(vingt|trent|quarant|cinquant|soixant)i[èe]me"#)?, - |text_match| { - let value = match text_match.group(1).as_ref() { - "vingt" => 20, - "trent" => 30, - "quarant" => 40, - "cinquant" => 50, - "soixant" => 60, - _ => return Err(RuleError::Invalid.into()), - }; - Ok(OrdinalValue::new(value)) - } - ); - b.rule_1_terminal("80ieme", - b.reg(r#"quatre[- ]vingts?i[èe]me"#)?, - |_| { - Ok(OrdinalValue::new(80)) - } - ); - b.rule_2("22ieme...29ieme, 32ieme...39ieme, 42ieme...49ieme, 52ieme...59ieme", - integer_check_by_range!(20, 50, |integer: &IntegerValue| integer.value % 10 == 0), - ordinal_check_by_range!(2, 9), - |integer, ordinal| { - Ok(OrdinalValue::new(integer.value().value + ordinal.value().value)) - } - ); - b.rule_3("22ieme...29ieme, 32ieme...39ieme, 42ieme...49ieme, 52ieme...59ieme", - integer_check_by_range!(20, 50, |integer: &IntegerValue| integer.value % 10 == 0), - b.reg(r"-")?, - ordinal_check_by_range!(2, 9), - |integer, _, ordinal| { - Ok(OrdinalValue::new(integer.value().value + ordinal.value().value)) - } - ); - b.rule_2("62ieme...70ieme, 72ieme...79ieme, 90ieme, 92ieme...99ieme", - integer_check_by_range!(60, 80, |integer: &IntegerValue| integer.value == 60 || integer.value == 80), - ordinal_check_by_range!(2, 19), - |integer, ordinal| { - Ok(OrdinalValue::new(integer.value().value + ordinal.value().value)) - } - ); - b.rule_3("62ieme...70ieme, 72ieme...79ieme, 90ieme, 92ieme...99ieme", - integer_check_by_range!(60, 80, |integer: &IntegerValue| integer.value == 60 || integer.value == 80), - b.reg(r"-")?, - ordinal_check_by_range!(2, 19), - |integer, _, ordinal| { - Ok(OrdinalValue::new(integer.value().value + ordinal.value().value)) - } - ); - b.rule_2("21, 31, 41, 51, 61", - integer_check_by_range!(20, 60, |integer: &IntegerValue| integer.value % 10 == 0), - b.reg(r#"(?:et |-)uni[èe]me"#)?, - |integer, _| { - Ok(OrdinalValue::new(integer.value().value + 1)) - } - ); - b.rule_2("81", - integer_check_by_range!(80, 80), - b.reg(r#"(?:et )?uni[èe]me"#)?, - |integer, _| { - Ok(OrdinalValue::new(integer.value().value + 1)) - } - ); - b.rule_2("71, 91", - integer_check_by_range!(60, 60), - b.reg(r#"et onzi[eè]me"#)?, - |integer, _| { - Ok(OrdinalValue::new(integer.value().value + 11)) - } - ); - b.rule_2(" et demi", - integer_check_by_range!(0, 99), - b.reg(r#"et demie?"#)?, - |integer, _| { - FloatValue::new(integer.value().value as f32 + 0.5) - } - ); - b.rule_1_terminal("70, 80, 90 (Belgium and Switzerland)", - b.reg(r#"(sept|huit|non)ante"#)?, - |text_match| { - let value = match text_match.group(1).as_ref() { - "sept" => 70, - "huit" => 80, - "non" => 90, - _ => return Err(RuleError::Invalid.into()), - }; - IntegerValue::new(value) - } - ); - b.rule_1_terminal("71, 81, 91 (Belgium and Switzerland)", - b.reg(r#"(sept|huit|non)ante et une?"#)?, - |text_match| { - let value = match text_match.group(1).as_ref() { - "sept" => 71, - "huit" => 81, - "non" => 91, - _ => return Err(RuleError::Invalid.into()), - }; - IntegerValue::new(value) - } - ); - - b.rule_2("72..79, 82..89, 92..99, (Belgium and Switzerland)", - b.reg(r#"(sept|huit|non)ante"#)?, - integer_check_by_range!(2, 9), - |text_match, integer| { - let value = match text_match.group(1).as_ref() { - "sept" => 70, - "huit" => 80, - "non" => 90, - _ => return Err(RuleError::Invalid.into()), - }; - IntegerValue::new(value + integer.value().value) - } - ); - b.rule_1_terminal("ordinal (100, 1_000, 1_000_000)", - b.reg(r#"(cent|mill|million|milliard)i[èe]me"#)?, - |text_match| { - let (value, grain) = match text_match.group(1).as_ref() { - "cent" => (100, 2), - "mill" => (1_000, 3), - "million" => (1_000_000, 6), - "milliard" => (1_000_000_000, 9), - _ => return Err(RuleError::Invalid.into()), - }; - Ok(OrdinalValue::new_with_grain(value, grain)) - } - ); - - b.rule_2("ordinal (200..900, 2_000..9_000, 2_000_000..9_000_000_000)", - integer_check_by_range!(2, 999), - b.reg(r#"(cent|mill|million|milliard)i[èe]me"#)?, - |integer, text_match| { - let (value, grain) = match text_match.group(1).as_ref() { - "cent" => (100, 2), - "mill" => (1_000, 3), - "million" => (1_000_000, 6), - "milliard" => (1_000_000_000, 9), - _ => return Err(RuleError::Invalid.into()), - }; - Ok(OrdinalValue::new_with_grain(integer.value().value * value, grain)) - } - ); - - b.rule_2("ordinal (1_1_000..9_999_999_000)", - integer_check_by_range!(1000, 99_999_999_000), - ordinal_check!(|ordinal: &OrdinalValue| { - let grain = ordinal.grain.unwrap_or(0); - grain == 2 || grain % 3 == 0 - }), - |integer, ordinal| { - let grain = ordinal.value().grain.unwrap_or(0); - let next_grain = (grain / 3) * 3 + 3; - if integer.value().value % 10i64.pow(next_grain as u32) != 0 { return Err(RuleError::Invalid.into()); } - Ok(OrdinalValue::new(integer.value().value + ordinal.value().value)) - } - ); - - b.rule_2("ordinal (102...9_999_999)", - integer_check!(|integer: &IntegerValue| integer.value >= 100 || integer.value % 100 == 0), - ordinal_check_by_range!(2, 99), - |integer, ordinal| { - Ok(OrdinalValue::new(integer.value().value + ordinal.value().value)) - } - ); - b.rule_2("ordinal (101, 201, 301, ...)", - integer_check!(|integer: &IntegerValue| integer.value >= 100 || integer.value % 100 == 0), - b.reg(r#"(?:et |-)?uni[èe]me"#)?, - |integer, _| { - Ok(OrdinalValue::new(integer.value().value + 1)) - } - ); - b.rule_1_terminal("ordinal (digits)", - b.reg(r#"0*(\d+) ?(ere?|ère|ème|eme|ieme|ième)"#)?, - |text_match| { - let value: i64 = text_match.group(1).parse()?; - Ok(OrdinalValue::new(value)) - }); - b.rule_2("le ", - b.reg(r#"l[ea]"#)?, - ordinal_check!(), - |_, a| Ok((*a.value()).prefixed()) - ); - Ok(()) -} diff --git a/grammar/fr/src/rules_amount.rs b/grammar/fr/src/rules_amount.rs new file mode 100644 index 00000000..7c959d2f --- /dev/null +++ b/grammar/fr/src/rules_amount.rs @@ -0,0 +1,243 @@ +use rustling::*; +use rustling_ontology_values::dimension::*; +use rustling_ontology_values::dimension::Precision::*; +use rustling_ontology_values::helpers; + +pub fn rules_percentage(b: &mut RuleSetBuilder) -> RustlingResult<()> { + b.rule_2(" per cent", + number_check!(), + b.reg(r"(?:%|p\.c\.|p. cents?|pour[ -]?cents?)")?, + |number, _| Ok(PercentageValue(number.value().value())) + ); + Ok(()) +} + +pub fn rules_finance(b: &mut RuleSetBuilder) -> RustlingResult<()> { + b.rule_2("intersect (X cents)", + amount_of_money_check!(), + amount_of_money_check!(|money: &AmountOfMoneyValue| money.unit == Some("cent")), + |a, b| helpers::compose_money(a.value(), b.value())); + b.rule_3("intersect (and X cents)", + amount_of_money_check!(), + b.reg(r#"et"#)?, + amount_of_money_check!(|money: &AmountOfMoneyValue| money.unit == Some("cent")), + |a, _, b| helpers::compose_money(&a.value(), &b.value())); + b.rule_2("intersect", + amount_of_money_check!(|money: &AmountOfMoneyValue| money.unit != Some("cent")), + number_check!(), + |a, b| helpers::compose_money_number(&a.value(), &b.value())); + b.rule_1_terminal("$", + b.reg(r#"\$|dollars?"#)?, + |_| Ok(MoneyUnitValue { unit: Some("$") }) + ); + b.rule_1_terminal("EUR", + b.reg(r#"€|(?:[e€]uro?s?)"#)?, + |_| Ok(MoneyUnitValue { unit: Some("EUR") }) + ); + b.rule_1_terminal("£", + b.reg(r#"£|livres?"#)?, + |_| Ok(MoneyUnitValue { unit: Some("£") }) + ); + b.rule_1_terminal("USD", + b.reg(r#"us[d\$]|dollars? am[eé]ricains?"#)?, + |_| Ok(MoneyUnitValue { unit: Some("USD") }) + ); + b.rule_1_terminal("AUD", + b.reg(r#"au[d\$]|dollars? australiens?"#)?, + |_| Ok(MoneyUnitValue { unit: Some("AUD") }) + ); + b.rule_1_terminal("CAD", + b.reg(r#"cad|dollars? canadiens?"#)?, + |_| Ok(MoneyUnitValue { unit: Some("CAD") }) + ); + b.rule_1_terminal("HKD", + b.reg(r#"hkd|dollars? de hong[- ]kong"#)?, + |_| Ok(MoneyUnitValue { unit: Some("HKD") }) + ); + b.rule_1_terminal("KR", + b.reg(r#"kr|couronnes?"#)?, + |_| Ok(MoneyUnitValue { unit: Some("KR") }) + ); + b.rule_1_terminal("DKK", + b.reg(r#"dkk|couronnes? danoises?"#)?, + |_| Ok(MoneyUnitValue { unit: Some("DKK") }) + ); + b.rule_1_terminal("NOK", + b.reg(r#"nok|couronnes? norv[ée]giennes?"#)?, + |_| Ok(MoneyUnitValue { unit: Some("NOK") }) + ); + b.rule_1_terminal("SEK", + b.reg(r#"sek|couronnes? su[ée]doises?"#)?, + |_| Ok(MoneyUnitValue { unit: Some("SEK") }) + ); + b.rule_1_terminal("CHF", + b.reg(r#"chf|francs? suisses?"#)?, + |_| Ok(MoneyUnitValue { unit: Some("CHF") }) + ); + b.rule_1_terminal("RUB", + b.reg(r#"rub|roubles?"#)?, + |_| Ok(MoneyUnitValue { unit: Some("RUB") }) + ); + b.rule_1_terminal("INR", + b.reg(r#"inr|roupies?"#)?, + |_| Ok(MoneyUnitValue { unit: Some("INR") }) + ); + b.rule_1_terminal("JPY", + b.reg(r#"jpy|yens?"#)?, + |_| Ok(MoneyUnitValue { unit: Some("JPY") }) + ); + b.rule_1_terminal("RMB|CNH|CNY", + b.reg(r#"cny|cnh|rmb|yuans?|renmimbis?"#)?, + |_| Ok(MoneyUnitValue { unit: Some("CNY") }) + ); + b.rule_1_terminal("¥", + b.reg(r#"¥"#)?, + |_| Ok(MoneyUnitValue { unit: Some("¥") }) + ); + b.rule_1_terminal("KRW", + b.reg(r#"₩|krw|wons? (?:sud[- ])?cor[ée]ns?|wons?"#)?, + |_| Ok(MoneyUnitValue { unit: Some("KRW") }) + ); + b.rule_1_terminal("Bitcoin", + b.reg(r#"฿|bitcoins?"#)?, + |_| Ok(MoneyUnitValue { unit: Some("฿") }) + ); + b.rule_1_terminal("GBP", + b.reg(r#"gbp|livres? sterlings?"#)?, + |_| Ok(MoneyUnitValue { unit: Some("GBP") }) + ); + b.rule_1_terminal("cent", + b.reg(r#"centimes?|cents?|penn(?:y|ies)|fens?"#)?, + |_| Ok(MoneyUnitValue { unit: Some("cent") }) + ); + b.rule_1_terminal("unnamed currency", + b.reg(r#"(?:balle)s?"#)?, + |_| Ok(MoneyUnitValue { unit: None }) + ); + b.rule_2(" ", + number_check!(), + money_unit!(), + |a, b| { + Ok(AmountOfMoneyValue { + value: a.value().value(), + unit: b.value().unit, + ..AmountOfMoneyValue::default() + }) + }); + b.rule_3(" de ", // "un million de dollars" + integer_check!(|integer: &IntegerValue| !integer.group), + b.reg(r#"d[e']"#)?, + money_unit!(), + |a, _, b| { + Ok(AmountOfMoneyValue { + value: a.value().value as f32, + precision: Exact, + unit: b.value().unit, + ..AmountOfMoneyValue::default() + }) + }); + b.rule_3(" de ", // "une douzaine de dollars" + integer_check!(|integer: &IntegerValue| integer.group), + b.reg(r#"d[e']"#)?, + money_unit!(), + |a, _, b| { + Ok(AmountOfMoneyValue { + value: a.value().value as f32, + precision: Approximate, + unit: b.value().unit, + ..AmountOfMoneyValue::default() + }) + }); + b.rule_2("about ", + b.reg(r#"(?:autour|pas loin|pr[eè]s|aux alentours) d[e']|environ|presque|(?:approximative|quasi)ment"#)?, + amount_of_money_check!(), + |_, a| { + Ok(AmountOfMoneyValue { + precision: Approximate, + ..a.value().clone() + }) + }); + b.rule_2("exactly ", + b.reg(r#"(?:tr[eè]s )?exactement|pr[eé]cis[eé]ment|pile(?: poil)?"#)?, + amount_of_money_check!(), + |_, a| { + Ok(AmountOfMoneyValue { + precision: Exact, + ..a.value().clone() + }) + }); + b.rule_2("exactly ", + amount_of_money_check!(), + b.reg(r#"pile(?: poil)?|tout rond"#)?, + |a, _| { + Ok(AmountOfMoneyValue { + precision: Exact, + ..a.value().clone() + }) + } + ); + Ok(()) +} + +pub fn rules_temperature(b: &mut RuleSetBuilder) -> RustlingResult<()> { + b.rule_1("number as temp", + number_check!(), + |a| { + Ok(TemperatureValue { + value: a.value().value(), + unit: None, + latent: true, + }) + }); + b.rule_2(" degrees", + temperature_check!(), + b.reg(r#"(?:deg(?:r[éeè])?s?\.?)|°"#)?, + |a, _| { + Ok(TemperatureValue { + value: a.value().value, + unit: Some("degree"), + latent: false, + }) + }); + b.rule_2(" Celcius", + temperature_check!(), + b.reg(r#"centigrades?|c(?:el[cs]?(?:ius)?)?\.?"#)?, + |a, _| { + Ok(TemperatureValue { + value: a.value().value, + unit: Some("celsius"), + latent: false, + }) + }); + b.rule_2(" Fahrenheit", + temperature_check!(), + b.reg(r#"f(?:ah?reh?n(?:h?eit)?)?\.?"#)?, + |a, _| { + Ok(TemperatureValue { + value: a.value().value, + unit: Some("fahrenheit"), + latent: false, + }) + }); + b.rule_2(" Kelvin", + temperature_check!(), + b.reg(r#"k(?:elvin)?\.?"#)?, + |a, _| { + Ok(TemperatureValue { + value: a.value().value, + unit: Some("kelvin"), + latent: false, + }) + }); + b.rule_2(" en dessous de zero", + temperature_check!(|temp: &TemperatureValue| !temp.latent), + b.reg(r#"en dessous de (?:0|z[ée]ro)"#)?, + |a, _| { + Ok(TemperatureValue { + value: -1.0 * a.value().value, + latent: false, + ..*a.value() + }) + }); + Ok(()) +} \ No newline at end of file diff --git a/grammar/fr/src/rules_celebrations.rs b/grammar/fr/src/rules_celebrations.rs new file mode 100644 index 00000000..f2891a74 --- /dev/null +++ b/grammar/fr/src/rules_celebrations.rs @@ -0,0 +1,114 @@ +use rustling::*; +use rustling_ontology_values::dimension::*; +use rustling_ontology_values::helpers; +use rustling_ontology_moment::{Weekday, Grain}; + +pub fn rules_celebration(b: &mut RuleSetBuilder) -> RustlingResult<()> { + b.rule_1_terminal("noel", + b.reg(r#"(?:(?:le )?jour de )?no[eë]l"#)?, + |_| Ok(helpers::month_day(12, 25)?.form(Form::Celebration)) + ); + b.rule_1_terminal("soir de noël", + b.reg(r#"(?:l[ea] )?(?:soir(?:ée)?|veille|r[eé]veillon) de no[eë]l"#)?, + |_| { + let start = helpers::month_day(12, 24)?.intersect(&helpers::hour(18, false)?)?; + let end = helpers::month_day(12, 25)?.intersect(&helpers::hour(0, false)?)?; + Ok(start.span_to(&end, false)? + .form(Form::Celebration)) + } + ); + b.rule_1_terminal("saint sylvestre", + b.reg(r#"(?:l[ea] )?(?:saint[- ]sylvestre|r[eé]veillon)"#)?, + |_| Ok(helpers::month_day(12, 31)?.form(Form::Celebration)) + ); + b.rule_1_terminal("jour de l'an", + b.reg(r#"(?:le )?(?:jour de l'|nouvel )an"#)?, + |_| Ok(helpers::month_day(1, 1)?.form(Form::Celebration)) + ); + b.rule_1_terminal("toussaint", + b.reg(r#"(?:(?:la |la journée de la |jour de la )?toussaint|jour des morts)"#)?, + |_| Ok(helpers::month_day(11, 1)?.form(Form::Celebration)) + ); + b.rule_1_terminal("Armistice", + b.reg(r#"(?:pour )?l'armistice"#)?, + |_| Ok(helpers::month_day(11, 11)?.form(Form::Celebration)) + ); + b.rule_1_terminal("Saint Etienne (Alsace)", + b.reg(r#"(?:(?:le jour|la f[eê]te) de )?la (?:saint|st) [eé]tienne"#)?, + |_| Ok(helpers::month_day(12, 26)?.form(Form::Celebration)) + ); + b.rule_1_terminal("jeudi saint", + b.reg(r#"(?:le )?jeudi saint"#)?, + |_| Ok(helpers::cycle_nth_after(Grain::Day, -3, &helpers::easter()?)? + .form(Form::Celebration)) + ); + b.rule_1_terminal("vendredi saint", + b.reg(r#"(?:le )?vendredi saint"#)?, + |_| Ok(helpers::cycle_nth_after(Grain::Day, -2, &helpers::easter()?)? + .form(Form::Celebration)) + ); + b.rule_1_terminal("samedi saint", + b.reg(r#"(?:le )?samedi saint"#)?, + |_| Ok(helpers::cycle_nth_after(Grain::Day, -1, &helpers::easter()?)? + .form(Form::Celebration)) + ); + b.rule_1_terminal("pâques", + b.reg(r#"(?:la f[eê]te de |le jour de |le dimanche de )?p[âa]ques"#)?, + |_| Ok(helpers::easter()?.form(Form::Celebration)) + ); + b.rule_1_terminal("le lundi de pâques", + b.reg(r#"le lundi de p[âa]ques"#)?, + |_| Ok(helpers::cycle_nth_after(Grain::Day, 1, &helpers::easter()?)? + .form(Form::Celebration)) + ); + b.rule_1_terminal("ascension", + b.reg(r#"(?:la f[eê]te de l'|le jeudi de l'|l'|le jour de l')ascension"#)?, + |_| Ok(helpers::cycle_nth_after(Grain::Day, 39, &helpers::easter()?)? + .form(Form::Celebration)) + ); + b.rule_1_terminal("pentecôte", + b.reg(r#"(?:la f[eê]te de |(?:le )?lundi de )?(?:la )?pentec[oô]te"#)?, + |_| Ok(helpers::cycle_nth_after(Grain::Day, 49, &helpers::easter()?)? + .form(Form::Celebration)) + ); + b.rule_1_terminal("1er mai", + b.reg(r#"(?:la )?f(e|ê)te du travail"#)?, + |_| Ok(helpers::month_day(5, 1)?.form(Form::Celebration)) + ); + b.rule_1_terminal("fêtes des pères", + b.reg(r#"(?:la )?f[eê]te des p[eè]res"#)?, + |_| { + let sundays_of_june = helpers::month(6)?.intersect(&helpers::day_of_week(Weekday::Sun)?)?; + let second_week_of_june = helpers::cycle_nth_after(Grain::Week, 2, &helpers::month_day(6, 1)?)?; + Ok(sundays_of_june.intersect(&second_week_of_june)? // third sunday of June + .form(Form::Celebration)) + } + ); + b.rule_1_terminal("fêtes des mères", + b.reg(r#"(?:la )?f[eê]te des m[eè]res"#)?, + |_| { + // It is the last last sunday of may + // If it is the same day as the Pentecost, it is the first sunday of june + // This case is not supported for now + Ok(helpers::day_of_week(Weekday::Sun)?.last_of(&helpers::month(5)?)? + .form(Form::Celebration)) + } + ); + b.rule_1_terminal("fête nationale", + b.reg(r#"(?:la )?f[eê]te (?:nationale|du (?:14|quatorze) juillet)"#)?, + |_| Ok(helpers::month_day(7, 14)? + .form(Form::Celebration)) + ); + b.rule_1_terminal("assomption", + b.reg(r#"(?:la f[eê]te de |le jour de )?l'assomption"#)?, + |_| Ok(helpers::month_day(8, 15)? + .form(Form::Celebration)) + ); + b.rule_2("à ", + b.reg(r#"au|[aà](?:l['a])?"#)?, + datetime_check!(form!(Form::Celebration)), + |_, a| Ok(a.value().clone()) + ); + + Ok(()) +} \ No newline at end of file diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs new file mode 100644 index 00000000..93fb4ac5 --- /dev/null +++ b/grammar/fr/src/rules_datetime.rs @@ -0,0 +1,1443 @@ +use rustling::*; +use rustling_ontology_values::dimension::*; +use rustling_ontology_values::helpers; +use rustling_ontology_moment::{Weekday, Grain}; + + +pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { + b.rule_2("intersect", + datetime_check!(|datetime: &DatetimeValue| !datetime.latent), + datetime_check!(|datetime: &DatetimeValue| !datetime.latent), + |a, b| a.value().intersect(b.value()) + ); + b.rule_3("intersect by 'de' or ','", + datetime_check!(|datetime: &DatetimeValue| !datetime.latent), + b.reg(r#"de|,"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent), + |a, _, b| a.value().intersect(b.value()) + ); + b.rule_3("intersect by 'mais/par exemple/plutôt'", + datetime_check!(|datetime: &DatetimeValue| !datetime.latent), + b.reg(r#"mais|par exemple|plutôt|plutot"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent), + |a, _, b| a.value().intersect(b.value()) + ); + b.rule_2("en ", + b.reg(r#"en|au mois d[e']|du mois d[e']"#)?, + datetime_check!(form!(Form::Month(_))), + |_, a| Ok(a.value().clone()) + ); + b.rule_2("pour ", + b.reg(r#"pour"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), + |_, a| Ok(a.value().clone()) + ); + b.rule_1_terminal("named-day", + b.reg(r#"lun\.?(?:di)?"#)?, + |_| helpers::day_of_week(Weekday::Mon) + ); + b.rule_1_terminal("named-day", + b.reg(r#"mar\.?(?:di)?"#)?, + |_| helpers::day_of_week(Weekday::Tue) + ); + b.rule_1_terminal("named-day", + b.reg(r#"mer\.?(?:credi)?"#)?, + |_| helpers::day_of_week(Weekday::Wed) + ); + b.rule_1_terminal("named-day", + b.reg(r#"jeu\.?(?:di)?"#)?, + |_| helpers::day_of_week(Weekday::Thu) + ); + b.rule_1_terminal("named-day", + b.reg(r#"ven\.?(?:dredi)?"#)?, + |_| helpers::day_of_week(Weekday::Fri) + ); + b.rule_1_terminal("named-day", + b.reg(r#"sam\.?(?:edi)?"#)?, + |_| helpers::day_of_week(Weekday::Sat) + ); + b.rule_1_terminal("named-day", + b.reg(r#"dim\.?(?:anche)?"#)?, + |_| helpers::day_of_week(Weekday::Sun) + ); + b.rule_1_terminal("named-month", + b.reg(r#"janvier|janv\.?"#)?, + |_| helpers::month(1) + ); + b.rule_1_terminal("named-month", + b.reg(r#"fevrier|février|fev|fév\.?"#)?, + |_| helpers::month(2) + ); + b.rule_1_terminal("named-month", + b.reg(r#"mars|mar\.?"#)?, + |_| helpers::month(3) + ); + b.rule_1_terminal("named-month", + b.reg(r#"avril|avr\.?"#)?, + |_| helpers::month(4) + ); + b.rule_1_terminal("named-month", + b.reg(r#"mai"#)?, + |_| helpers::month(5) + ); + b.rule_1_terminal("named-month", + b.reg(r#"juin|jun\.?"#)?, + |_| helpers::month(6) + ); + b.rule_1_terminal("named-month", + b.reg(r#"juillet|juil?\."#)?, + |_| helpers::month(7) + ); + b.rule_1_terminal("named-month", + b.reg(r#"aout|août|aou\.?"#)?, + |_| helpers::month(8) + ); + b.rule_1_terminal("named-month", + b.reg(r#"septembre|sept\.|sep\.?"#)?, + |_| helpers::month(9) + ); + b.rule_1_terminal("named-month", + b.reg(r#"octobre|oct\.?"#)?, + |_| helpers::month(10) + ); + b.rule_1_terminal("named-month", + b.reg(r#"novembre|nov\.?"#)?, + |_| helpers::month(11) + ); + b.rule_1_terminal("named-month", + b.reg(r#"décembre|decembre|déc\.?|dec\.?"#)?, + |_| helpers::month(12) + ); + b.rule_1_terminal("maintenant", + b.reg(r#"maintenant|tout de suite|en ce moment"#)?, + |_| helpers::cycle_nth(Grain::Second, 0) + ); + b.rule_1_terminal("aujourd'hui", + b.reg(r#"(?:aujourd'? ?hui)|(?:ce jour)|(?:dans la journ[ée]e?)"#)?, + |_| helpers::cycle_nth(Grain::Day, 0) + ); + // FIXME: "le lendemain" interpreted as demain, not as relative to another date + // but there is a rule "le lendemain du " - inconsistent + b.rule_1_terminal("demain", + b.reg(r#"(?:demain)|(?:le lendemain)"#)?, + |_| helpers::cycle_nth(Grain::Day, 1) + ); + b.rule_1_terminal("hier", + b.reg(r#"hier|la veille"#)?, + |_| helpers::cycle_nth(Grain::Day, -1) + ); + b.rule_1_terminal("fin du mois", + b.reg(r#"(?:(?:[aà] )?la )?fin (?:du|de) mois"#)?, + |_| { + let month = helpers::cycle_nth(Grain::Month, 1)?; + Ok(helpers::cycle_nth_after(Grain::Day, -10, &month)? + .span_to(&month, false)? + .latent() + .form(Form::PartOfMonth)) + } + ); + b.rule_1_terminal("après-demain", + b.reg(r#"apr(?:e|è)s[- ]?demain"#)?, + |_| helpers::cycle_nth(Grain::Day, 2) + ); + b.rule_1_terminal("avant-hier", + b.reg(r#"avant[- ]?hier"#)?, + |_| helpers::cycle_nth(Grain::Day, -2) + ); + b.rule_2("le lendemain du ", + b.reg(r#"(?:le|au)? ?lendemain du"#)?, + datetime_check!(), + |_, datetime| helpers::cycle_nth_after_not_immediate(Grain::Day, 1, datetime.value()) + ); + b.rule_2("la veille du ", + b.reg(r#"(la )?veille du"#)?, + datetime_check!(), + |_, datetime| helpers::cycle_nth_after_not_immediate(Grain::Day, -1, datetime.value()) + ); + b.rule_2("ce ", + b.reg(r#"ce"#)?, + datetime_check!(form!(Form::DayOfWeek{..})), + |_, datetime| datetime.value().the_nth_not_immediate(0) + ); + b.rule_2("ce ", + b.reg(r#"ce"#)?, + datetime_check!(), + |_, datetime| Ok(datetime.value().the_nth(0)? + .datetime_kind(datetime.value().datetime_kind.clone())) + ); + b.rule_2(" prochain", + datetime_check!(form!(Form::DayOfWeek{..})), + b.reg(r#"prochain"#)?, + |datetime, _| datetime.value().the_nth_not_immediate(0) + ); + b.rule_2(" prochain", + datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), + b.reg(r#"prochaine?"#)?, + |datetime, _| datetime.value().the_nth_not_immediate(0) + ); + b.rule_2("au prochain ", + b.reg(r#"(au|[aà] la) prochaine?"#)?, + datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), + |_, datetime| datetime.value().the_nth_not_immediate(0) + ); + b.rule_2(" prochain", + // The direction check is to avoid application of datetime_check(month) on rule result + // "avant " + datetime_check!(|datetime: &DatetimeValue| form!(Form::Month(_))(datetime) && !datetime.direction.is_some()), + b.reg(r#"prochain"#)?, + |datetime, _| datetime.value().the_nth_not_immediate(0) + ); + b.rule_2(" suivant|d'après", + datetime_check!(), + b.reg(r#"suivante?s?|d'apr[eéè]s"#)?, + |datetime, _| datetime.value().the_nth(1) + ); + b.rule_2(" dernier|passé", + datetime_check!(), + b.reg(r#"derni[eéè]re?|pass[ée]e?"#)?, + |datetime, _| datetime.value().the_nth(-1) + ); + b.rule_2(" en huit", + datetime_check!(form!(Form::DayOfWeek{..})), + b.reg(r#"en (?:huit|8)"#)?, + |datetime, _| datetime.value().the_nth(1) + ); + b.rule_2(" en quinze", + datetime_check!(form!(Form::DayOfWeek{..})), + b.reg(r#"en (quinze|15)"#)?, + |datetime, _| datetime.value().the_nth(2) + ); + b.rule_4("dernier de (latent)", + b.reg(r#"derni[eéè]re?"#)?, + datetime_check!(form!(Form::DayOfWeek{..})), + b.reg(r#"d['e]"#)?, + datetime_check!(), + |_, dow, _, datetime| dow.value().last_of(datetime.value()) + ); + b.rule_4("dernier de (latent)", + b.reg(r#"derni[eéè]re?"#)?, + cycle_check!(), + b.reg(r#"d['e]"#)?, + datetime_check!(), + |_, cycle, _, datetime| cycle.value().last_of(datetime.value()) + ); + b.rule_4(" de ", + ordinal_check!(), // the first + datetime_check!(), // Thursday + b.reg(r#"d[e']"#)?, // of + datetime_check!(), // march + |ordinal, a, _, b| { + b.value().intersect(a.value())?.the_nth(ordinal.value().value - 1) + } + ); + b.rule_3(" week-end de ", + ordinal_check!(), + b.reg(r#"week(?:\s|-)?end (?:d['eu]|en|du mois de)"#)?, + datetime_check!(form!(Form::Month(_))), + |ordinal, _, datetime| { + let weekend = helpers::weekend()?; + let nth_week_end = datetime.value().intersect(&weekend)?; + nth_week_end.the_nth(ordinal.value().value - 1) + } + ); + b.rule_2("dernier week-end de ", + b.reg(r#"(?:le )?dernier week(?:\s|-)?end (?:du mois d[e']|d['eu]|en)"#)?, + datetime_check!(form!(Form::Month(_))), + |_, datetime| { + let weekend = helpers::weekend()?; + weekend.last_of(datetime.value()) + } + ); + // FIXME: change latency ranges for years? E.g. latent until 1900? + b.rule_1("year", + integer_check_by_range!(1000, 2100), + |integer| helpers::year(integer.value().value as i32) + ); + b.rule_1("year (latent)", + integer_check_by_range!(-1000, 999), + |integer| Ok(helpers::year(integer.value().value as i32)?.latent()) + ); + b.rule_2("l'année ", + b.reg(r#"l[' ]an(?:n[eé]+)?"#)?, + integer_check!(), + |_, integer| helpers::year(integer.value().value as i32) + ); + b.rule_2("en ", + b.reg(r#"en"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::Year(_))(datetime)), + |_, year| Ok(year.value().clone()) + ); + b.rule_1("year (latent)", + integer_check_by_range!(2101, 3000), + |integer| Ok(helpers::year(integer.value().value as i32)?.latent()) + ); + b.rule_1_terminal("day of month (premier)", + b.reg(r#"premier|prem\.?|1er|1 er"#)?, + |_| helpers::day_of_month(1) + ); + b.rule_2("le (non ordinal)", + b.reg(r#"le"#)?, + integer_check_by_range!(1, 31), + |_, integer| helpers::day_of_month(integer.value().value as u32) + ); + b.rule_4("le à ", + b.reg(r#"le"#)?, + integer_check_by_range!(1, 31), + b.reg(r#"[aà]"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(_))(datetime)), + |_, integer, _, datetime| { + let day_of_month = helpers::day_of_month(integer.value().value as u32)?; + day_of_month.intersect(&datetime.value()) + } + ); + b.rule_2(" ", + integer_check_by_range!(1, 31), + datetime_check!(form!(Form::Month(_))), + |integer, month| Ok(month.value() + .intersect(&helpers::day_of_month(integer.value().value as u32)?)? + .form(Form::DayOfMonth)) + ); + b.rule_2(" ", + datetime_check!(form!(Form::DayOfWeek{..})), // Weird it is not used in the production of the rule + integer_check_by_range!(1, 31), + |_, integer| helpers::day_of_month(integer.value().value as u32) + ); + b.rule_4(" à )", + datetime_check!(form!(Form::DayOfWeek{..})), // Weird it is not used in the production of the rule + integer_check_by_range!(1, 31), + b.reg(r#"[aà]"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(_))(datetime)), + |_, integer, _, tod| helpers::day_of_month(integer.value().value as u32) + ?.intersect(tod.value()) + ); + b.rule_1(" (latent)", + integer_check_by_range!(1, 23), + |integer| Ok(helpers::hour(integer.value().value as u32, integer.value().value < 12)?.latent()) + ); + b.rule_1(" (latent)", + integer_check_by_range!(0, 0), + |_| Ok(helpers::hour(0, false)?.latent()) + ); + b.rule_1_terminal("midi", + b.reg(r#"midi(?: pile| exactement| pr[eé]cises)?"#)?, + |_| helpers::hour(12, false) + ); + b.rule_1_terminal("minuit", + b.reg(r#"minuit(?: pile| exactement| pr[eé]cises)?"#)?, + |_| helpers::hour(0, false) + ); + b.rule_2(" heures", + datetime_check!(form!(Form::TimeOfDay(TimeOfDayForm::Hour { .. }))), + b.reg(r#"h\.?(?:eure)?s?(?: pile| exactement| pr[eé]cises?)?"#)?, + |a, _| Ok(a.value().clone().not_latent()) + ); + b.rule_1_terminal("quart (relative minutes)", + b.reg(r#"(?:un )?quart"#)?, + |_| Ok(RelativeMinuteValue(15)) + ); + b.rule_1_terminal("demi (relative minutes)", + b.reg(r#"demie?"#)?, + |_| Ok(RelativeMinuteValue(30)) + ); + b.rule_1_terminal("trois quarts (relative minutes)", + b.reg(r#"(?:3|trois) quarts?"#)?, + |_| Ok(RelativeMinuteValue(45)) + ); + b.rule_1("number (as relative minutes)", + integer_check_by_range!(1, 59), + |a| Ok(RelativeMinuteValue(a.value().value as i32)) + ); + b.rule_2("number minutes (as relative minutes)", + integer_check_by_range!(1, 59), + b.reg(r#"min\.?(?:ute)?s?"#)?, + |a, _| Ok(RelativeMinuteValue(a.value().value as i32)) + ); + b.rule_2(" (as relative minutes)", + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(TimeOfDayForm::Hour { .. }))(datetime)), + relative_minute_check!(), + |datetime, minutes| helpers::hour_relative_minute( + datetime.value().form_time_of_day()?.full_hour(), + minutes.value().0, + datetime.value().form_time_of_day()?.is_12_clock() + ) + ); + b.rule_3(" (as relative minutes) exactly", + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(TimeOfDayForm::Hour { .. }))(datetime)), + relative_minute_check!(), + b.reg(r#"pile|exactement|pr[ée]cises?"#)?, + |datetime, minutes, _| helpers::hour_relative_minute( + datetime.value().form_time_of_day()?.full_hour(), + minutes.value().0, + datetime.value().form_time_of_day()?.is_12_clock() + ) + ); + b.rule_3(" moins (as relative minutes)", + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(TimeOfDayForm::Hour { .. }))(datetime)), + b.reg(r#"moins(?: le)?"#)?, + relative_minute_check!(), + |datetime, _, minutes| helpers::hour_relative_minute( + datetime.value().form_time_of_day()?.full_hour(), + -1 * minutes.value().0, + datetime.value().form_time_of_day()?.is_12_clock() + ) + ); + b.rule_4(" moins (as relative minutes) exactly ", + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(TimeOfDayForm::Hour { .. }))(datetime)), + b.reg(r#"moins(?: le)?"#)?, + relative_minute_check!(), + b.reg(r#"pile|exactement|pr[ée]cises?"#)?, + |datetime, _, minutes, _| helpers::hour_relative_minute( + datetime.value().form_time_of_day()?.full_hour(), + -1 * minutes.value().0, + datetime.value().form_time_of_day()?.is_12_clock() + ) + ); + b.rule_3(" et|passé de ", + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(TimeOfDayForm::Hour { .. }))(datetime)), + b.reg(r#"et|pass[ée]e?s? de"#)?, + relative_minute_check!(), + |datetime, _, minutes| helpers::hour_relative_minute( + datetime.value().form_time_of_day()?.full_hour(), + minutes.value().0, + datetime.value().form_time_of_day()?.is_12_clock() + ) + ); + b.rule_4(" et|passé de exactly", + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(TimeOfDayForm::Hour { .. }))(datetime)), + b.reg(r#"et|pass[ée]e?s? de"#)?, + relative_minute_check!(), + b.reg(r#"pile|exactement|pr[ée]cises?"#)?, + |datetime, _, minutes, _| helpers::hour_relative_minute( + datetime.value().form_time_of_day()?.full_hour(), + minutes.value().0, + datetime.value().form_time_of_day()?.is_12_clock() + ) + ); + b.rule_4(" de exactly", + datetime_check!(form!(Form::TimeOfDay(_))), + b.reg(r#"(?:d[eu] |dans )(?:l[ 'a])?"#)?, + datetime_check!(form!(Form::PartOfDay(_))), + b.reg(r#"pile|exactement|pr[eé]cises?"#)?, + |a, _, b, _| Ok(a.value().intersect(b.value())?.form(a.value().form.clone())) + ); + // Adding "pour" here makes time-of-day ambiguous w/ Duration + // Duration has less priority than Datetime types, therefore duration will be output only + // if the output kind filter is set for Duration + b.rule_2("à ", + b.reg(r#"[aà]|pour"#)?, + datetime_check!(form!(Form::TimeOfDay(_))), + |_, a| Ok(a.value().clone().not_latent()) + ); + b.rule_2("vers ", + b.reg(r#"(?:plut[ôo]t )?(?:vers|autour de|[aà] environ|aux alentours de)"#)?, + datetime_check!(form!(Form::TimeOfDay(_))), + |_, a| Ok(a.value().clone().not_latent().precision(Precision::Approximate)) + ); + // Written time/date in numeric formats + b.rule_1_terminal("hh(:|h)mm (time-of-day)", + b.reg(r#"((?:[01]?\d)|(?:2[0-3]))[:h]([0-5]\d)"#)?, + |text_match| { + let hour: u32 = text_match.group(1).parse()?; + let minute: u32 = text_match.group(2).parse()?; + helpers::hour_minute(hour, minute, hour < 12) + } + ); + b.rule_3_terminal("hh(:|h)mm - hh(:|h)mm (time-of-day interval)", + b.reg(r#"((?:[01]?\d)|(?:2[0-3]))[:h]([0-5]\d)"#)?, + b.reg(r#" ?\- ?"#)?, + b.reg(r#"((?:[01]?\d)|(?:2[0-3]))[:h]([0-5]\d)"#)?, + |a, _, b| { + let hour_start: u32 = a.group(1).parse()?; + let minute_start: u32 = a.group(2).parse()?; + let hour_end: u32 = b.group(1).parse()?; + let minute_end: u32 = b.group(2).parse()?; + let start = helpers::hour_minute(hour_start, minute_start, hour_start < 12)?; + let end = helpers::hour_minute(hour_end, minute_end, hour_end < 12)?; + start.smart_span_to(&end, false) + } + ); + b.rule_1_terminal("hh:mm:ss", + b.reg(r#"((?:[01]?\d)|(?:2[0-3]))[:.]([0-5]\d)[:.]([0-5]\d)"#)?, + |text_match| helpers::hour_minute_second( + text_match.group(1).parse()?, + text_match.group(2).parse()?, + text_match.group(3).parse()?, + false + ) + + ); + b.rule_1_terminal("hhmm (military time-of-day)", + b.reg(r#"((?:[01]\d)|(?:2[0-3]))([0-5]\d)"#)?, + |text_match| Ok(helpers::hour_minute( + text_match.group(1).parse()?, + text_match.group(2).parse()?, + false + )?.latent()) + ); + b.rule_1_terminal("yyyy-mm-dd - ISO", + b.reg(r#"(\d{4})[-/](0?[1-9]|1[0-2])[-/](3[01]|[12]\d|0?[1-9])"#)?, + |text_match| helpers::year_month_day( + text_match.group(1).parse()?, + text_match.group(2).parse()?, + text_match.group(3).parse()?) + ); + // Supporting these date formats also with whitespace as a separator for legacy + // But this seems too permissive? + b.rule_1_terminal("dd/mm/yy or dd/mm/yyyy", + b.reg(r#"(0?[1-9]|[12]\d|3[01])[-\./ ](0?[1-9]|1[0-2])[-\./ ](\d{2,4})"#)?, + |text_match| helpers::year_month_day( + text_match.group(3).parse()?, + text_match.group(2).parse()?, + text_match.group(1).parse()?, + ) + ); + b.rule_1_terminal("dd/mm", + b.reg(r#"(0?[1-9]|[12]\d|3[01])[\./ ](1[0-2]|0?[1-9])"#)?, + |text_match| helpers::month_day( + text_match.group(2).parse()?, + text_match.group(1).parse()?) + ); + // End of Written time/date in numeric formats + b.rule_1_terminal("matin", + b.reg(r#"mat(?:in[ée]?e?)?"#)?, + |_| Ok(helpers::hour(4, false)? + .span_to(&helpers::hour(12, false)?, false)? + .latent() + .form(Form::PartOfDay(PartOfDayForm::Morning))) + ); + b.rule_1_terminal("début de matinée", + b.reg(r#"(?:le matin (?:tr[eè]s )?t[ôo]t|(?:tr[eè]s )?t[ôo]t le matin|d[ée]but de matin[ée]e)"#)?, + |_| Ok(helpers::hour(4, false)? + .span_to(&helpers::hour(9, false)?, false)? + .latent() + .form(Form::PartOfDay(PartOfDayForm::Morning))) + ); + b.rule_1_terminal("petit dejeuner", + b.reg(r#"petit[- ]d[ée]jeuner"#)?, + |_| Ok(helpers::hour(5, false)? + .span_to(&helpers::hour(10, false)?, false)? + .latent() + .form(Form::Meal)) + ); + b.rule_1_terminal("milieu de matinée", + b.reg(r#"milieu de matin[ée]e"#)?, + |_| Ok(helpers::hour(9, false)? + .span_to(&helpers::hour(11, false)?, false)? + .latent() + .form(Form::PartOfDay(PartOfDayForm::Morning))) + ); + b.rule_1_terminal("brunch", + b.reg(r#"brunch"#)?, + |_| Ok(helpers::hour(10, false)? + .span_to(&helpers::hour(15, false)?, false)? + .latent() + .form(Form::Meal)) + ); + b.rule_1_terminal("fin de matinée", + b.reg(r#"fin de matin[ée]e"#)?, + |_| Ok(helpers::hour(10, false)? + .span_to(&helpers::hour(12, false)?, false)? + .latent() + .form(Form::PartOfDay(PartOfDayForm::Morning))) + ); + b.rule_1_terminal("déjeuner", + b.reg(r#"d[eéè]jeuner"#)?, + |_| Ok(helpers::hour(12, false)? + .span_to(&helpers::hour(14, false)?, false)? + .latent() + .form(Form::Meal)) + ); + b.rule_1_terminal("après le déjeuner", + b.reg(r#"apr[eè]s (?:le )?d[eéè]jeuner"#)?, + |_| { + let period = helpers::hour(13, false)? + .span_to(&helpers::hour(17, false)?, false)?; + Ok(helpers::cycle_nth(Grain::Day, 0)?.intersect(&period)?.form(Form::PartOfDay(PartOfDayForm::Afternoon))) + } + ); + b.rule_1_terminal("avant le déjeuner", + b.reg(r#"avant (?:le )?d[eéè]jeuner"#)?, + |_| { + let period = helpers::hour(10, false)? + .span_to(&helpers::hour(12, false)?, false)?; + Ok(helpers::cycle_nth(Grain::Day, 0)?.intersect(&period)?.form(Form::PartOfDay(PartOfDayForm::Morning))) + } + ); + b.rule_1_terminal("avant le travail", + b.reg(r#"avant le travail"#)?, + |_| { + let period = helpers::hour(7, false)? + .span_to(&helpers::hour(10, false)?, false)?; + Ok(helpers::cycle_nth(Grain::Day, 0)?.intersect(&period)?.form(Form::PartOfDay(PartOfDayForm::Morning))) + } + ); + b.rule_1_terminal("pendant le travail", + b.reg(r#"pendant le travail"#)?, + |_| { + let period = helpers::hour(9, false)? + .span_to(&helpers::hour(19, false)?, false)?; + Ok(helpers::cycle_nth(Grain::Day, 0)?.intersect(&period)?.form(Form::PartOfDay(PartOfDayForm::None))) + } + ); + b.rule_1_terminal("après le travail", + b.reg(r#"apr[eè]s (?:le )?travail"#)?, + |_| { + let period = helpers::hour(17, false)? + .span_to(&helpers::hour(21, false)?, false)?; + Ok(helpers::cycle_nth(Grain::Day, 0)?.intersect(&period)?.form(Form::PartOfDay(PartOfDayForm::Evening))) + } + ); + b.rule_1_terminal("après-midi", + b.reg(r#"apr[eéè]s?[ \-]?midi|aprem"#)?, + |_| { + Ok(helpers::hour(12, false)? + .span_to(&helpers::hour(19, false)?, false)? + .latent() + .form(Form::PartOfDay(PartOfDayForm::Afternoon))) + } + ); + b.rule_1_terminal("début d'après-midi", + b.reg(r#"d[ée]but (?:d'|de l')(?:apr[eéè]s?[ \-]?midi|aprem)"#)?, + |_| { + Ok(helpers::hour(12, false)? + .span_to(&helpers::hour(15, false)?, false)? + .latent() + .form(Form::PartOfDay(PartOfDayForm::Afternoon))) + } + ); + b.rule_1_terminal("milieu d'après-midi", + b.reg(r#"milieu (?:d'|de l')(?:apr[eéè]s?[ \-]?midi|aprem)"#)?, + |_| { + Ok(helpers::hour(15, false)? + .span_to(&helpers::hour(17, false)?, false)? + .latent() + .form(Form::PartOfDay(PartOfDayForm::Afternoon))) + } + ); + b.rule_1_terminal("gouter", + b.reg(r#"(?:(?:[àa] )?l[' ]heure du|au moment du|pendant le|pour le) go[uû]ter"#)?, + |_| Ok(helpers::hour(16, false)? + .span_to(&helpers::hour(18, false)?, false)? + .form(Form::Meal)) + ); + b.rule_1_terminal("thé", + b.reg(r#"(?:(?:[àa] )?l[' ]heure du|au moment du|pendant le|pour le) th[eé]"#)?, + |_| Ok(helpers::hour(15, false)? + .span_to(&helpers::hour(17, false)?, false)? + .form(Form::Meal)) + ); + b.rule_1_terminal("cafe", + b.reg(r#"(?:(?:[àa] )?l[' ]heure du|au moment du|pendant le|pour le) caf[eé]"#)?, + |_| Ok(helpers::hour(14, false)? + .span_to(&helpers::hour(16, false)?, false)? + .form(Form::Meal)) + ); + b.rule_1_terminal("fin d'après-midi", + b.reg(r#"fin (?:d'|de l')(?:apr[eéè]s?[ \-]?midi|aprem)"#)?, + |_| { + Ok(helpers::hour(17, false)? + .span_to(&helpers::hour(19, false)?, false)? + .latent() + .form(Form::PartOfDay(PartOfDayForm::Afternoon))) + } + ); + // TODO: APERO + b.rule_1_terminal("début de journée", + b.reg(r#"d[ée]but de (?:la )?journ[ée]e"#)?, + |_| { + Ok(helpers::hour(6, false)? + .span_to(&helpers::hour(10, false)?, false)? + .latent() + .form(Form::PartOfDay(PartOfDayForm::Morning))) + } + ); + b.rule_1_terminal("milieu de journée", + b.reg(r#"milieu de (?:la )?journ[ée]e"#)?, + |_| { + Ok(helpers::hour(11, false)? + .span_to(&helpers::hour(16, false)?, false)? + .latent() + .form(Form::PartOfDay(PartOfDayForm::None))) + } + ); + b.rule_1_terminal("fin de journée", + b.reg(r#"fin de (?:la )?journ[ée]e"#)?, + |_| { + Ok(helpers::hour(17, false)? + .span_to(&helpers::hour(21, false)?, false)? + .latent() + .form(Form::PartOfDay(PartOfDayForm::Evening))) + } + ); + b.rule_1_terminal("soir", + b.reg(r#"soir[ée]?e?"#)?, + |_| { + Ok(helpers::hour(18, false)? + .span_to(&helpers::hour(0, false)?, false)? + .latent() + .form(Form::PartOfDay(PartOfDayForm::Evening))) + } + ); + b.rule_1_terminal("début de soirée", + b.reg(r#"d[ée]but de (?:la )?soir[ée]e?"#)?, + |_| { + Ok(helpers::hour(18, false)? + .span_to(&helpers::hour(21, false)?, false)? + .latent() + .form(Form::PartOfDay(PartOfDayForm::Evening))) + } + ); + b.rule_1_terminal("fin de soirée", + b.reg(r#"fin de (?:la )?soir[ée]e?"#)?, + |_| { + Ok(helpers::hour(21, false)? + .span_to(&helpers::hour(0, false)?, false)? + .latent() + .form(Form::PartOfDay(PartOfDayForm::Evening))) + } + ); + b.rule_1_terminal("diner", + b.reg(r#"d[iî]ner|souper"#)?, + |_| Ok(helpers::hour(18, false)? + .span_to(&helpers::hour(23, false)?, false)? + .form(Form::Meal)) + ); + b.rule_1_terminal("nuit", + b.reg(r#"nuit"#)?, + |_| { + Ok(helpers::hour(22, false)? + .span_to(&helpers::hour(6, false)?, false)? + .latent() + .form(Form::PartOfDay(PartOfDayForm::Night))) + } + ); + b.rule_2("a l'heure de ", + b.reg(r#"(?:[àa] )?l[' ]heure du|au moment du|pendant l[ea']|au|pour l[ea']|l[ea']"#)?, + datetime_check!(|datetime: &DatetimeValue| form!(Form::Meal)(datetime)), + |_, a| Ok(a.value().clone().not_latent()) + ); + b.rule_2(" ", + datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), + datetime_check!(form!(Form::Meal)), + |a, b| a.value().intersect(b.value()) + ); + b.rule_2("prep? & article ", // This is very catch-all/junky + b.reg(r#"(?:pendant |durant |dans |d[eè]s )?l[ae']?|en|au"#)?, + datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime)), + |_, a| Ok(a.value().clone().not_latent()) + ); + b.rule_2("ce ", + b.reg(r#"cet?t?e?"#)?, + datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), + |_, datetime| Ok(helpers::cycle_nth(Grain::Day, 0)? + .intersect(datetime.value())? + .form(datetime.value().form.clone()) + .datetime_kind(DatetimeKind::DatetimeComplement { date_and_time: true, today: true })) + ); + b.rule_2(" ", + datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), + datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), + |a, b| a.value().intersect(b.value()) + ); + b.rule_2(" du matin", + datetime_check!(form!(Form::TimeOfDay(_))), + b.reg(r#"(?:(?:du|dans|de) )?(?:(?:au|le|la) )?mat(?:in[ée]?e?)?(?: pile| exactement| pr[eé]cises?)?"#)?, + |a, _| { + let period = helpers::hour(0, false)? + .span_to(&helpers::hour(12, false)?, false)?; + Ok(a.value().intersect(&period)?.form(a.value().form.clone())) + } + ); + b.rule_2(" de l'apres-midi", + datetime_check!(form!(Form::TimeOfDay(_))), + b.reg(r#"(?:dans |de )?l[' ]apr[eè]s[\- ]midi(?: pile| exactement| pr[eé]cises?)?"#)?, + |a, _| { + let period = helpers::hour(12, false)? + .span_to(&helpers::hour(19, false)?, false)?; + Ok(a.value().intersect(&period)?.form(a.value().form.clone())) + } + ); + b.rule_2(" du soir", + datetime_check!(form!(Form::TimeOfDay(_))), + b.reg(r#"(?:(?:du|dans|de) )?(?:(?:au|le|la) )?soir[ée]?e?(?: pile| exactement| pr[eé]cises?)?"#)?, + |a, _| { + let period = helpers::hour(16, false)? + .span_to(&helpers::hour(0, false)?, false)?; + Ok(a.value().intersect(&period)?.form(a.value().form.clone())) + } + ); + b.rule_3(" du ", + datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), + b.reg(r#"du"#)?, + datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), + |a, _, b| b.value().intersect(a.value()) + ); + b.rule_1_terminal("(ce/le) week-end", + b.reg(r#"(?:[cl]e )?week(?:\s|-)?end"#)?, + |_| helpers::weekend() + ); + b.rule_1_terminal("le week-end dernier", + b.reg(r#"le week(?:\s|-)?end dernier"#)?, + |_| { + let weekend = helpers::weekend()?; + Ok(weekend.the_nth(-1)?.datetime_kind(DatetimeKind::DatePeriod)) + } + ); + b.rule_1_terminal("le week-end prochain", + b.reg(r#"le week(?:\s|-)?end prochain|le prochain week(?:\s|-)?end"#)?, + |_| { + let weekend = helpers::weekend()?; + Ok(weekend.the_nth(1)?.datetime_kind(DatetimeKind::DatePeriod)) + } + ); + b.rule_1_terminal("début de semaine", + b.reg(r#"(?:en |au )?d[ée]but de (?:cette |la )?semaine"#)?, + |_| helpers::day_of_week(Weekday::Mon) + ?.span_to(&helpers::day_of_week(Weekday::Tue)?, false) + ); + b.rule_1_terminal("milieu de semaine", + b.reg(r#"(?:en |au )?milieu de (?:cette |la )?semaine"#)?, + |_| helpers::day_of_week(Weekday::Wed) + ?.span_to(&helpers::day_of_week(Weekday::Thu)?, false) + ); + b.rule_1_terminal("fin de semaine (Warning: this is the weekend in Quebec)", + b.reg(r#"(?:en |à la )?fin de (?:cette |la )?semaine"#)?, + |_| helpers::day_of_week(Weekday::Thu) + ?.span_to(&helpers::day_of_week(Weekday::Sun)?, false) + ); + b.rule_1_terminal("en semaine", + b.reg(r#"(?:pendant la |en )semaine"#)?, + |_| helpers::day_of_week(Weekday::Mon) + ?.span_to(&helpers::day_of_week(Weekday::Fri)?, false) + ); + b.rule_1_terminal("season", + b.reg(r#"(?:cet )?(?:été|ete)"#)?, + |_| helpers::month_day(6, 21)?.span_to(&helpers::month_day(9, 23)?, false) + ); + b.rule_1_terminal("season", + b.reg(r#"(?:cet )?automne"#)?, + |_| helpers::month_day(9, 23)?.span_to(&helpers::month_day(12, 21)?, false) + ); + b.rule_1_terminal("season", + b.reg(r#"(?:cet )?hiver"#)?, + |_| helpers::month_day(12, 21)?.span_to(&helpers::month_day(3, 20)?, false) + ); + b.rule_1_terminal("season", + b.reg(r#"(?:ce )?printemps"#)?, + |_| helpers::month_day(3, 20)?.span_to(&helpers::month_day(6, 21)?, false) + ); + b.rule_2("le ", + b.reg(r#"l[ea]"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent), + |_, a| Ok(a.value().clone()) + ); + b.rule_4("dd-dd (interval)", + b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, + b.reg(r#"\-|(?:jusqu')?au"#)?, + b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, + datetime_check!(form!(Form::Month(_))), + |a, _, b, month| { + let start = month.value().intersect(&helpers::day_of_month(a.group(1).parse()?)?)?; + let end = month.value().intersect(&helpers::day_of_month(b.group(1).parse()?)?)?; + start.span_to(&end, true) + } + ); + b.rule_4("-dd (interval)", + datetime_check!(), + b.reg(r#"\-|(?:jusqu')?au"#)?, + b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, + datetime_check!(form!(Form::Month(_))), + |datetime, _, text_match, month| { + let start = month.value().intersect(datetime.value())?; + let end = month.value().intersect(&helpers::day_of_month(text_match.group(1).parse()?)?)?; + start.span_to(&end, true) + } + ); + b.rule_5("- dd (interval)", + datetime_check!(), + b.reg(r#"\-|(?:jusqu')?au"#)?, + datetime_check!(form!(Form::DayOfWeek{..})), + b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, + datetime_check!(form!(Form::Month(_))), + |datetime, _, _, text_match, month| { + let start = month.value().intersect(datetime.value())?; + let end = month.value().intersect(&helpers::day_of_month(text_match.group(1).parse()?)?)?; + start.span_to(&end, true) + } + ); + b.rule_6(" 1er- dd (interval)", + datetime_check!(form!(Form::DayOfWeek{..})), + b.reg(r#"premier|prem\.?|1er|1 er"#)?, + b.reg(r#"\-|(?:jusqu')?au"#)?, + datetime_check!(form!(Form::DayOfWeek{..})), + b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, + datetime_check!(form!(Form::Month(_))), + |_, _, _, _, text_match, month| { + let start = month.value().intersect(&helpers::day_of_month(1)?)?; + let end = month.value().intersect(&helpers::day_of_month(text_match.group(1).parse()?)?)?; + start.span_to(&end, true) + } + ); + b.rule_6("du dd- dd (interval)", + b.reg(r#"du"#)?, + b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, + b.reg(r#"\-|(?:jusqu')?au"#)?, + datetime_check!(form!(Form::DayOfWeek{..})), + b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, + datetime_check!(form!(Form::Month(_))), + |_, a, _, _, b, month| { + let start = month.value().intersect(&helpers::day_of_month(a.group(1).parse()?)?)?; + let end = month.value().intersect(&helpers::day_of_month(b.group(1).parse()?)?)?; + start.span_to(&end, true) + } + ); + b.rule_6("du dd- dd (interval)", + b.reg(r#"du"#)?, + datetime_check!(), + b.reg(r#"\-|(?:jusqu')?au"#)?, + datetime_check!(form!(Form::DayOfWeek{..})), + b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, + datetime_check!(form!(Form::Month(_))), + |_, datetime, _, _, text_match, month| { + let start = month.value().intersect(datetime.value())?; + let end = month.value().intersect(&helpers::day_of_month(text_match.group(1).parse()?)?)?; + start.span_to(&end, true) + } + ); + b.rule_4("la nuit ", + b.reg(r#"(dans|pendant|durant) la nuit (?:du|de)"#)?, + datetime_check!(form!(Form::DayOfWeek{..})), + b.reg(r#"\-|(?:jusqu')?au"#)?, + datetime_check!(form!(Form::DayOfWeek{..})), + |_, start, _, end| { + let start = start.value().intersect(&helpers::hour(22, false)?)?; + let end = end.value().intersect(&helpers::hour(6, false)?)?; + start.span_to(&end, false) + } + ); + b.rule_5("entre dd et dd (interval)", + b.reg(r#"entre(?: le)?"#)?, + b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, + b.reg(r#"et(?: le)?"#)?, + b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, + datetime_check!(form!(Form::Month(_))), + |_, a, _, b, month| { + let start = month.value().intersect(&helpers::day_of_month(a.group(1).parse()?)?)?; + let end = month.value().intersect(&helpers::day_of_month(b.group(1).parse()?)?)?; + start.span_to(&end, true) + } + ); + b.rule_4_terminal("du dd au dd(interval)", + b.reg(r#"du"#)?, + b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, + b.reg(r#"(?:jusqu')?au"#)?, + b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, + |_, a, _, b| { + let start = helpers::day_of_month(a.group(1).parse()?)?; + let end = helpers::day_of_month(b.group(1).parse()?)?; + start.span_to(&end, true) + } + ); + b.rule_2("fin (interval)", + b.reg(r#"fin(?: du mois d[e']? ?)?"#)?, + datetime_check!(form!(Form::Month(_))), + |_, month| { + let start = month.value().intersect(&helpers::day_of_month(25)?)?; + let end = helpers::cycle(Grain::Day)?.last_of(month.value())?; + start.span_to(&end, true) + } + ); + b.rule_2("début (interval)", + b.reg(r#"d[ée]but(?: du mois d[e'] ?)?"#)?, + datetime_check!(form!(Form::Month(_))), + |_, month| { + let start = month.value().intersect(&helpers::day_of_month(1)?)?; + let end = month.value().intersect(&helpers::day_of_month(5)?)?; + start.span_to(&end, true) + } + ); + b.rule_2("première quinzaine de (interval)", + b.reg(r#"(?:premi[èe]re|1 ?[èe]re) (?:quinzaine|15 ?aine) d[e']"#)?, + datetime_check!(form!(Form::Month(_))), + |_, month| { + let start = month.value().intersect(&helpers::day_of_month(1)?)?; + let end = month.value().intersect(&helpers::day_of_month(14)?)?; + start.span_to(&end, true) + } + ); + b.rule_2("deuxième quinzaine de (interval)", + b.reg(r#"(?:deuxi[èe]me|2 ?[èe]me) (?:quinzaine|15 ?aine) d[e']"#)?, + datetime_check!(form!(Form::Month(_))), + |_, month| { + let start = month.value().intersect(&helpers::day_of_month(15)?)?; + let end = helpers::cycle(Grain::Day)?.last_of(month.value())?; + start.span_to(&end, true) + } + ); + b.rule_2("", + b.reg(r#"mi[- ]"#)?, + datetime_check!(form!(Form::Month(_))), + |_, month| { + let start = month.value().intersect(&helpers::day_of_month(10)?)?; + let end = month.value().intersect(&helpers::day_of_month(19)?)?; + start.span_to(&end, true) + } + ); + + /* Intervals */ + b.rule_3(" - (interval)", + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), + b.reg(r#" \- |(?:jusqu')?(?:au|[aà])"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), + |a, _, b| a.value().span_to(b.value(), true) + ); + b.rule_3(" avant (interval)", + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), + b.reg(r#"jusqu'(?:au|[aà])|avant"#)?, + datetime_check!(form!(Form::TimeOfDay(_))), + |a, _, b| a.value().span_to(b.value(), false) + ); + b.rule_4("de - (interval)", + b.reg(r#"depuis|d[e'u]?"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), + b.reg(r#" \- |(?:jusqu')?(?:au|[aà])"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), + |_, a, _, b| a.value().span_to(b.value(), true) + ); + b.rule_4("entre et (interval)", + b.reg(r#"entre"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), + b.reg(r#"et"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), + |_, a, _, b| a.value().span_to(b.value(), true) + ); + // Specific case with years + b.rule_5("de - (interval)", + b.reg(r#"depuis|d[e'u]?"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), + b.reg(r#" \- |(?:jusqu')?(?:au|[aà])"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime) && datetime.is_coarse_grain_greater_than(Grain::Year)), + datetime_check!(form!(Form::Year(_))), + |_, a, _, b, year| a.value().span_to(b.value(), true)?.intersect(year.value()) + ); + b.rule_5("entre et (interval)", + b.reg(r#"entre"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), + b.reg(r#"et"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime) && datetime.is_coarse_grain_greater_than(Grain::Year)), + datetime_check!(form!(Form::Year(_))), + |_, a, _, b, year| a.value().span_to(b.value(), true)?.intersect(year.value()) + ); + b.rule_3(" - (interval)", + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(_))(datetime)), + b.reg(r#" \- |(?:jusqu')?(?:au|[aà])"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(_))(datetime)), + |a, _, b| a.value().smart_span_to(b.value(), false) + ); + b.rule_4("de - (interval)", + b.reg(r#"(?:[aà] partir )?d['e]"#)?, + datetime_check!(form!(Form::TimeOfDay(_))), + b.reg(r#"(?:jusqu')?(?:au|[aà])"#)?, + datetime_check!(form!(Form::TimeOfDay(_))), + |_, a, _, b| a.value().smart_span_to(b.value(), false) + ); + b.rule_2("de maintenant - (interval)", + b.reg(r#"(?:[aà] partir )?de maintenant (?:jusqu')?(?:au|[aà])"#)?, + datetime_check!(form!(Form::TimeOfDay(_))), + |_, a| helpers::cycle_nth(Grain::Second, 0)?.smart_span_to(a.value(), false) + ); + b.rule_3("de - maintenant (interval)", + b.reg(r#"(?:[aà] partir )?d['e]"#)?, + datetime_check!(form!(Form::TimeOfDay(_))), + b.reg(r#"(?:jusqu')?(?:au|[aà]) maintenant"#)?, + |_, a, _| { + let now = helpers::cycle_nth(Grain::Second, 0)?; + a.value().smart_span_to(&now, false) + } + ); + b.rule_4("entre et (interval)", + b.reg(r#"entre"#)?, + datetime_check!(form!(Form::TimeOfDay(_))), + b.reg(r#"et"#)?, + datetime_check!(form!(Form::TimeOfDay(_))), + |_, a, _, b| a.value().smart_span_to(b.value(), false) + ); + b.rule_2("jusqu'à ", + b.reg(r#"(?:n[ ']importe quand )?jusqu'(?:au|[aà]|en)"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent), + |_, datetime| Ok(datetime.value().clone().mark_before_end()) + ); + b.rule_2("avant ", + b.reg(r#"(?:n[ ']importe quand )?avant"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent), + |_, datetime| Ok(datetime.value().clone().mark_before_start()) + ); + b.rule_2("après ", + b.reg(r#"apr[eè]s"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent), + |_, datetime| Ok(datetime.value().clone().mark_after_end()) + ); + b.rule_2("à partir de ", + b.reg(r#"[aà] partir d['eu]"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent), + |_, datetime| Ok(datetime.value().clone().mark_after_start()) + ); + b.rule_2("à partir de ", + b.reg(r#"[aà] partir d['eu](?:l[ 'a])?"#)?, + datetime_check!(form!(Form::PartOfDay(_))), + |_, datetime| Ok(datetime.value().clone().mark_after_start()) + ); + b.rule_2("après le ", + b.reg(r#"apr(?:e|è)s le"#)?, + integer_check_by_range!(1, 31), + |_, integer| Ok(helpers::day_of_month(integer.value().value as u32)?.mark_after_end()) + ); + b.rule_2("après le ", + b.reg(r#"[aà] partir d['eu]"#)?, + integer_check_by_range!(1, 31), + |_, integer| Ok(helpers::day_of_month(integer.value().value as u32)?.mark_after_start()) + ); + b.rule_2("depuis ", + b.reg(r#"depuis"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent), + |_, datetime| Ok(datetime.value().the_nth(-1)?.mark_after_start()) + ); + b.rule_2("d'ici ", + b.reg(r#"d'ici"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(_))(datetime)), + |_, tod| { + // FIXME: This adds one second to the value of now+then + let now = helpers::cycle_nth(Grain::Second, 0)?; + let then = tod.value().clone().mark_before_start(); + now.span_to(&then, false) + } + ); + b.rule_2("d'ici ", + b.reg(r#"d'ici"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), + |_, date| { + // FIXME: This adds one second to the value of now+then + let today = helpers::cycle_nth(Grain::Day, 0)?; + let then = date.value().clone().mark_before_start(); + today.span_to(&then, false) + } + ); + Ok(()) +} + +pub fn rules_datetime_with_duration(b: &mut RuleSetBuilder) -> RustlingResult<()> { + b.rule_2("il y a ", + b.reg(r#"il y a"#)?, + duration_check!(), + |_, duration| duration.value().ago() + ); + // With "depuis/d'ici", interpretation of duration ambiguous with time-of-day we choose time-of-day + // I.e. "x heures (y)", but not "x heures et y minutes", "x minutes", etc. + // FIXME: some time-of-day patterns should be removed, e.g. "x heures y minutes" - they are not + // proper time-of-day, but they can be duration expressions + // TODO: check if Grain::Second here breaks DatePeriod - cf. implem. of equivalent in English? + b.rule_2("depuis ", + b.reg(r#"depuis|[cç]a fait"#)?, + duration_check!(), + |_, duration| { + if duration.value().get_coarser_grain() == Grain::Hour { + return Err(RuleError::Invalid.into()) + } + duration.value().ago()? + .span_to(&helpers::cycle_nth(Grain::Second, 0)?, false) + }); + b.rule_2("d'ici ", + b.reg(r#"d'ici|dans l(?:'|es?)"#)?, + duration_check!(), + |_, duration| { + let duration_grain = duration.value().get_coarser_grain(); + // Priority to d'ici + if duration_grain == Grain::Hour && + // FIXME: There must be a better way to do this check! + duration.value().period.0.get(Grain::Hour as usize).unwrap_or(&0) <= &23 { + return Err(RuleError::Invalid.into()) + } + let grain = if duration_grain.is_date_grain() { Grain::Day } else { Grain::Second }; + let start = helpers::cycle_nth(grain, 0)?; + let end = if grain == Grain::Day { duration.value().in_present_day()? } else { duration.value().in_present()? }; + start.span_to(&end, true) + } + ); + b.rule_2("dans le ", + b.reg(r#"dans l(?:'|es?)"#)?, + duration_check!(), + |_, duration| { + let duration_grain = duration.value().get_coarser_grain(); + let grain = if duration_grain.is_date_grain() { Grain::Day } else { Grain::Second }; + let start = helpers::cycle_nth(grain, 0)?; + let end = if grain == Grain::Day { duration.value().in_present_day()? } else { duration.value().in_present()? }; + start.span_to(&end, false) + } + ); + b.rule_3(" apres ", + duration_check!(), + b.reg(r#"apr[eè]s"#)?, + datetime_check!(), + |duration, _, datetime| duration.value().after(datetime.value()) + ); + b.rule_3(" avant ", + duration_check!(), + b.reg(r#"avant"#)?, + datetime_check!(), + |duration, _, datetime| duration.value().before(datetime.value()) + ); + b.rule_2("dans ", + b.reg(r#"dans"#)?, + duration_check!(), + |_, duration| duration.value().in_present() + ); + b.rule_2(" plus tard", + duration_check!(), + b.reg(r"plus tard")?, + |duration, _| duration.value().in_present() + ); + Ok(()) +} + + +pub fn rules_datetime_with_cycle(b: &mut RuleSetBuilder) -> RustlingResult<()> { + // Cycle patterns relative to now + b.rule_2("ce|dans le ", + b.reg(r#"(?:(?:dans )?l[ea' ]|cet?(?:te)?)"#)?, + cycle_check!(), + |_, cycle| helpers::cycle_nth(cycle.value().grain, 0) + ); + b.rule_3("ce (la ou ci)", + b.reg(r#"cet?t?e?s?"#)?, + cycle_check!(), + b.reg(r#"-?ci"#)?, + |_, cycle, _| helpers::cycle_nth(cycle.value().grain, 0) + ); + b.rule_2(" dernier", + cycle_check!(), + b.reg(r#"derni[èe]re?|pass[ée]e?|pr[eé]c[eé]dente?|(?:d')? ?avant"#)?, + |cycle, _| helpers::cycle_nth(cycle.value().grain, -1) + ); + b.rule_3("le dernier", + b.reg(r#"l[ae']? ?"#)?, + cycle_check!(), + b.reg(r#"derni[èe]re?|pass[ée]e?"#)?, + |_, cycle, _| helpers::cycle_nth(cycle.value().grain, -1) + ); + b.rule_3("n derniers ", + integer_check_by_range!(2, 9999), + b.reg(r#"derni.re?s?"#)?, + cycle_check!(), + |integer, _, cycle| { + let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, -1 * integer.value().value)?; + // All grains except Day will trigger the right datetime_kind + if cycle.value().grain == Grain::Day { + res = res.datetime_kind(DatetimeKind::DatePeriod); + } + Ok(res) + } + ); + b.rule_4("(pendant/durant/dans) les n derniers ", + b.reg(r#"(?:pendant |durant |dans )?[cld]es"#)?, + integer_check_by_range!(2, 9999), + b.reg(r#"derni.re?s?"#)?, + cycle_check!(), + |_, integer, _, cycle| { + let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, -1 * integer.value().value)?; + // All grains except Day will trigger the right datetime_kind + if cycle.value().grain == Grain::Day { + res = res.datetime_kind(DatetimeKind::DatePeriod); + } + Ok(res) + } + ); + b.rule_3("n passes|precedents", + integer_check_by_range!(2, 9999), + cycle_check!(), + b.reg(r#"pass[eèé][eèé]?s?|pr[eé]c[eé]dente?s?|(?:d')? ?avant|plus t[oô]t"#)?, + |integer, cycle, _| { + let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, -1 * integer.value().value)?; + // All grains except Day will trigger the right datetime_kind + if cycle.value().grain == Grain::Day { + res = res.datetime_kind(DatetimeKind::DatePeriod); + } + Ok(res) + } + ); + b.rule_4("(pendant/durant/dans) les n passes|precedents", + b.reg(r#"(?:pendant |durant |dans )?[cld]es"#)?, + integer_check_by_range!(2, 9999), + cycle_check!(), + b.reg(r#"pass[eèé][eèé]?s?|pr[eé]c[eé]dente?s?|(?:d')? ?avant|plus t[oô]t"#)?, + |_, integer, cycle, _| helpers::cycle_n_not_immediate(cycle.value().grain, -1 * integer.value().value) + ); + // Incorrect resolution if some follows the expression, + // e.g. "suivant le " (unsupported) + b.rule_2(" prochain|suivant|d'après", + cycle_check!(), + b.reg(r#"prochaine?|suivante?|qui suit|(?:d')? ?apr[eèé]s"#)?, + |cycle, _| helpers::cycle_nth(cycle.value().grain, 1) + ); + b.rule_3("le prochain|suivant|d'après", + b.reg(r#"l[ae']? ?|une? ?"#)?, + cycle_check!(), + b.reg(r#"prochaine?|suivante?|qui suit|(?:d'? ?)?apr[eèé]s"#)?, + |_, cycle, _| helpers::cycle_nth(cycle.value().grain, 1) + ); + b.rule_3("n prochains ", + integer_check_by_range!(2, 9999), + b.reg(r#"prochaine?s?|suivante?s?|apr[eèé]s"#)?, + cycle_check!(), + |integer, _, cycle| { + let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, integer.value().value)?; + // All grains except Day will trigger the right datetime_kind + if cycle.value().grain == Grain::Day { + res = res.datetime_kind(DatetimeKind::DatePeriod); + } + Ok(res) + } + ); + b.rule_4("(pendant/durant/dans) les n prochains ", + b.reg(r#"(?:pendant |durant |dans )?[cld]es"#)?, + integer_check_by_range!(2, 9999), + b.reg(r#"prochaine?s?|suivante?s?|apr[eèé]s"#)?, + cycle_check!(), + |_, integer, _, cycle| { + let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, integer.value().value)?; + // All grains except Day will trigger the right datetime_kind + if cycle.value().grain == Grain::Day { + res = res.datetime_kind(DatetimeKind::DatePeriod); + } + Ok(res) + } + ); + b.rule_3("n suivants", + integer_check_by_range!(2, 9999), + cycle_check!(), + b.reg(r#"prochaine?s?|suivante?s?|apr[eèé]s|qui sui(?:t|ves?)|plus tard"#)?, + |integer, cycle, _| { + let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, integer.value().value)?; + // All grains except Day will trigger the right datetime_kind + if cycle.value().grain == Grain::Day { + res = res.datetime_kind(DatetimeKind::DatePeriod); + } + Ok(res) + } + ); + b.rule_4("(pendant/durant/dans) les n suivants", + b.reg(r#"(?:pendant |durant |dans )?[cld]es"#)?, + integer_check_by_range!(2, 9999), + cycle_check!(), + b.reg(r#"prochaine?s?|suivante?s?|apr[eèé]s|qui sui(?:t|ves?)|plus tard"#)?, + |_, integer, cycle, _| { + let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, integer.value().value)?; + // All grains except Day will trigger the right datetime_kind + if cycle.value().grain == Grain::Day { + res = res.datetime_kind(DatetimeKind::DatePeriod); + } + Ok(res) + } + ); + b.rule_3("n avant", + integer_check_by_range!(2, 9999), + cycle_check!(), + b.reg(r#"(?:d')? ?avant|plus t[oô]t"#)?, + |integer, cycle, _| { + let mut res = helpers::cycle_nth(cycle.value().grain, -1 * integer.value().value)?; + // All grains except Day will trigger the right datetime_kind + if cycle.value().grain == Grain::Day { + res = res.datetime_kind(DatetimeKind::DatePeriod); + } + Ok(res) + } + ); + b.rule_3("n après", + integer_check_by_range!(2, 9999), + cycle_check!(), + b.reg(r#"(?:d')? ?apr[eèé]s|qui sui(?:t|ves?)|plus tard"#)?, + |integer, cycle, _| { + let mut res = helpers::cycle_nth(cycle.value().grain, integer.value().value)?; + // All grains except Day will trigger the right datetime_kind + if cycle.value().grain == Grain::Day { + res = res.datetime_kind(DatetimeKind::DatePeriod); + } + Ok(res) + } + ); + // Cycle patterns relative to another datetime + b.rule_4("le après|suivant ", + b.reg(r#"l[ea']? ?"#)?, + cycle_check!(), + b.reg(r#"suivante?|apr[eèé]s"#)?, + datetime_check!(), + |_, cycle, _, datetime| helpers::cycle_nth_after(cycle.value().grain, 1, datetime.value()) + ); + b.rule_4("le avant|précédent ", + b.reg(r#"l[ea']? ?"#)?, + cycle_check!(), + b.reg(r#"avant|pr[ée]c[ée]dent"#)?, + datetime_check!(), + |_, cycle, _, datetime| helpers::cycle_nth_after(cycle.value().grain, -1, datetime.value()) + ); + b.rule_4(" de ", + ordinal_check!(), + cycle_check!(), + b.reg(r#"d['eu]|en"#)?, + datetime_check!(), + |ordinal, cycle, _, datetime| helpers::cycle_nth_after_not_immediate(cycle.value().grain, ordinal.value().value - 1, datetime.value()) + ); + b.rule_5("le de ", + b.reg(r#"l[ea]"#)?, + ordinal_check!(), + cycle_check!(), + b.reg(r#"d['eu]|en"#)?, + datetime_check!(), + |_, ordinal, cycle, _, datetime| helpers::cycle_nth_after_not_immediate(cycle.value().grain, ordinal.value().value - 1, datetime.value()) + ); + b.rule_4("le de ", + b.reg(r#"l[ea]"#)?, + cycle_check!(), + b.reg(r#"d['eu]|en"#)?, + datetime_check!(), + |_, cycle, _, datetime| helpers::cycle_nth_after_not_immediate(cycle.value().grain, 0, datetime.value()) + ); + Ok(()) +} + + +/* DATETIME - CYCLE DEFINITIONS */ +pub fn rules_cycle(b: &mut RuleSetBuilder) -> RustlingResult<()> { + b.rule_1_terminal("seconde (cycle)", + b.reg(r#"secondes?"#)?, + |_| CycleValue::new(Grain::Second) + ); + b.rule_1_terminal("minute (cycle)", + b.reg(r#"minutes?"#)?, + |_| CycleValue::new(Grain::Minute) + ); + b.rule_1_terminal("heure (cycle)", + b.reg(r#"heures?"#)?, + |_| CycleValue::new(Grain::Hour) + ); + b.rule_1_terminal("jour (cycle)", + b.reg(r#"jour(?:n[ée]e?)?s?"#)?, + |_| CycleValue::new(Grain::Day) + ); + b.rule_1_terminal("semaine (cycle)", + b.reg(r#"semaines?"#)?, + |_| CycleValue::new(Grain::Week) + ); + b.rule_1("mois (cycle)", + b.reg(r#"mois"#)?, + |_| CycleValue::new(Grain::Month) + ); + b.rule_1_terminal("trimestre (cycle)", + b.reg(r#"trimestre"#)?, + |_| CycleValue::new(Grain::Quarter) + ); + b.rule_1("année (cycle)", + b.reg(r#"an(?:n[ée]e?)?s?"#)?, + |_| CycleValue::new(Grain::Year) + ); + Ok(()) +} diff --git a/grammar/fr/src/rules_duration.rs b/grammar/fr/src/rules_duration.rs new file mode 100644 index 00000000..33cb0e88 --- /dev/null +++ b/grammar/fr/src/rules_duration.rs @@ -0,0 +1,117 @@ +use rustling::*; +use rustling_ontology_values::dimension::*; +use rustling_ontology_values::helpers; +use rustling_ontology_moment::{Grain, PeriodComp, Period}; + +pub fn rules_duration(b: &mut RuleSetBuilder) -> RustlingResult<()> { + b.rule_1_terminal("seconde (unit-of-duration)", + b.reg(r#"sec(?:onde)?s?"#)?, + |_| Ok(UnitOfDurationValue::new(Grain::Second)) + ); + b.rule_1_terminal("minute (unit-of-duration)", + b.reg(r#"min(?:ute)?s?"#)?, + |_| Ok(UnitOfDurationValue::new(Grain::Minute)) + ); + b.rule_1_terminal("heure (unit-of-duration)", + b.reg(r#"h(?:eure)?s?"#)?, + |_| Ok(UnitOfDurationValue::new(Grain::Hour)) + ); + b.rule_1_terminal("jour (unit-of-duration)", + b.reg(r#"jour(?:n[ée]e?)?s?"#)?, + |_| Ok(UnitOfDurationValue::new(Grain::Day)) + ); + b.rule_1_terminal("semaine (unit-of-duration)", + b.reg(r#"semaines?"#)?, + |_| Ok(UnitOfDurationValue::new(Grain::Week)) + ); + b.rule_1_terminal("mois (unit-of-duration)", + b.reg(r#"mois?"#)?, + |_| Ok(UnitOfDurationValue::new(Grain::Month)) + ); + b.rule_1_terminal("année (unit-of-duration)", + b.reg(r#"an(?:n[ée]e?)?s?"#)?, + |_| Ok(UnitOfDurationValue::new(Grain::Year)) + ); + b.rule_1_terminal("un quart heure", + b.reg(r#"(1/4\s?h(?:eure)?|(?:un|1) quart d'heure)"#)?, + |_| Ok(DurationValue::new(PeriodComp::minutes(15).into())) + ); + b.rule_1_terminal("une demi heure", + b.reg(r#"(?:1/2\s?h(?:eure)?|(?:1|une) demi(?:e)?(?:\s|-)heure)"#)?, + |_| Ok(DurationValue::new(PeriodComp::minutes(30).into())) + ); + b.rule_1_terminal("trois quarts d'heure", + b.reg(r#"(?:3/4\s?h(?:eure)?|(?:3|trois) quart(?:s)? d'heure)"#)?, + |_| Ok(DurationValue::new(PeriodComp::minutes(45).into())) + ); + b.rule_2(" ", + integer_check_by_range!(0), + unit_of_duration_check!(), + |integer, unit| Ok(DurationValue::new(PeriodComp::new(unit.value().grain, integer.value().value).into())) + ); + b.rule_3(" de ", + integer_check!(|integer: &IntegerValue| integer.value >= 0 && integer.group), + b.reg(r#"d[e']"#)?, + unit_of_duration_check!(), + |integer, _, unit| Ok(DurationValue::new(PeriodComp::new(unit.value().grain, integer.value().value).into())) + ); + b.rule_4(" h ", + integer_check_by_range!(0), + b.reg(r#"h(?:eures?)?"#)?, + integer_check_by_range!(0,59), + b.reg(r#"m(?:inutes?)?"#)?, + |hour, _, minute, _| { + let hour_period = Period::from(PeriodComp::new(Grain::Hour, hour.value().clone().value)); + let minute_period = Period::from(PeriodComp::new(Grain::Minute, minute.value().clone().value)); + Ok(DurationValue::new(hour_period + minute_period)) + } + ); + b.rule_3(" et quart", + integer_check_by_range!(0), + unit_of_duration_check!(), + b.reg(r#"et quart"#)?, + |integer, uod, _| { + let quarter_period: Period = uod.value().grain.quarter_period().map(|a| a.into()).ok_or_else(|| RuleError::Invalid)?; + Ok(DurationValue::new(quarter_period + PeriodComp::new(uod.value().grain, integer.value().value))) + } + ); + b.rule_3(" et demie", + integer_check_by_range!(0), + unit_of_duration_check!(), + b.reg(r#"et demie?"#)?, + |integer, uod, _| { + let half_period: Period = uod.value().grain.half_period().map(|a| a.into()).ok_or_else(|| RuleError::Invalid)?; + Ok(DurationValue::new(half_period + PeriodComp::new(uod.value().grain, integer.value().value))) + } + ); + b.rule_3(" et ", + duration_check!(|duration: &DurationValue| !duration.suffixed), + b.reg(r#"et"#)?, + duration_check!(|duration: &DurationValue| !duration.prefixed), + |a, _, b| Ok(a.value() + b.value()) + ); + b.rule_2(" ", + duration_check!(|duration: &DurationValue| !duration.suffixed), + duration_check!(|duration: &DurationValue| !duration.prefixed), + |a, b| Ok(a.value() + b.value()) + ); + b.rule_2(" ", + duration_check!(|duration: &DurationValue| !duration.prefixed), + integer_check_by_range!(0), + |duration, integer| helpers::compose_duration_with_integer(duration.value(), integer.value()) + ); + b.rule_2("environ ", + b.reg(r#"environ"#)?, + duration_check!(), + |_, duration| Ok(duration.value().clone().precision(Precision::Approximate)) + ); + // Ambiguous w/ time-of-day w/ "pour" + // Duration has less priority than Datetime types, therefore duration will be output only + // if the output kind filter is set for Duration + b.rule_2("pendant ", + b.reg(r#"pendant|durant|pour(?: une dur[eé]e? d['e])?"#)?, + duration_check!(), + |_, duration| Ok(duration.value().clone().prefixed()) + ); + Ok(()) +} \ No newline at end of file diff --git a/grammar/fr/src/rules_number.rs b/grammar/fr/src/rules_number.rs new file mode 100644 index 00000000..da1ee39d --- /dev/null +++ b/grammar/fr/src/rules_number.rs @@ -0,0 +1,561 @@ +use std::f32; + +use rustling::*; +use rustling_ontology_values::dimension::*; +use rustling_ontology_values::helpers; + +pub fn rules_numbers(b: &mut RuleSetBuilder) -> RustlingResult<()> { + b.rule_2("intersect", + number_check!(|number: &NumberValue| number.grain().unwrap_or(0) > 1), + number_check!(), + |a, b| helpers::compose_numbers(&a.value(), &b.value())); + b.rule_1_terminal( + "number (0..16)", + b.reg(r#"(z[eé]ro|une?|deux|trois|quatre|cinq|six|sept|huit|neuf|dix|onze|douze|treize|quatorze|quinze|seize)"#)?, + |text_match| { + let value = match text_match.group(1).as_ref() { + "zéro" => 0, + "zero" => 0, + "un" => 1, + "une" => 1, + "deux" => 2, + "trois" => 3, + "quatre" => 4, + "cinq" => 5, + "six" => 6, + "sept" => 7, + "huit" => 8, + "neuf" => 9, + "dix" => 10, + "onze" => 11, + "douze" => 12, + "treize" => 13, + "quatorze" => 14, + "quinze" => 15, + "seize" => 16, + _ => return Err(RuleError::Invalid.into()), + }; + IntegerValue::new(value) + }); + b.rule_1_terminal("quelques", + b.reg(r#"quelques"#)?, + |_| IntegerValue::new_with_grain(3, 1) + ); + b.rule_1_terminal("number (20..60)", + b.reg(r#"(vingt|trente|quarante|cinquante|soixante)"#)?, + |text_match| { + let value = match text_match.group(1).as_ref() { + "vingt" => 20, + "trente" => 30, + "quarante" => 40, + "cinquante" => 50, + "soixante" => 60, + _ => return Err(RuleError::Invalid.into()), + }; + IntegerValue::new(value) + }); + b.rule_2("number (17..19)", + integer_check_by_range!(10, 10), + integer_check_by_range!(7, 9), + |_, b| IntegerValue::new(b.value().value + 10)); + b.rule_3("number (17..19)", + integer_check_by_range!(10, 10), + b.reg(r"-")?, + integer_check_by_range!(7, 9), + |_, _, b| IntegerValue::new(b.value().value + 10)); + b.rule_2_terminal("number 80", + b.reg(r#"quatre"#)?, + b.reg(r#"vingts?"#)?, + |_, _| IntegerValue::new(80)); + b.rule_3_terminal("number 80", + b.reg(r#"quatre"#)?, + b.reg(r"-")?, + b.reg(r#"vingts?"#)?, + |_, _, _| IntegerValue::new(80)); + b.rule_3("numbers 21 31 41 51", + integer_check_by_range!(20, 50, |integer: &IntegerValue| integer.value % 10 == 0), + b.reg(r#"-?et-?"#)?, + integer_check_by_range!(1, 1), + |a, _, b| IntegerValue::new(a.value().value + b.value().value)); + b.rule_2("numbers 22..29 32..39 .. 52..59", + integer_check_by_range!(20, 50, |integer: &IntegerValue| integer.value % 10 == 0), + integer_check_by_range!(2, 9), + |a, b| IntegerValue::new(a.value().value + b.value().value)); + b.rule_3("numbers 22..29 32..39 .. 52..59", + integer_check_by_range!(20, 50, |integer: &IntegerValue| integer.value % 10 == 0), + b.reg(r"-")?, + integer_check_by_range!(2, 9), + |a, _, b| IntegerValue::new(a.value().value + b.value().value)); + b.rule_3("numbers 61 71", + integer_check_by_range!(60, 60), + b.reg(r#"-?et-?"#)?, + integer_check_by_range!(1, + 11, + |integer: &IntegerValue| integer.value == 1 || integer.value == 11), + |a, _, b| IntegerValue::new(a.value().value + b.value().value)); + b.rule_2("numbers 81 91", + integer_check_by_range!(80, 80), + integer_check_by_range!(1, + 11, + |integer: &IntegerValue| integer.value == 1 || integer.value == 11), + |a, b| IntegerValue::new(a.value().value + b.value().value)); + b.rule_3("numbers 81 91", + integer_check_by_range!(80, 80), + b.reg(r#"-"#)?, + integer_check_by_range!(1, + 11, + |integer: &IntegerValue| integer.value == 1 || integer.value == 11), + |a, _, b| IntegerValue::new(a.value().value + b.value().value)); + b.rule_2("numbers 62..69 .. 92..99", + integer_check_by_range!(60, + 80, + |integer: &IntegerValue| integer.value == 60 || integer.value == 80), + integer_check_by_range!(2, 19), + |a, b| IntegerValue::new(a.value().value + b.value().value)); + b.rule_3("numbers 62..69 .. 92..99", + integer_check_by_range!(60, + 80, + |integer: &IntegerValue| integer.value == 60 || integer.value == 80), + b.reg(r"-")?, + integer_check_by_range!(2, 19), + |a, _, b| IntegerValue::new(a.value().value + b.value().value)); + b.rule_1_terminal("hundred", + b.reg(r#"cents?"#)?, + |_| IntegerValue::new_with_grain(100, 2) + ); + b.rule_1_terminal("thousand", + b.reg(r#"milles?"#)?, + |_| IntegerValue::new_with_grain(1000, 3) + ); + b.rule_1_terminal("million", + b.reg(r#"millions?"#)?, + |_| IntegerValue::new_with_grain(1000000, 6) + ); + b.rule_1_terminal("billion", + b.reg(r#"milliards?"#)?, + |_| IntegerValue::new_with_grain(1000000000, 9) + ); + b.rule_2("number hundreds", + integer_check_by_range!(1, 99), + b.reg(r#"cents?"#)?, + |a, _| { + Ok(IntegerValue { + value: a.value().value * 100, + grain: Some(2), + ..IntegerValue::default() + }) + }); + b.rule_2("number thousands", + integer_check_by_range!(1, 999), + b.reg(r#"milles?"#)?, + |a, _| { + Ok(IntegerValue { + value: a.value().value * 1000, + grain: Some(3), + ..IntegerValue::default() + }) + }); + b.rule_2("number millions", + integer_check_by_range!(1, 999), + b.reg(r#"millions?"#)?, + |a, _| { + Ok(IntegerValue { + value: a.value().value * 1000000, + grain: Some(6), + ..IntegerValue::default() + }) + }); + b.rule_2("number billions", + integer_check_by_range!(1, 999), + b.reg(r#"milliards?"#)?, + |a, _| { + Ok(IntegerValue { + value: a.value().value * 1000000000, + grain: Some(9), + ..IntegerValue::default() + }) + }); + b.rule_1_terminal("integer (numeric)", + b.reg(r#"(\d{1,18})"#)?, + |text_match| { + let value: i64 = text_match.group(1).parse()?; + IntegerValue::new(value) + }); + b.rule_1_terminal("integer with thousands separator .", + b.reg(r#"(\d{1,3}(\.\d\d\d){1,5})"#)?, + |text_match| { + let reformatted_string = text_match.group(1).replace(".", ""); + let value: i64 = reformatted_string.parse()?; + IntegerValue::new(value) + }); + b.rule_1_terminal("decimal number", + b.reg(r#"(\d*,\d+)"#)?, + |text_match| { + let reformatted_string = text_match.group(1).replace(",", "."); + let value: f32 = reformatted_string.parse()?; + FloatValue::new(value) + }); + b.rule_3("number dot number", + number_check!(|number: &NumberValue| !number.prefixed()), + b.reg(r#"virgule|point"#)?, + number_check!(|number: &NumberValue| !number.suffixed()), + |a, _, b| { + let power = b.value().value().to_string().chars().count(); + let coeff = 10.0_f32.powf(-1.0 * power as f32); + Ok(FloatValue { + value: b.value().value() * coeff + a.value().value(), + ..FloatValue::default() + }) + }); + b.rule_4("number dot zero ... number", + number_check!(|number: &NumberValue| !number.prefixed()), + b.reg(r#"virgule|point"#)?, + b.reg(r#"(?:(?:z[eé]ro )*(?:z[eé]ro))"#)?, + number_check!(|number: &NumberValue| !number.suffixed()), + |a, _, zeros, b| { + let power = zeros.group(0).split_whitespace().count() + b.value().value().to_string().chars().count(); + let coeff = 10.0_f32.powf(-1.0 * power as f32); + Ok(FloatValue { + value: b.value().value() * coeff + a.value().value(), + ..FloatValue::default() + }) + }); + b.rule_1_terminal("decimal with thousands separator", + b.reg(r#"(\d+(\.\d\d\d)+,\d+)"#)?, + |text_match| { + let reformatted_string = text_match.group(1).replace(".", "").replace(",", "."); + let value: f32 = reformatted_string.parse()?; + FloatValue::new(value) + }); + b.rule_2("numbers prefix with -, negative or minus", + b.reg(r#"-|moins"#)?, + number_check!(|number: &NumberValue| !number.prefixed()), + |_, a| -> RuleResult { + Ok(match a.value().clone() { + // checked + NumberValue::Integer(integer) => { + IntegerValue { + value: integer.value * -1, + prefixed: true, + ..integer + } + .into() + } + NumberValue::Float(float) => { + FloatValue { + value: float.value * -1.0, + prefixed: true, + ..float + } + .into() + } + }) + }); + b.rule_2("numbers suffixes (K, M, G)", + number_check!(|number: &NumberValue| !number.suffixed()), + b.reg_neg_lh(r#"([kmg])"#, r#"^[\W\$€]"#)?, + |a, text_match| -> RuleResult { + let multiplier = match text_match.group(0).as_ref() { + "k" => 1000, + "m" => 1000000, + "g" => 1000000000, + _ => return Err(RuleError::Invalid.into()), + }; + Ok(match a.value().clone() { // checked + NumberValue::Integer(integer) => { + IntegerValue { + value: integer.value * multiplier, + suffixed: true, + ..integer + } + .into() + } + NumberValue::Float(float) => { + let product = float.value * (multiplier as f32); + if product.floor() == product { + IntegerValue { + value: product as i64, + suffixed: true, + ..IntegerValue::default() + } + .into() + } else { + FloatValue { + value: product, + suffixed: true, + ..float + } + .into() + } + } + }) + }); + b.rule_1_terminal("(douzaine ... soixantaine)", + b.reg(r#"(demi[ -]douz|diz|douz|quinz|vingt|trent|quarant|cinquant|soixant|cent)aines?"#)?, + |text_match| { + let value = match text_match.group(1).as_ref() { + "demi douz" => 6, + "demi-douz" => 6, + "diz" => 10, + "douz" => 12, + "quinz" => 15, + "vingt" => 20, + "trent" => 30, + "quarant" => 40, + "cinquant" => 50, + "soixant" => 60, + "cent" => 100, + _ => return Err(RuleError::Invalid.into()), + }; + Ok(IntegerValue { + value, + group: true, + .. IntegerValue::default() + }) + } + ); + b.rule_2("number dozen", + integer_check_by_range!(1, 9), + integer_check!(|integer: &IntegerValue| integer.group), + |a, b| { + Ok(IntegerValue { + value: a.value().value * b.value().value, + grain: b.value().grain, + group: true, + ..IntegerValue::default() + }) + }); + b.rule_1_terminal("ordinal 0", + b.reg(r#"z[eé]rot?i[eè]me"#)?, + |_| { + Ok(OrdinalValue::new(0)) + } + ); + b.rule_1_terminal("ordinal 1", + b.reg(r#"premi[eè]re?"#)?, + |_| { + Ok(OrdinalValue::new(1)) + } + ); + b.rule_1_terminal("ordinal 2", + b.reg(r#"seconde?|deuxi[eè]me"#)?, + |_| { + Ok(OrdinalValue::new(2)) + } + ); + b.rule_1_terminal( + "ordinals (premier..seizieme)", + b.reg(r#"(trois|quatr|cinqu|six|sept|huit|neuv|dix|onz|douz|treiz|quatorz|quinz|seiz)i[eè]me"#)?, + |text_match| { + let value = match text_match.group(1).as_ref() { + "trois" => 3, + "quatr" => 4, + "cinqu" => 5, + "six" => 6, + "sept" => 7, + "huit" => 8, + "neuv" => 9, + "dix" => 10, + "onz" => 11, + "douz" => 12, + "treiz" => 13, + "quatorz" => 14, + "quinz" => 15, + "seiz" => 16, + _ => return Err(RuleError::Invalid.into()), + }; + Ok(OrdinalValue::new(value)) + }); + b.rule_2("17ieme, 18ieme, 19ieme", + b.reg(r#"dix-?"#)?, + ordinal_check_by_range!(7, 9), + |_, ordinal| { + Ok(OrdinalValue::new(10 + ordinal.value().value)) + } + ); + b.rule_1_terminal("20ieme, 30ieme, 40ieme, 50ieme, 60ieme", + b.reg(r#"(vingt|trent|quarant|cinquant|soixant)i[èe]me"#)?, + |text_match| { + let value = match text_match.group(1).as_ref() { + "vingt" => 20, + "trent" => 30, + "quarant" => 40, + "cinquant" => 50, + "soixant" => 60, + _ => return Err(RuleError::Invalid.into()), + }; + Ok(OrdinalValue::new(value)) + } + ); + b.rule_1_terminal("80ieme", + b.reg(r#"quatre[- ]vingts?i[èe]me"#)?, + |_| { + Ok(OrdinalValue::new(80)) + } + ); + b.rule_2("22ieme...29ieme, 32ieme...39ieme, 42ieme...49ieme, 52ieme...59ieme", + integer_check_by_range!(20, 50, |integer: &IntegerValue| integer.value % 10 == 0), + ordinal_check_by_range!(2, 9), + |integer, ordinal| { + Ok(OrdinalValue::new(integer.value().value + ordinal.value().value)) + } + ); + b.rule_3("22ieme...29ieme, 32ieme...39ieme, 42ieme...49ieme, 52ieme...59ieme", + integer_check_by_range!(20, 50, |integer: &IntegerValue| integer.value % 10 == 0), + b.reg(r"-")?, + ordinal_check_by_range!(2, 9), + |integer, _, ordinal| { + Ok(OrdinalValue::new(integer.value().value + ordinal.value().value)) + } + ); + b.rule_2("62ieme...70ieme, 72ieme...79ieme, 90ieme, 92ieme...99ieme", + integer_check_by_range!(60, 80, |integer: &IntegerValue| integer.value == 60 || integer.value == 80), + ordinal_check_by_range!(2, 19), + |integer, ordinal| { + Ok(OrdinalValue::new(integer.value().value + ordinal.value().value)) + } + ); + b.rule_3("62ieme...70ieme, 72ieme...79ieme, 90ieme, 92ieme...99ieme", + integer_check_by_range!(60, 80, |integer: &IntegerValue| integer.value == 60 || integer.value == 80), + b.reg(r"-")?, + ordinal_check_by_range!(2, 19), + |integer, _, ordinal| { + Ok(OrdinalValue::new(integer.value().value + ordinal.value().value)) + } + ); + b.rule_2("21, 31, 41, 51, 61", + integer_check_by_range!(20, 60, |integer: &IntegerValue| integer.value % 10 == 0), + b.reg(r#"(?:et |-)uni[èe]me"#)?, + |integer, _| { + Ok(OrdinalValue::new(integer.value().value + 1)) + } + ); + b.rule_2("81", + integer_check_by_range!(80, 80), + b.reg(r#"(?:et )?uni[èe]me"#)?, + |integer, _| { + Ok(OrdinalValue::new(integer.value().value + 1)) + } + ); + b.rule_2("71, 91", + integer_check_by_range!(60, 60), + b.reg(r#"et onzi[eè]me"#)?, + |integer, _| { + Ok(OrdinalValue::new(integer.value().value + 11)) + } + ); + b.rule_2(" et demi", + integer_check_by_range!(0, 99), + b.reg(r#"et demie?"#)?, + |integer, _| { + FloatValue::new(integer.value().value as f32 + 0.5) + } + ); + b.rule_1_terminal("70, 80, 90 (Belgium and Switzerland)", + b.reg(r#"(sept|huit|non)ante"#)?, + |text_match| { + let value = match text_match.group(1).as_ref() { + "sept" => 70, + "huit" => 80, + "non" => 90, + _ => return Err(RuleError::Invalid.into()), + }; + IntegerValue::new(value) + } + ); + b.rule_1_terminal("71, 81, 91 (Belgium and Switzerland)", + b.reg(r#"(sept|huit|non)ante et une?"#)?, + |text_match| { + let value = match text_match.group(1).as_ref() { + "sept" => 71, + "huit" => 81, + "non" => 91, + _ => return Err(RuleError::Invalid.into()), + }; + IntegerValue::new(value) + } + ); + + b.rule_2("72..79, 82..89, 92..99, (Belgium and Switzerland)", + b.reg(r#"(sept|huit|non)ante"#)?, + integer_check_by_range!(2, 9), + |text_match, integer| { + let value = match text_match.group(1).as_ref() { + "sept" => 70, + "huit" => 80, + "non" => 90, + _ => return Err(RuleError::Invalid.into()), + }; + IntegerValue::new(value + integer.value().value) + } + ); + b.rule_1_terminal("ordinal (100, 1_000, 1_000_000)", + b.reg(r#"(cent|mill|million|milliard)i[èe]me"#)?, + |text_match| { + let (value, grain) = match text_match.group(1).as_ref() { + "cent" => (100, 2), + "mill" => (1_000, 3), + "million" => (1_000_000, 6), + "milliard" => (1_000_000_000, 9), + _ => return Err(RuleError::Invalid.into()), + }; + Ok(OrdinalValue::new_with_grain(value, grain)) + } + ); + + b.rule_2("ordinal (200..900, 2_000..9_000, 2_000_000..9_000_000_000)", + integer_check_by_range!(2, 999), + b.reg(r#"(cent|mill|million|milliard)i[èe]me"#)?, + |integer, text_match| { + let (value, grain) = match text_match.group(1).as_ref() { + "cent" => (100, 2), + "mill" => (1_000, 3), + "million" => (1_000_000, 6), + "milliard" => (1_000_000_000, 9), + _ => return Err(RuleError::Invalid.into()), + }; + Ok(OrdinalValue::new_with_grain(integer.value().value * value, grain)) + } + ); + + b.rule_2("ordinal (1_1_000..9_999_999_000)", + integer_check_by_range!(1000, 99_999_999_000), + ordinal_check!(|ordinal: &OrdinalValue| { + let grain = ordinal.grain.unwrap_or(0); + grain == 2 || grain % 3 == 0 + }), + |integer, ordinal| { + let grain = ordinal.value().grain.unwrap_or(0); + let next_grain = (grain / 3) * 3 + 3; + if integer.value().value % 10i64.pow(next_grain as u32) != 0 { return Err(RuleError::Invalid.into()); } + Ok(OrdinalValue::new(integer.value().value + ordinal.value().value)) + } + ); + + b.rule_2("ordinal (102...9_999_999)", + integer_check!(|integer: &IntegerValue| integer.value >= 100 || integer.value % 100 == 0), + ordinal_check_by_range!(2, 99), + |integer, ordinal| { + Ok(OrdinalValue::new(integer.value().value + ordinal.value().value)) + } + ); + b.rule_2("ordinal (101, 201, 301, ...)", + integer_check!(|integer: &IntegerValue| integer.value >= 100 || integer.value % 100 == 0), + b.reg(r#"(?:et |-)?uni[èe]me"#)?, + |integer, _| { + Ok(OrdinalValue::new(integer.value().value + 1)) + } + ); + b.rule_1_terminal("ordinal (digits)", + b.reg(r#"0*(\d+) ?(ere?|ère|ème|eme|ieme|ième)"#)?, + |text_match| { + let value: i64 = text_match.group(1).parse()?; + Ok(OrdinalValue::new(value)) + }); + b.rule_2("le ", + b.reg(r#"l[ea]"#)?, + ordinal_check!(), + |_, a| Ok((*a.value()).prefixed()) + ); + Ok(()) +} \ No newline at end of file From 87cb28f7c9bfa355505fd4c39da4fc256abca9ca Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Thu, 20 Jun 2019 18:28:04 +0200 Subject: [PATCH 005/107] =?UTF-8?q?Add=20support=20for=20mid/end/beg=20sea?= =?UTF-8?q?son,=20mi-journ=C3=A9e,=20prep=20+=20part-of-day.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- grammar/fr/src/rules_datetime.rs | 62 ++++++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 93fb4ac5..987049c0 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -428,6 +428,11 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { datetime_check!(form!(Form::TimeOfDay(_))), |_, a| Ok(a.value().clone().not_latent()) ); + b.rule_2("à ", + b.reg(r#"[aà]|pour"#)?, + datetime_check!(form!(Form::PartOfDay(_))), + |_, a| Ok(a.value().clone().not_latent()) + ); b.rule_2("vers ", b.reg(r#"(?:plut[ôo]t )?(?:vers|autour de|[aà] environ|aux alentours de)"#)?, datetime_check!(form!(Form::TimeOfDay(_))), @@ -652,7 +657,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { } ); b.rule_1_terminal("milieu de journée", - b.reg(r#"milieu de (?:la )?journ[ée]e"#)?, + b.reg(r#"(?:milieu de (?:la )?|(?:(?:[àa] )?la )?mi[ -])journ[ée]e"#)?, |_| { Ok(helpers::hour(11, false)? .span_to(&helpers::hour(16, false)?, false)? @@ -826,6 +831,54 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.reg(r#"(?:ce )?printemps"#)?, |_| helpers::month_day(3, 20)?.span_to(&helpers::month_day(6, 21)?, false) ); + b.rule_1_terminal("début de l'été", + b.reg(r#"début de (?:cet |l')?(?:été|ete)"#)?, + |_| helpers::month_day(6, 21)?.span_to(&helpers::month_day(7, 15)?, false) + ); + b.rule_1_terminal("milieu de l'été", + b.reg(r#"milieu de (?:cet |l')?(?:été|ete)"#)?, + |_| helpers::month_day(7, 15)?.span_to(&helpers::month_day(8, 15)?, false) + ); + b.rule_1_terminal("fin de l'été", + b.reg(r#"fin de (?:cet |l')?(?:été|ete)"#)?, + |_| helpers::month_day(8, 15)?.span_to(&helpers::month_day(9, 21)?, false) + ); + b.rule_1_terminal("début de l'automne", + b.reg(r#"début de (?:cet |l')?(?:été|ete)"#)?, + |_| helpers::month_day(9, 21)?.span_to(&helpers::month_day(10, 15)?, false) + ); + b.rule_1_terminal("milieu de l'automne", + b.reg(r#"milieu de (?:cet |l')?(?:été|ete)"#)?, + |_| helpers::month_day(10, 15)?.span_to(&helpers::month_day(11, 15)?, false) + ); + b.rule_1_terminal("fin de l'automne", + b.reg(r#"fin de (?:cet |l')?(?:été|ete)"#)?, + |_| helpers::month_day(11, 15)?.span_to(&helpers::month_day(12, 21)?, false) + ); + b.rule_1_terminal("début de l'hiver", + b.reg(r#"début de (?:cet |l')?(?:été|ete)"#)?, + |_| helpers::month_day(12, 21)?.span_to(&helpers::month_day(1, 15)?, false) + ); + b.rule_1_terminal("milieu de l'hiver", + b.reg(r#"milieu de (?:cet |l')?(?:été|ete)"#)?, + |_| helpers::month_day(1, 15)?.span_to(&helpers::month_day(2, 15)?, false) + ); + b.rule_1_terminal("fin de l'hiver", + b.reg(r#"fin de (?:cet |l')?(?:été|ete)"#)?, + |_| helpers::month_day(2, 15)?.span_to(&helpers::month_day(3, 21)?, false) + ); + b.rule_1_terminal("début du printemps", + b.reg(r#"début de (?:cet |l')?(?:été|ete)"#)?, + |_| helpers::month_day(3, 21)?.span_to(&helpers::month_day(4, 15)?, false) + ); + b.rule_1_terminal("milieu du printemps", + b.reg(r#"milieu de (?:cet |l')?(?:été|ete)"#)?, + |_| helpers::month_day(4, 15)?.span_to(&helpers::month_day(5, 15)?, false) + ); + b.rule_1_terminal("fin du printemps", + b.reg(r#"fin de (?:cet |l')?(?:été|ete)"#)?, + |_| helpers::month_day(5, 15)?.span_to(&helpers::month_day(6, 21)?, false) + ); b.rule_2("le ", b.reg(r#"l[ea]"#)?, datetime_check!(|datetime: &DatetimeValue| !datetime.latent), @@ -1072,6 +1125,11 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { datetime_check!(|datetime: &DatetimeValue| !datetime.latent), |_, datetime| Ok(datetime.value().clone().mark_before_start()) ); + b.rule_2("avant ", + b.reg(r#"(?:n[ ']importe quand )?(avant|jusqu'(?:au|[aà]|en))"#)?, + datetime_check!(form!(Form::PartOfDay(_))), + |_, datetime| Ok(datetime.value().clone().mark_before_start()) + ); b.rule_2("après ", b.reg(r#"apr[eè]s"#)?, datetime_check!(|datetime: &DatetimeValue| !datetime.latent), @@ -1083,7 +1141,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { |_, datetime| Ok(datetime.value().clone().mark_after_start()) ); b.rule_2("à partir de ", - b.reg(r#"[aà] partir d['eu](?:l[ 'a])?"#)?, + b.reg(r#"[aà] partir d['eu](?:l[ 'a])?|après"#)?, datetime_check!(form!(Form::PartOfDay(_))), |_, datetime| Ok(datetime.value().clone().mark_after_start()) ); From 74a9d1a6d2dcfc387aa7946846e22eba14a84793 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Thu, 4 Jul 2019 15:50:44 +0200 Subject: [PATCH 006/107] Include prefix + in numbers (all lang) --- grammar/de/src/rules.rs | 23 +++++++++++++++++++++++ grammar/en/src/rules.rs | 23 +++++++++++++++++++++++ grammar/es/src/rules.rs | 23 +++++++++++++++++++++++ grammar/fr/src/rules.rs | 23 +++++++++++++++++++++++ grammar/it/src/rules.rs | 24 +++++++++++++++++++++++- grammar/ja/src/rules.rs | 23 +++++++++++++++++++++++ grammar/ko/src/rules.rs | 23 +++++++++++++++++++++++ grammar/pt/src/rules.rs | 25 +++++++++++++++++++++++-- grammar/zh/src/rules.rs | 24 +++++++++++++++++++++++- 9 files changed, 207 insertions(+), 4 deletions(-) diff --git a/grammar/de/src/rules.rs b/grammar/de/src/rules.rs index 8294311e..21e93243 100644 --- a/grammar/de/src/rules.rs +++ b/grammar/de/src/rules.rs @@ -2339,6 +2339,29 @@ pub fn rules_numbers(b: &mut RuleSetBuilder) -> RustlingResult<()> { } }) }); + b.rule_2("numbers prefix with +, positive", + b.reg(r#"\+"#)?, + number_check!(|number: &NumberValue| !number.prefixed()), + |_, a| -> RuleResult { + Ok(match a.value().clone() { + // checked + NumberValue::Integer(integer) => { + IntegerValue { + prefixed: true, + ..integer + } + .into() + } + NumberValue::Float(float) => { + FloatValue { + prefixed: true, + ..float + } + .into() + } + }) + } + ); b.rule_2("numbers suffixes (K, M, G)", number_check!(|number: &NumberValue| !number.suffixed()), b.reg_neg_lh(r#"([kmg])"#, r#"^[^\W\$€]"#)?, diff --git a/grammar/en/src/rules.rs b/grammar/en/src/rules.rs index d5ff16a6..7c23b20a 100644 --- a/grammar/en/src/rules.rs +++ b/grammar/en/src/rules.rs @@ -2091,6 +2091,29 @@ pub fn rules_numbers(b: &mut RuleSetBuilder) -> RustlingResult<()> { } }) }); + b.rule_2("numbers prefix with +, positive", + b.reg(r#"\+"#)?, + number_check!(|number: &NumberValue| !number.prefixed()), + |_, a| -> RuleResult { + Ok(match a.value().clone() { + // checked + NumberValue::Integer(integer) => { + IntegerValue { + prefixed: true, + ..integer + } + .into() + } + NumberValue::Float(float) => { + FloatValue { + prefixed: true, + ..float + } + .into() + } + }) + } + ); b.rule_2("numbers suffixes (K, M, G)", number_check!(|number: &NumberValue| !number.suffixed()), b.reg_neg_lh(r#"([kmg])"#, r#"^[^\W\$€]"#)?, diff --git a/grammar/es/src/rules.rs b/grammar/es/src/rules.rs index 53830462..638ac263 100644 --- a/grammar/es/src/rules.rs +++ b/grammar/es/src/rules.rs @@ -1703,6 +1703,29 @@ pub fn rules_numbers(b: &mut RuleSetBuilder) -> RustlingResult<()> { } }) }); + b.rule_2("numbers prefix with +, positive", + b.reg(r#"\+"#)?, + number_check!(|number: &NumberValue| !number.prefixed()), + |_, a| -> RuleResult { + Ok(match a.value().clone() { + // checked + NumberValue::Integer(integer) => { + IntegerValue { + prefixed: true, + ..integer + } + .into() + } + NumberValue::Float(float) => { + FloatValue { + prefixed: true, + ..float + } + .into() + } + }) + } + ); b.rule_2("numbers suffixes (K, M, G)", number_check!(|number: &NumberValue| !number.suffixed()), b.reg_neg_lh(r#"([kmg])"#, r#"^[\W\$€]"#)?, diff --git a/grammar/fr/src/rules.rs b/grammar/fr/src/rules.rs index 21976ee0..813f7afc 100644 --- a/grammar/fr/src/rules.rs +++ b/grammar/fr/src/rules.rs @@ -1931,6 +1931,29 @@ pub fn rules_numbers(b: &mut RuleSetBuilder) -> RustlingResult<()> { } }) }); + b.rule_2("numbers prefix with +, positive", + b.reg(r#"\+"#)?, + number_check!(|number: &NumberValue| !number.prefixed()), + |_, a| -> RuleResult { + Ok(match a.value().clone() { + // checked + NumberValue::Integer(integer) => { + IntegerValue { + prefixed: true, + ..integer + } + .into() + } + NumberValue::Float(float) => { + FloatValue { + prefixed: true, + ..float + } + .into() + } + }) + } + ); b.rule_2("numbers suffixes (K, M, G)", number_check!(|number: &NumberValue| !number.suffixed()), b.reg_neg_lh(r#"([kmg])"#, r#"^[\W\$€]"#)?, diff --git a/grammar/it/src/rules.rs b/grammar/it/src/rules.rs index 0916ae2c..d5892d65 100644 --- a/grammar/it/src/rules.rs +++ b/grammar/it/src/rules.rs @@ -2008,7 +2008,29 @@ pub fn rules_numbers(b: &mut RuleSetBuilder) -> RustlingResult<()> { } }) }); - + b.rule_2("numbers prefix with +, positive", + b.reg(r#"\+"#)?, + number_check!(|number: &NumberValue| !number.prefixed()), + |_, a| -> RuleResult { + Ok(match a.value().clone() { + // checked + NumberValue::Integer(integer) => { + IntegerValue { + prefixed: true, + ..integer + } + .into() + } + NumberValue::Float(float) => { + FloatValue { + prefixed: true, + ..float + } + .into() + } + }) + } + ); b.rule_2("numbers suffixes (K, M, G)", number_check!(|number: &NumberValue| !number.suffixed()), b.reg_neg_lh(r#"([kmg])"#, r#"^[\W\$€]"#)?, diff --git a/grammar/ja/src/rules.rs b/grammar/ja/src/rules.rs index 301ff7f1..e61b91e2 100644 --- a/grammar/ja/src/rules.rs +++ b/grammar/ja/src/rules.rs @@ -309,6 +309,29 @@ pub fn rules_numbers(b: &mut RuleSetBuilder) -> RustlingResult<()> { } }) }); + b.rule_2("numbers prefix with +, positive", + b.reg(r#"\+"#)?, + number_check!(|number: &NumberValue| !number.prefixed()), + |_, a| -> RuleResult { + Ok(match a.value().clone() { + // checked + NumberValue::Integer(integer) => { + IntegerValue { + prefixed: true, + ..integer + } + .into() + } + NumberValue::Float(float) => { + FloatValue { + prefixed: true, + ..float + } + .into() + } + }) + } + ); Ok(()) } diff --git a/grammar/ko/src/rules.rs b/grammar/ko/src/rules.rs index 76a35e97..5297659d 100644 --- a/grammar/ko/src/rules.rs +++ b/grammar/ko/src/rules.rs @@ -1537,6 +1537,29 @@ pub fn rules_numbers(b: &mut RuleSetBuilder) -> RustlingResult<()> { }) } ); + b.rule_2("numbers prefix with +, positive", + b.reg(r#"\+"#)?, + number_check!(|number: &NumberValue| !number.prefixed()), + |_, a| -> RuleResult { + Ok(match a.value().clone() { + // checked + NumberValue::Integer(integer) => { + IntegerValue { + prefixed: true, + ..integer + } + .into() + } + NumberValue::Float(float) => { + FloatValue { + prefixed: true, + ..float + } + .into() + } + }) + } + ); b.rule_2("ordinals (첫번째)", integer_check!(), b.reg(r#"번째|째|째번"#)?, diff --git a/grammar/pt/src/rules.rs b/grammar/pt/src/rules.rs index c1354700..a79f09d9 100644 --- a/grammar/pt/src/rules.rs +++ b/grammar/pt/src/rules.rs @@ -2153,8 +2153,29 @@ pub fn rules_numbers(b: &mut RuleSetBuilder) -> RustlingResult<()> { } }) }); - - + b.rule_2("numbers prefix with +, positive", + b.reg(r#"\+"#)?, + number_check!(|number: &NumberValue| !number.prefixed()), + |_, a| -> RuleResult { + Ok(match a.value().clone() { + // checked + NumberValue::Integer(integer) => { + IntegerValue { + prefixed: true, + ..integer + } + .into() + } + NumberValue::Float(float) => { + FloatValue { + prefixed: true, + ..float + } + .into() + } + }) + } + ); b.rule_1_terminal("ordinals (primeiro..9)", b.reg(r#"(primeir|segund|terceir|quart|quint|sext|s[eéè]tim|oitav|non)(?:[oa]s?)?"#)?, |text_match| { diff --git a/grammar/zh/src/rules.rs b/grammar/zh/src/rules.rs index 2ac827df..cd4e9d37 100644 --- a/grammar/zh/src/rules.rs +++ b/grammar/zh/src/rules.rs @@ -810,7 +810,29 @@ pub fn rules_numbers(b: &mut RuleSetBuilder) -> RustlingResult<()> { } }) }); - + b.rule_2("numbers prefix with +, positive", + b.reg(r#"\+"#)?, + number_check!(|number: &NumberValue| !number.prefixed()), + |_, a| -> RuleResult { + Ok(match a.value().clone() { + // checked + NumberValue::Integer(integer) => { + IntegerValue { + prefixed: true, + ..integer + } + .into() + } + NumberValue::Float(float) => { + FloatValue { + prefixed: true, + ..float + } + .into() + } + }) + } + ); b.rule_1_terminal("decimal with thousands separator", b.reg(r#"(\d+(,\d\d\d)+\.\d+)"#)?, |text_match| { From 6c075e4a4844461c4d28c7fcd85818862b25c168 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Wed, 10 Jul 2019 15:45:52 +0200 Subject: [PATCH 007/107] Add training examples (all lang) --- grammar/de/src/training.rs | 8 ++++---- grammar/en/src/training.rs | 8 ++++---- grammar/es/src/training.rs | 8 ++++---- grammar/fr/src/training.rs | 8 ++++---- grammar/it/src/training.rs | 8 ++++---- grammar/ja/src/training.rs | 8 ++++---- grammar/ko/src/training.rs | 4 ++-- grammar/pt/src/training.rs | 8 ++++---- grammar/zh/src/training.rs | 4 ++-- 9 files changed, 32 insertions(+), 32 deletions(-) diff --git a/grammar/de/src/training.rs b/grammar/de/src/training.rs index 3b23e934..8d9f751f 100644 --- a/grammar/de/src/training.rs +++ b/grammar/de/src/training.rs @@ -4,12 +4,12 @@ use rustling_ontology_values::dimension::*; use rustling_ontology_values::ResolverContext; pub fn examples_percentage(v: &mut Vec<::rustling::train::Example>) { - example!(v, check_percentage(15.0), "15%", "fünfzehn prozent", "15 vom Hundert"); + example!(v, check_percentage(15.0), "15%", "fünfzehn prozent", "15 vom Hundert","+15%"); example!(v, check_percentage(3.2), "3,2 %", "drei komma zwei prozent", "3,2 vom Hundert"); } pub fn examples_temperature(v: &mut Vec<::rustling::train::Example>) { - example!(v, check_temperature(3.0, Some("degree")), "3°", "3 °", "plus 3°", "3 ° über null"); + example!(v, check_temperature(3.0, Some("degree")), "3°", "3 °", "plus 3°", "3 ° über null","+3°"); example!(v, check_temperature(71.0, Some("degree")), "71 grad", "71 Grad", "plus 71 Grad", "71 Grad über null", "ein und siebzig Grad"); example!(v, check_temperature(-7.0, Some("degree")), "-7°", "-7 Grad", "minus sieben grad", "7 Grad unter null", "sieben grad unter dem gefrierpunkt", "7 Grad unterm gefrierpunkt"); example!(v, check_temperature(-92.0, None), "zwei und neunzig unter dem gefrierpunkt", "92 unterm gefrierpunkt"); @@ -22,7 +22,7 @@ pub fn examples_temperature(v: &mut Vec<::rustling::train::Example>) } pub fn examples_finance(v: &mut Vec<::rustling::train::Example>) { - example!(v, check_finance(800.0, Some("$"), Precision::Exact), "800 dollar"); + example!(v, check_finance(800.0, Some("$"), Precision::Exact), "800 dollar","+800 dollar"); example!(v, check_finance(2134.0, Some("$"), Precision::Exact), "2.134 $"); example!(v, check_finance(90.0, Some("$"), Precision::Exact), "präzise neunzig $"); example!(v, check_finance(478.0, Some("USD"), Precision::Exact), "478 US-Dollar"); @@ -292,7 +292,7 @@ pub fn examples_time(v: &mut Vec<::rustling::train::Example>) { pub fn examples_numbers(v: &mut Vec<::rustling::train::Example>) { example!(v, check_integer(0), "0", "null"); - example!(v, check_integer(1), "1", "eins"); + example!(v, check_integer(1), "1", "eins", "+1"); example!(v, check_integer(3), "3", "drei"); example!(v, check_integer(30), "30", "dreissig"); example!(v, check_integer(33), "33", "drei und dreissig", "dreiunddreissig", "0033"); diff --git a/grammar/en/src/training.rs b/grammar/en/src/training.rs index f9ea88d1..1482b585 100644 --- a/grammar/en/src/training.rs +++ b/grammar/en/src/training.rs @@ -5,12 +5,12 @@ use rustling_ontology_values::ResolverContext; pub fn examples_percentage(v: &mut Vec<::rustling::train::Example>) { example!(v, check_percentage(0.3), "0.3%", "zero point three per cent"); - example!(v, check_percentage(15.0), "15%", "15 %", "fifteen percent"); + example!(v, check_percentage(15.0), "15%", "15 %", "+15%", "fifteen percent"); example!(v, check_percentage(202.0), "202%", "202 p.c.", "202percent"); } pub fn examples_temperature(v: &mut Vec<::rustling::train::Example>) { - example!(v, check_temperature(3.0, Some("degree")), "three degrees", "3 degrees", "3°", "3 °"); + example!(v, check_temperature(3.0, Some("degree")), "three degrees", "3 degrees", "3°", "+3°", "3 °"); example!(v, check_temperature(32.0, Some("celsius")), "thirty two degrees celsius", "thirty two degrees centigrade", "32°C", "32 °c"); example!(v, check_temperature(-27.0, Some("celsius")), "minus 27 celsius", "-27C", "- 27 c"); example!(v, check_temperature(-5.0, Some("fahrenheit")), "minus five degrees fahrenheit", "-5 °F", "- 5°f"); @@ -20,7 +20,7 @@ pub fn examples_temperature(v: &mut Vec<::rustling::train::Example>) } pub fn examples_finance(v: &mut Vec<::rustling::train::Example>) { - example!(v, check_finance(800.0, Some("$"), Precision::Exact), "800$", "eight hundred dollars", "eight hundred dollar"); + example!(v, check_finance(800.0, Some("$"), Precision::Exact), "800$", "eight hundred dollars", "+800$","eight hundred dollar"); example!(v, check_finance(10.0, Some("USD"), Precision::Approximate), "around ten us dollars", "almost 10US$"); example!(v, check_finance(3.0, Some("AUD"), Precision::Exact), "exactly 3 australian dollar", "precisely 3 AUD"); example!(v, check_finance(0.0, Some("HKD"), Precision::Exact), "zero hk dollar"); @@ -260,7 +260,7 @@ pub fn examples_durations(v: &mut Vec<::rustling::train::Example>) { pub fn examples_numbers(v: &mut Vec<::rustling::train::Example>) { example!(v, check_integer(0), "0", "naught", "nought", "zero", "nil"); - example!(v, check_integer(1), "1", "one", "single"); + example!(v, check_integer(1), "1", "+1", "one", "single"); example!(v, check_integer(2), "2", "two", "a pair"); example!(v, check_integer(33), "33", "thirty three", "0033"); example!(v, check_integer(14), "14", "fourteen"); diff --git a/grammar/es/src/training.rs b/grammar/es/src/training.rs index 21bd9549..4ffda87c 100644 --- a/grammar/es/src/training.rs +++ b/grammar/es/src/training.rs @@ -263,7 +263,7 @@ pub fn examples_durations(v: &mut Vec<::rustling::train::Example>) { } pub fn examples_numbers(v: &mut Vec<::rustling::train::Example>) { - example!(v, check_integer(1), "1", "un", "uno", "una"); + example!(v, check_integer(1), "1", "+1", "un", "uno", "una"); example!(v, check_integer(11), "once"); example!(v, check_integer(17), "diecisiete"); example!(v, check_integer(21), "veintiuno"); @@ -299,12 +299,12 @@ pub fn examples_numbers(v: &mut Vec<::rustling::train::Example>) { pub fn examples_percentage(v: &mut Vec<::rustling::train::Example>) { example!(v, check_percentage(0.3), "0,3%", "cero coma tres por ciento", "cero coma tres porciento"); - example!(v, check_percentage(15.0), "15%", "quince por ciento", "quince porciento"); + example!(v, check_percentage(15.0), "15%", "+15%", "quince por ciento", "quince porciento"); example!(v, check_percentage(355.0), "355 %", "355 por ciento", "355 porciento", "trescientos cincuenta y cinco por ciento"); } pub fn examples_temperature(v: &mut Vec<::rustling::train::Example>) { - example!(v, check_temperature(3.0, Some("degree")), "tres grados", "3 grados", "3°", "3 °"); + example!(v, check_temperature(3.0, Some("degree")), "tres grados", "3 grados", "3°", "+3°", "3 °"); example!(v, check_temperature(32.0, Some("celsius")), "treinta y dos grados celsius", "treinta y dos grados centígrados", "32°C", "32° C", "32° c", "32°c", "32 °c"); example!(v, check_temperature(-27.0, Some("degree")), "menos 27 grados", "27 grados bajo cero","menos veintisiete grados"); example!(v, check_temperature(-27.0, Some("celsius")), "menos 27 grados celsius", "menos 27 grados centigrados", "-27C", "-27°C", "-27° C", "-27°c", "-27° c", "- 27 c"); @@ -314,7 +314,7 @@ pub fn examples_temperature(v: &mut Vec<::rustling::train::Example>) } pub fn examples_finance(v: &mut Vec<::rustling::train::Example>) { - example!(v, check_finance(800.0, Some("$"), Precision::Exact), "800 $", "800$", "ochocientos dólares"); + example!(v, check_finance(800.0, Some("$"), Precision::Exact), "800 $", "+800$", "800$", "ochocientos dólares"); example!(v, check_finance(10.0, Some("$"), Precision::Approximate), "unos diez dólares", "diez dólares más o menos"); example!(v, check_finance(10.0, Some("USD"), Precision::Approximate), "unos diez dólares americanos", "10 USD más o menos", "casi 10US$"); example!(v, check_finance(3.0, Some("AUD"), Precision::Exact), "tres dólares australianos"); diff --git a/grammar/fr/src/training.rs b/grammar/fr/src/training.rs index 3b6f790d..b40994a2 100644 --- a/grammar/fr/src/training.rs +++ b/grammar/fr/src/training.rs @@ -5,12 +5,12 @@ use rustling_ontology_values::ResolverContext; pub fn examples_percentage(v: &mut Vec<::rustling::train::Example>) { example!(v, check_percentage(0.3), "0,3%", "zéro virgule trois pour cent"); - example!(v, check_percentage(15.0), "15%", "quinze pour cent"); + example!(v, check_percentage(15.0), "15%", "+15%", "quinze pour cent"); example!(v, check_percentage(355.0), "355 %", "355 pourcent"); } pub fn examples_temperature(v: &mut Vec<::rustling::train::Example>) { - example!(v, check_temperature(3.0, Some("degree")), "trois degrés", "3 degrés", "3°", "3 °"); + example!(v, check_temperature(3.0, Some("degree")), "trois degrés", "3 degrés", "3°", "+3°", "3 °"); example!(v, check_temperature(32.0, Some("celsius")), "trente deux degrés celsius", "trente deux degrés centigrade", "32°C", "32 °c"); example!(v, check_temperature(-27.0, Some("celsius")), "moins 27 celsius", "-27C", "- 27 c"); example!(v, check_temperature(-5.0, Some("fahrenheit")), "moins cinq degrés fahrenheit", "-5 °F", "- 5°f"); @@ -20,7 +20,7 @@ pub fn examples_temperature(v: &mut Vec<::rustling::train::Example>) } pub fn examples_finance(v: &mut Vec<::rustling::train::Example>) { - example!(v, check_finance(800.0, Some("$"), Precision::Exact), "800 $", "huit cents dollars"); + example!(v, check_finance(800.0, Some("$"), Precision::Exact), "800 $", "+800$", "huit cents dollars"); example!(v, check_finance(10.0, Some("USD"), Precision::Approximate), "environ dix dollars américains", "près de 10 USD", "presque 10US$"); example!(v, check_finance(3.0, Some("AUD"), Precision::Exact), "3 dollars australiens"); example!(v, check_finance(3.5, Some("AUD"), Precision::Exact), "3 dollars australiens et cinquante cents"); @@ -260,7 +260,7 @@ pub fn examples_durations(v: &mut Vec<::rustling::train::Example>) { } pub fn examples_numbers(v: &mut Vec<::rustling::train::Example>) { - example!(v, check_integer(1), "1", "un", "une"); + example!(v, check_integer(1), "1", "+1", "un", "une"); example!(v, check_integer(11), "onze"); example!(v, check_integer(17), "dix sept", "dix-sept"); example!(v, check_integer(21), "vingt et un", "vingt-et-un"); diff --git a/grammar/it/src/training.rs b/grammar/it/src/training.rs index 76dc3ec8..cfdc523f 100644 --- a/grammar/it/src/training.rs +++ b/grammar/it/src/training.rs @@ -269,7 +269,7 @@ pub fn examples_durations(v: &mut Vec<::rustling::train::Example>) { } pub fn examples_numbers(v: &mut Vec<::rustling::train::Example>) { - example!(v, check_integer(1), "1", "un", "uno", "una", "un'"); + example!(v, check_integer(1), "1", "+1", "un", "uno", "una", "un'"); example!(v, check_integer(11), "undici"); example!(v, check_integer(17), "diciassette"); example!(v, check_integer(21), "vent uno"); @@ -302,12 +302,12 @@ pub fn examples_numbers(v: &mut Vec<::rustling::train::Example>) { pub fn examples_percentage(v: &mut Vec<::rustling::train::Example>) { example!(v, check_percentage(0.3), "0,3%", "zero virgola tre per cento", "zero virgola tre percento"); - example!(v, check_percentage(15.0), "15%", "quindici per cento", "quindici percento"); + example!(v, check_percentage(15.0), "15%", "+15%", "quindici per cento", "quindici percento"); example!(v, check_percentage(355.0), "355 %", "355 per cento", "355 percento", "tre cento cinquanta cinque per cento", "tre cento cinquanta cinque percento"); } pub fn examples_temperature(v: &mut Vec<::rustling::train::Example>) { - example!(v, check_temperature(3.0, Some("degree")), "tre gradi", "3 gradi", "3°", "3 °"); + example!(v, check_temperature(3.0, Some("degree")), "tre gradi", "3 gradi", "3°", "+3°", "3 °"); example!(v, check_temperature(32.0, Some("celsius")), "trenta due gradi celsius", "trenta due gradi centigradi", "32°C", "32° C", "32° c", "32°c", "32 °c"); example!(v, check_temperature(-27.0, Some("celsius")), "meno 27 celsius", "meno 27 gradi celsius", "meno venti sette gradi celsius", "-27C", "-27°C", "-27° C", "-27°c", "-27° c", "- 27 c"); example!(v, check_temperature(-5.0, Some("fahrenheit")), "meno cinque gradi fahrenheit", "-5 °F", "-5°F", "-5°f", "-5° f", "- 5°f"); @@ -317,7 +317,7 @@ pub fn examples_temperature(v: &mut Vec<::rustling::train::Example>) } pub fn examples_finance(v: &mut Vec<::rustling::train::Example>) { - example!(v, check_finance(800.0, Some("$"), Precision::Exact), "800 $", "800$", "otto cento dollari"); + example!(v, check_finance(800.0, Some("$"), Precision::Exact), "800 $", "800$", "+800$", "otto cento dollari"); example!(v, check_finance(10.0, Some("$"), Precision::Approximate), "circa dieci dollari", "dieci dollari circa"); example!(v, check_finance(10.0, Some("USD"), Precision::Approximate), "circa dieci dollari americani", "più o meno 10 USD", "quasi 10US$"); example!(v, check_finance(3.0, Some("AUD"), Precision::Exact), "tre dollari australiani"); diff --git a/grammar/ja/src/training.rs b/grammar/ja/src/training.rs index d1375846..0bfbf439 100644 --- a/grammar/ja/src/training.rs +++ b/grammar/ja/src/training.rs @@ -25,7 +25,7 @@ pub fn examples_numbers(v: &mut Vec<::rustling::train::Example>) { example!(v, check_integer(1096), "千九十六"); example!(v, check_integer(40020), "四万二十"); - example!(v, check_float(0.8), "0.8", "0点8", "零点八", "〇点8", "〇.8"); + example!(v, check_float(0.8), "0.8", "+0.8", "0点8", "零点八", "〇点8", "〇.8"); example!(v, check_ordinal(1), "最初", "一番目", "一行目", "一錠目", "一匹目"); example!(v, check_ordinal(7), "七番目", "七体目", "七問目", "七拍子目", "七種目", "七種類目"); @@ -35,7 +35,7 @@ pub fn examples_numbers(v: &mut Vec<::rustling::train::Example>) { } pub fn examples_finance(v: &mut Vec<::rustling::train::Example>) { - example!(v, check_finance(800.0, Some("$"), Precision::Exact), "800$", "800ドル", "八百ドル"); + example!(v, check_finance(800.0, Some("$"), Precision::Exact), "800$", "+800$", "800ドル", "八百ドル"); example!(v, check_finance(10.0, Some("USD"), Precision::Exact), "10アメリカドル", "十米ドル"); example!(v, check_finance(3.0, Some("AUD"), Precision::Exact), "3豪ドル", "三オーストラリアドル"); example!(v, check_finance(0.0, Some("HKD"), Precision::Exact), "0香港ドル", "零香港ドル"); @@ -58,7 +58,7 @@ pub fn examples_finance(v: &mut Vec<::rustling::train::Example>) { pub fn examples_percentage(v: &mut Vec<::rustling::train::Example>) { example!(v, check_percentage(0.3), "0,3 %", "0,3%", "0,3%"); - example!(v, check_percentage(25.0), "25%", "25%", "25パーセント", "二十五パーセント"); + example!(v, check_percentage(25.0), "25%", "+25%","25%", "25パーセント", "二十五パーセント"); example!(v, check_percentage(10.0), "割"); example!(v, check_percentage(1.0), "分"); example!(v, check_percentage(0.1), "厘"); @@ -66,7 +66,7 @@ pub fn examples_percentage(v: &mut Vec<::rustling::train::Example>) { pub fn examples_temperature(v: &mut Vec<::rustling::train::Example>) { example!(v, check_temperature(0.0, Some("degree")), "零度", "0度", "零ど", "0ど", "0 °", "0°"); - example!(v, check_temperature(5.0, Some("degree")), "五度", "5度", "5 °", "5°"); + example!(v, check_temperature(5.0, Some("degree")), "五度", "5度", "5 °", "+5°","5°"); example!(v, check_temperature(6.0, Some("degree")), "六ど", "6ど"); example!(v, check_temperature(14.0, Some("degree")), "14度"); example!(v, check_temperature(25.0, Some("degree")), "二十五度"); diff --git a/grammar/ko/src/training.rs b/grammar/ko/src/training.rs index 7c2b05ef..c330bb51 100644 --- a/grammar/ko/src/training.rs +++ b/grammar/ko/src/training.rs @@ -17,7 +17,7 @@ pub fn examples_finance(v: &mut Vec<::rustling::train::Example>) { } pub fn examples_temperature(v: &mut Vec<::rustling::train::Example>) { - example!(v, check_temperature(37.0, Some("celsius")), "37°C", "섭씨37°", "섭씨37도"); + example!(v, check_temperature(37.0, Some("celsius")), "+37°C", "37°C", "섭씨37°", "섭씨37도"); example!(v, check_temperature(70.0, Some("fahrenheit")), "70°F", "화씨70°", "화씨70도"); example!(v, check_temperature(45.0, Some("degree")), "45°", "45도"); example!(v, check_temperature(-15.0, Some("degree")), "영하 15도"); @@ -276,7 +276,7 @@ pub fn examples_time(v: &mut Vec<::rustling::train::Example>) { pub fn examples_numbers(v: &mut Vec<::rustling::train::Example>) { example!(v, check_integer(0), "0", "영", "빵", "공"); - example!(v, check_integer(1), "1", "일", "하나", "한"); + example!(v, check_integer(1), "1", "+1", "일", "하나", "한"); example!(v, check_integer(10), "10", "십", "열"); example!(v, check_integer(11), "11", "십일", "열하나", "십하나", "열한"); example!(v, check_integer(20), "20", "이십", "스물"); diff --git a/grammar/pt/src/training.rs b/grammar/pt/src/training.rs index 503ec5e6..3f5cd9d9 100644 --- a/grammar/pt/src/training.rs +++ b/grammar/pt/src/training.rs @@ -4,7 +4,7 @@ use rustling_ontology_values::dimension::*; use rustling_ontology_values::ResolverContext; pub fn examples_numbers(v: &mut Vec<::rustling::train::Example>) { - example!(v, check_integer(1), "1", "um", "uma"); + example!(v, check_integer(1), "1", "+1", "um", "uma"); example!(v, check_integer(11), "onze"); example!(v, check_integer(17), "dezessete"); example!(v, check_integer(21), "vinte e um"); @@ -41,12 +41,12 @@ pub fn examples_numbers(v: &mut Vec<::rustling::train::Example>) { pub fn examples_percentage(v: &mut Vec<::rustling::train::Example>) { example!(v, check_percentage(0.3), "0,3%", "zero vírgula três por cento"); - example!(v, check_percentage(15.0), "15%", "quinze por cento"); + example!(v, check_percentage(15.0), "15%", "+15%", "quinze por cento"); example!(v, check_percentage(355.0), "355 %", "355 por cento", "trezentos e cinquenta e cinco por cento"); } pub fn examples_temperature(v: &mut Vec<::rustling::train::Example>) { - example!(v, check_temperature(3.0, Some("degree")), "três graus", "3 graus"); + example!(v, check_temperature(3.0, Some("degree")), "três graus", "+3 graus", "3 graus"); example!(v, check_temperature(32.0, Some("celsius")), "trinta e dois graus celsius", "trinta e dois graus centígrados", "32°C", "32°c"); example!(v, check_temperature(-27.0, Some("degree")), "menos 27 graus", "27 graus abaixo de zero","menos vinte e sete graus"); example!(v, check_temperature(-27.0, Some("celsius")), "menos 27 graus celsius", "menos 27 graus centígrados", "-27°C", "-27°c"); @@ -64,7 +64,7 @@ pub fn examples_finance(v: &mut Vec<::rustling::train::Example>) { example!(v, check_finance(3.5, Some("AUD"), Precision::Exact), "3,5 dólares australianos", "três dólares australianos e cinquenta centavos"); example!(v, check_finance(0.0, Some("HKD"), Precision::Exact), "zero dólares de hong kong"); example!(v, check_finance(125.0, Some("CAD"), Precision::Exact), "125 CAD", "cento e vinte e cinco dólares canadenses"); - example!(v, check_finance(45.0, Some("EUR"), Precision::Exact), "45€", "quarenta e cinco euros"); + example!(v, check_finance(45.0, Some("EUR"), Precision::Exact), "45€", "+45€", "quarenta e cinco euros"); // TODO: Support money amounts with cents dois vírgula cinco euros =/= 2.5 //example!(v, check_finance(2.05, Some("EUR"), Precision::Exact), "2,05€", "dois vírgula cinco euros"); example!(v, check_finance(2.0, Some("£"), Precision::Exact), "2£", "duas libras"); diff --git a/grammar/zh/src/training.rs b/grammar/zh/src/training.rs index 607b3d5d..5e201d61 100644 --- a/grammar/zh/src/training.rs +++ b/grammar/zh/src/training.rs @@ -101,7 +101,7 @@ pub fn examples_time(v: &mut Vec<::rustling::train::Example>) { pub fn examples_temperature(v: &mut Vec<::rustling::train::Example>) { - example!(v, check_temperature(45.0, Some("degree")), "45°", "45度"); + example!(v, check_temperature(45.0, Some("degree")), "45°", "+45°", "45度"); example!(v, check_temperature(45.0, Some("degree")), "45°", "45度"); example!(v, check_temperature(50.0, Some("fahrenheit")), @@ -152,7 +152,7 @@ pub fn examples_temperature(v: &mut Vec<::rustling::train::Example>) pub fn examples_numbers(v: &mut Vec<::rustling::train::Example>) { example!(v, check_integer(0), "0", "〇", "零", "零个", "0个"); - example!(v, check_integer(1), "1", "一", "一个", "1个"); + example!(v, check_integer(1), "1", "+1", "一", "一个", "1个"); example!(v, check_integer(2), "2", "二", "两", "兩", "二個", "二个"); example!(v, check_integer(3), "3", "三"); example!(v, check_integer(4), "4", "四"); From cacdfb9666190663b58e8bd047af7152ae9940cb Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Thu, 18 Jul 2019 11:59:22 +0200 Subject: [PATCH 008/107] Add support for POD 'sunrise', 'middle of the night', and changed hour range for mid-day. --- grammar/fr/src/rules_datetime.rs | 20 ++++++++++++++++++-- grammar/fr/src/training.rs | 2 +- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 987049c0..13650182 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -517,6 +517,13 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { .latent() .form(Form::PartOfDay(PartOfDayForm::Morning))) ); + b.rule_1_terminal("lever du soleil", + b.reg(r#"lever du soleil|aurore|aube"#)?, + |_| Ok(helpers::hour(5, false)? + .span_to(&helpers::hour(9, false)?, false)? + .latent() + .form(Form::PartOfDay(PartOfDayForm::Morning))) + ); b.rule_1_terminal("petit dejeuner", b.reg(r#"petit[- ]d[ée]jeuner"#)?, |_| Ok(helpers::hour(5, false)? @@ -525,7 +532,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { .form(Form::Meal)) ); b.rule_1_terminal("milieu de matinée", - b.reg(r#"milieu de matin[ée]e"#)?, + b.reg(r#"(?:le )?milieu de matin[ée]e"#)?, |_| Ok(helpers::hour(9, false)? .span_to(&helpers::hour(11, false)?, false)? .latent() @@ -659,7 +666,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.rule_1_terminal("milieu de journée", b.reg(r#"(?:milieu de (?:la )?|(?:(?:[àa] )?la )?mi[ -])journ[ée]e"#)?, |_| { - Ok(helpers::hour(11, false)? + Ok(helpers::hour(12, false)? .span_to(&helpers::hour(16, false)?, false)? .latent() .form(Form::PartOfDay(PartOfDayForm::None))) @@ -716,6 +723,15 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { .form(Form::PartOfDay(PartOfDayForm::Night))) } ); + b.rule_1_terminal("milieu de la nuit", + b.reg(r#"(?:le )?milieu de la nuit"#)?, + |_| { + Ok(helpers::hour(2, false)? + .span_to(&helpers::hour(4, false)?, false)? + .latent() + .form(Form::PartOfDay(PartOfDayForm::Night))) + } + ); b.rule_2("a l'heure de ", b.reg(r#"(?:[àa] )?l[' ]heure du|au moment du|pendant l[ea']|au|pour l[ea']|l[ea']"#)?, datetime_check!(|datetime: &DatetimeValue| form!(Form::Meal)(datetime)), diff --git a/grammar/fr/src/training.rs b/grammar/fr/src/training.rs index 1314a0f5..fe5077b3 100644 --- a/grammar/fr/src/training.rs +++ b/grammar/fr/src/training.rs @@ -174,7 +174,7 @@ pub fn examples_datetime(v: &mut Vec<::rustling::train::Example>) { example!(v, check_moment_span!(c, [2013, 2, 12, 12], [2013, 2, 12, 15]), "en début d'après-midi", "en début d'aprem"); example!(v, check_moment_span!(c, [2013, 2, 12, 17], [2013, 2, 12, 19]), "en fin d'après-midi", "en fin d'aprem"); example!(v, check_moment_span!(c, [2013, 2, 12, 6], [2013, 2, 12, 10]), "en début de journée"); - example!(v, check_moment_span!(c, [2013, 2, 12, 11], [2013, 2, 12, 16]), "milieu de journée"); + example!(v, check_moment_span!(c, [2013, 2, 12, 12], [2013, 2, 12, 16]), "milieu de journée"); example!(v, check_moment_span!(c, [2013, 2, 12, 17], [2013, 2, 12, 21]), "en fin de journée"); example!(v, check_moment_span!(c, [2013, 2, 12, 18], [2013, 2, 13, 00]), "ce soir"); example!(v, check_moment_span!(c, [2013, 2, 12, 18], [2013, 2, 12, 21]), "en début de soirée"); From 1ed9f9ccdfef9bbc4ddfb42ea85e6ddbed0e8c01 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Thu, 18 Jul 2019 14:31:02 +0200 Subject: [PATCH 009/107] Add support for POD 'sunset'. --- grammar/fr/src/rules_datetime.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 13650182..fe994eda 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -690,6 +690,15 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { .form(Form::PartOfDay(PartOfDayForm::Evening))) } ); + b.rule_1_terminal("coucher du soleil", + b.reg(r#"coucher du soleil|cr[eé]puscule|tomb[ée]e de la nuit"#)?, + |_| { + Ok(helpers::hour(18, false)? + .span_to(&helpers::hour(21, false)?, false)? + .latent() + .form(Form::PartOfDay(PartOfDayForm::Evening))) + } + ); b.rule_1_terminal("début de soirée", b.reg(r#"d[ée]but de (?:la )?soir[ée]e?"#)?, |_| { @@ -724,7 +733,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { } ); b.rule_1_terminal("milieu de la nuit", - b.reg(r#"(?:le )?milieu de la nuit"#)?, + b.reg(r#"milieu de la nuit"#)?, |_| { Ok(helpers::hour(2, false)? .span_to(&helpers::hour(4, false)?, false)? From 5f42b6c0f6008adaad65c9231b1fc6a4eacaa036 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Thu, 18 Jul 2019 15:17:04 +0200 Subject: [PATCH 010/107] Corrected rule 'lever du soleil', Changed hour range 'aube'. --- grammar/fr/src/rules_datetime.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index fe994eda..6d735903 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -518,9 +518,9 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { .form(Form::PartOfDay(PartOfDayForm::Morning))) ); b.rule_1_terminal("lever du soleil", - b.reg(r#"lever du soleil|aurore|aube"#)?, - |_| Ok(helpers::hour(5, false)? - .span_to(&helpers::hour(9, false)?, false)? + b.reg(r#"lever d[ue] soleil|aurore|aube"#)?, + |_| Ok(helpers::hour(4, false)? + .span_to(&helpers::hour(8, false)?, false)? .latent() .form(Form::PartOfDay(PartOfDayForm::Morning))) ); @@ -693,8 +693,8 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.rule_1_terminal("coucher du soleil", b.reg(r#"coucher du soleil|cr[eé]puscule|tomb[ée]e de la nuit"#)?, |_| { - Ok(helpers::hour(18, false)? - .span_to(&helpers::hour(21, false)?, false)? + Ok(helpers::hour(19, false)? + .span_to(&helpers::hour(22, false)?, false)? .latent() .form(Form::PartOfDay(PartOfDayForm::Evening))) } @@ -1166,10 +1166,15 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { |_, datetime| Ok(datetime.value().clone().mark_after_start()) ); b.rule_2("à partir de ", - b.reg(r#"[aà] partir d['eu](?:l[ 'a])?|après"#)?, + b.reg(r#"[aà] partir d['eu](?:l[ 'a])?"#)?, datetime_check!(form!(Form::PartOfDay(_))), |_, datetime| Ok(datetime.value().clone().mark_after_start()) ); + b.rule_2("après ", + b.reg(r#"après"#)?, + datetime_check!(form!(Form::PartOfDay(_))), + |_, datetime| Ok(datetime.value().clone().mark_after_end()) + ); b.rule_2("après le ", b.reg(r#"apr(?:e|è)s le"#)?, integer_check_by_range!(1, 31), From a357fdc81991f6b27c88c9591a07fc07d5fa16a1 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Thu, 18 Jul 2019 15:52:15 +0200 Subject: [PATCH 011/107] =?UTF-8?q?Added=20rule=20from=20=20t?= =?UTF-8?q?o=20.=20Added=20article=20in=20'fin=20de=20la=20ma?= =?UTF-8?q?tin=C3=A9e'.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- grammar/fr/src/rules_datetime.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 6d735903..09de62fd 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -546,7 +546,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { .form(Form::Meal)) ); b.rule_1_terminal("fin de matinée", - b.reg(r#"fin de matin[ée]e"#)?, + b.reg(r#"fin de (?:la )?matin[ée]e"#)?, |_| Ok(helpers::hour(10, false)? .span_to(&helpers::hour(12, false)?, false)? .latent() @@ -1119,6 +1119,22 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { datetime_check!(form!(Form::TimeOfDay(_))), |_, a, _, b| a.value().smart_span_to(b.value(), false) ); + b.rule_4("de - (interval)", + b.reg(r#"(?:[aà] partir )?d['eu]"#)?, + datetime_check!(form!(Form::PartOfDay(_))), + b.reg(r#"(?:jusqu')?(?:au|[aà])"#)?, + datetime_check!(form!(Form::PartOfDay(_))), + //datetime.value().clone().mark_before_start() + |_, a, _, b| a.value().smart_span_to(b.value(), false) + ); + b.rule_4("de - (interval)", + b.reg(r#"(?:[aà] partir )?d['e]"#)?, + datetime_check!(form!(Form::Meal)), + b.reg(r#"(?:jusqu')?(?:au|[aà])"#)?, + datetime_check!(form!(Form::Meal)), + //datetime.value().clone().mark_before_start() + |_, a, _, b| a.value().smart_span_to(b.value(), false) + ); b.rule_2("de maintenant - (interval)", b.reg(r#"(?:[aà] partir )?de maintenant (?:jusqu')?(?:au|[aà])"#)?, datetime_check!(form!(Form::TimeOfDay(_))), From 3b6bd768f54fa3ea3ecbabfefa52ff514d62910b Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Thu, 18 Jul 2019 17:03:26 +0200 Subject: [PATCH 012/107] Add support for 'last '. --- grammar/fr/src/rules_datetime.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 09de62fd..3a02bd60 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -176,10 +176,15 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { |datetime, _| datetime.value().the_nth_not_immediate(0) ); b.rule_2("au prochain ", - b.reg(r#"(au|[aà] la) prochaine?"#)?, + b.reg(r#"(au |(?:[aà] )?la )prochaine?"#)?, datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), |_, datetime| datetime.value().the_nth_not_immediate(0) ); + b.rule_2("au dernier ", + b.reg(r#"(au |(?:[aà] )?la )dernier?"#)?, + datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), + |_, datetime| datetime.value().the_nth_not_immediate(-1) + ); b.rule_2(" prochain", // The direction check is to avoid application of datetime_check(month) on rule result // "avant " From e3a72e4b7e5820fd432530e6b8692f1e298278d8 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Thu, 18 Jul 2019 17:16:37 +0200 Subject: [PATCH 013/107] Correction rule 'au (prochain|dernier) '. --- grammar/fr/src/rules_datetime.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 3a02bd60..899130e3 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -176,12 +176,12 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { |datetime, _| datetime.value().the_nth_not_immediate(0) ); b.rule_2("au prochain ", - b.reg(r#"(au |(?:[aà] )?la )prochaine?"#)?, + b.reg(r#"(au |(?:[aà] )?l[ae] )prochaine?"#)?, datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), |_, datetime| datetime.value().the_nth_not_immediate(0) ); b.rule_2("au dernier ", - b.reg(r#"(au |(?:[aà] )?la )dernier?"#)?, + b.reg(r#"(au |(?:[aà] )?l[ea] )derni[eè]re?"#)?, datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), |_, datetime| datetime.value().the_nth_not_immediate(-1) ); From 96b61f9abc49d3eae5fb9cc4bd8edac7575416ee Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Thu, 18 Jul 2019 17:58:19 +0200 Subject: [PATCH 014/107] Remove useless comments. --- grammar/fr/src/rules_datetime.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 899130e3..ae504c10 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -1129,7 +1129,6 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { datetime_check!(form!(Form::PartOfDay(_))), b.reg(r#"(?:jusqu')?(?:au|[aà])"#)?, datetime_check!(form!(Form::PartOfDay(_))), - //datetime.value().clone().mark_before_start() |_, a, _, b| a.value().smart_span_to(b.value(), false) ); b.rule_4("de - (interval)", @@ -1137,7 +1136,6 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { datetime_check!(form!(Form::Meal)), b.reg(r#"(?:jusqu')?(?:au|[aà])"#)?, datetime_check!(form!(Form::Meal)), - //datetime.value().clone().mark_before_start() |_, a, _, b| a.value().smart_span_to(b.value(), false) ); b.rule_2("de maintenant - (interval)", From 077023a491882299c9a0a7976daebee2ca87551f Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Mon, 22 Jul 2019 11:11:41 +0200 Subject: [PATCH 015/107] Correction rules 'au dernier ', 'au prochain >datetime>'. --- grammar/fr/src/rules_datetime.rs | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index ae504c10..98123547 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -175,15 +175,25 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.reg(r#"prochaine?"#)?, |datetime, _| datetime.value().the_nth_not_immediate(0) ); - b.rule_2("au prochain ", + // TODO: add restrictions on datetime form? + b.rule_2("au prochain ", b.reg(r#"(au |(?:[aà] )?l[ae] )prochaine?"#)?, - datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), - |_, datetime| datetime.value().the_nth_not_immediate(0) + datetime_check!(|datetime: &DatetimeValue| !form!(Form::PartOfDay(_))(datetime) && !form!(Form::Meal)(datetime)), + |_, a| { + Ok(a.value().the_nth(0)? + .form(a.value().form.clone()) + .datetime_kind(a.value().datetime_kind.clone())) + } ); - b.rule_2("au dernier ", + // TODO: add restrictions on datetime form? + b.rule_2("au dernier ", b.reg(r#"(au |(?:[aà] )?l[ea] )derni[eè]re?"#)?, - datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), - |_, datetime| datetime.value().the_nth_not_immediate(-1) + datetime_check!(|datetime: &DatetimeValue| !form!(Form::PartOfDay(_))(datetime) && !form!(Form::Meal)(datetime)), + |_, a| { + Ok(a.value().the_nth(-1)? + .form(a.value().form.clone()) + .datetime_kind(a.value().datetime_kind.clone())) + } ); b.rule_2(" prochain", // The direction check is to avoid application of datetime_check(month) on rule result From 655149487fb15ea7bd1798ea05ad811c51d799c7 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Mon, 22 Jul 2019 16:02:09 +0200 Subject: [PATCH 016/107] =?UTF-8?q?Add=20support=20for=20'fin=20de=20cette?= =?UTF-8?q?=20ann=C3=A9e'.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- grammar/fr/src/rules_datetime.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 98123547..a9f6202f 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -919,6 +919,15 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.reg(r#"fin de (?:cet |l')?(?:été|ete)"#)?, |_| helpers::month_day(5, 15)?.span_to(&helpers::month_day(6, 21)?, false) ); + b.rule_1_terminal("fin de cette année", + b.reg(r#"fin (?:de (?:l'|cette )|d')?ann[ée]e"#)?, + |_| { + let current_year = helpers::cycle_nth(Grain::Year, 0)?; + let start = current_year.intersect(&helpers::month(10)?)?; + let end = current_year.intersect(&helpers::month(12)?)?; + start.span_to(&end, true) + } + ); b.rule_2("le ", b.reg(r#"l[ea]"#)?, datetime_check!(|datetime: &DatetimeValue| !datetime.latent), From 01e3795e0ed8fbe6c45e310baa2f757a51a2627f Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Mon, 22 Jul 2019 16:22:52 +0200 Subject: [PATCH 017/107] Corrections rules 'end/mid/beg of season'. --- grammar/fr/src/rules_datetime.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index a9f6202f..de31f1d2 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -884,39 +884,39 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { |_| helpers::month_day(8, 15)?.span_to(&helpers::month_day(9, 21)?, false) ); b.rule_1_terminal("début de l'automne", - b.reg(r#"début de (?:cet |l')?(?:été|ete)"#)?, + b.reg(r#"début de (?:cet |l')?automne"#)?, |_| helpers::month_day(9, 21)?.span_to(&helpers::month_day(10, 15)?, false) ); b.rule_1_terminal("milieu de l'automne", - b.reg(r#"milieu de (?:cet |l')?(?:été|ete)"#)?, + b.reg(r#"milieu de (?:cet |l')?automne"#)?, |_| helpers::month_day(10, 15)?.span_to(&helpers::month_day(11, 15)?, false) ); b.rule_1_terminal("fin de l'automne", - b.reg(r#"fin de (?:cet |l')?(?:été|ete)"#)?, + b.reg(r#"fin de (?:cet |l')?automne"#)?, |_| helpers::month_day(11, 15)?.span_to(&helpers::month_day(12, 21)?, false) ); b.rule_1_terminal("début de l'hiver", - b.reg(r#"début de (?:cet |l')?(?:été|ete)"#)?, + b.reg(r#"début de (?:cet |l')?hiver"#)?, |_| helpers::month_day(12, 21)?.span_to(&helpers::month_day(1, 15)?, false) ); b.rule_1_terminal("milieu de l'hiver", - b.reg(r#"milieu de (?:cet |l')?(?:été|ete)"#)?, + b.reg(r#"milieu de (?:cet |l')?hiver"#)?, |_| helpers::month_day(1, 15)?.span_to(&helpers::month_day(2, 15)?, false) ); b.rule_1_terminal("fin de l'hiver", - b.reg(r#"fin de (?:cet |l')?(?:été|ete)"#)?, + b.reg(r#"fin de (?:cet |l')?hiver"#)?, |_| helpers::month_day(2, 15)?.span_to(&helpers::month_day(3, 21)?, false) ); b.rule_1_terminal("début du printemps", - b.reg(r#"début de (?:cet |l')?(?:été|ete)"#)?, + b.reg(r#"début (?:du|de ce)? printemps"#)?, |_| helpers::month_day(3, 21)?.span_to(&helpers::month_day(4, 15)?, false) ); b.rule_1_terminal("milieu du printemps", - b.reg(r#"milieu de (?:cet |l')?(?:été|ete)"#)?, + b.reg(r#"milieu (?:du|de ce)? printemps"#)?, |_| helpers::month_day(4, 15)?.span_to(&helpers::month_day(5, 15)?, false) ); b.rule_1_terminal("fin du printemps", - b.reg(r#"fin de (?:cet |l')?(?:été|ete)"#)?, + b.reg(r#"fin (?:du|de ce)? printemps"#)?, |_| helpers::month_day(5, 15)?.span_to(&helpers::month_day(6, 21)?, false) ); b.rule_1_terminal("fin de cette année", From 6aeb606064d5f6c0da9fdb4e48bab438b4db2b6f Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Mon, 22 Jul 2019 16:39:48 +0200 Subject: [PATCH 018/107] Enlarge coverage rule 'fin du mois'. --- grammar/fr/src/rules_datetime.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index de31f1d2..bb0e323f 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -127,7 +127,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { |_| helpers::cycle_nth(Grain::Day, -1) ); b.rule_1_terminal("fin du mois", - b.reg(r#"(?:(?:[aà] )?la )?fin (?:du|de) mois"#)?, + b.reg(r#"(?:(?:(?:[aà] )?la|en)? )?fin (?:du|de) mois"#)?, |_| { let month = helpers::cycle_nth(Grain::Month, 1)?; Ok(helpers::cycle_nth_after(Grain::Day, -10, &month)? From 19a7ed0ee4ec8ebe4b1947a80da35eea5e5ece88 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Mon, 22 Jul 2019 17:38:53 +0200 Subject: [PATCH 019/107] =?UTF-8?q?Add=20support=20for=20'd=C3=A9but=20de?= =?UTF-8?q?=20l'ann=C3=A9e'.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- grammar/fr/src/rules_datetime.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index bb0e323f..2446330c 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -928,8 +928,18 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { start.span_to(&end, true) } ); + b.rule_1_terminal("début de cette année", + b.reg(r#"d[ée]but (?:de (?:l'|cette )|d')?ann[ée]e"#)?, + |_| { + let current_year = helpers::cycle_nth(Grain::Year, 0)?; + let start = current_year.intersect(&helpers::month(1)?)?; + let end = current_year.intersect(&helpers::month(2)?)?; + start.span_to(&end, true) + } + ); b.rule_2("le ", b.reg(r#"l[ea]"#)?, + //b.reg(r#"l[ea]|en|au|à|pour"#)?, datetime_check!(|datetime: &DatetimeValue| !datetime.latent), |_, a| Ok(a.value().clone()) ); From 020041aa9adfbe60c99ce5aa166f591e6a8c2e86 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Mon, 22 Jul 2019 17:51:23 +0200 Subject: [PATCH 020/107] =?UTF-8?q?Add=20prep=20+=20datetime=20'a|en|au|?= =?UTF-8?q?=C3=A0|pour'.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- grammar/fr/src/rules_datetime.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 2446330c..05fca4ab 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -938,8 +938,8 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { } ); b.rule_2("le ", - b.reg(r#"l[ea]"#)?, - //b.reg(r#"l[ea]|en|au|à|pour"#)?, + //b.reg(r#"l[ea]"#)?, + b.reg(r#"l[ea]|en|au|à|pour"#)?, datetime_check!(|datetime: &DatetimeValue| !datetime.latent), |_, a| Ok(a.value().clone()) ); From 486d7c9321950ead1451c224b0cc2b0b74b01d7e Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Tue, 23 Jul 2019 11:40:17 +0200 Subject: [PATCH 021/107] =?UTF-8?q?Add=20preposition=20'au'=20in=20rule=20?= =?UTF-8?q?'go=C3=BBter'.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- grammar/fr/src/rules_datetime.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 05fca4ab..7c8746f3 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -10,6 +10,11 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { datetime_check!(|datetime: &DatetimeValue| !datetime.latent), |a, b| a.value().intersect(b.value()) ); + b.rule_2("intersect + ", + datetime_check!(form!(Form::DayOfWeek{..})), + datetime_check!(|datetime: &DatetimeValue| !form!(Form::PartOfDay(_))(datetime) && !form!(Form::Meal)(datetime)), + |a, b| a.value().intersect(b.value()) + ); b.rule_3("intersect by 'de' or ','", datetime_check!(|datetime: &DatetimeValue| !datetime.latent), b.reg(r#"de|,"#)?, @@ -642,7 +647,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { } ); b.rule_1_terminal("gouter", - b.reg(r#"(?:(?:[àa] )?l[' ]heure du|au moment du|pendant le|pour le) go[uû]ter"#)?, + b.reg(r#"(?:(?:[àa] )?l[' ]heure du|au(?: moment du)?|pendant le|pour le) go[uû]ter"#)?, |_| Ok(helpers::hour(16, false)? .span_to(&helpers::hour(18, false)?, false)? .form(Form::Meal)) From 1cc5889e6bb6384e1da17fa8e36346ad01fd3c86 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Tue, 23 Jul 2019 11:43:58 +0200 Subject: [PATCH 022/107] Delete redundant rules. One rule for intersection . --- grammar/fr/src/rules_datetime.rs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 7c8746f3..e1b47ae1 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -10,11 +10,6 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { datetime_check!(|datetime: &DatetimeValue| !datetime.latent), |a, b| a.value().intersect(b.value()) ); - b.rule_2("intersect + ", - datetime_check!(form!(Form::DayOfWeek{..})), - datetime_check!(|datetime: &DatetimeValue| !form!(Form::PartOfDay(_))(datetime) && !form!(Form::Meal)(datetime)), - |a, b| a.value().intersect(b.value()) - ); b.rule_3("intersect by 'de' or ','", datetime_check!(|datetime: &DatetimeValue| !datetime.latent), b.reg(r#"de|,"#)?, @@ -766,11 +761,6 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { datetime_check!(|datetime: &DatetimeValue| form!(Form::Meal)(datetime)), |_, a| Ok(a.value().clone().not_latent()) ); - b.rule_2(" ", - datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), - datetime_check!(form!(Form::Meal)), - |a, b| a.value().intersect(b.value()) - ); b.rule_2("prep? & article ", // This is very catch-all/junky b.reg(r#"(?:pendant |durant |dans |d[eè]s )?l[ae']?|en|au"#)?, datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime)), @@ -784,7 +774,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { .form(datetime.value().form.clone()) .datetime_kind(DatetimeKind::DatetimeComplement { date_and_time: true, today: true })) ); - b.rule_2(" ", + b.rule_2("intersect ", datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), |a, b| a.value().intersect(b.value()) From 84b5289c2eee027fb65592b2d986c43842b9770d Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Tue, 23 Jul 2019 15:01:40 +0200 Subject: [PATCH 023/107] =?UTF-8?q?Add=20rule=20interval=20=20-=20=20to=20support=20'en?= =?UTF-8?q?tre=204H=20et=20le=20d=C3=AEner'=20for=20example.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- grammar/fr/src/rules_datetime.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index e1b47ae1..db8e5cb6 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -1118,6 +1118,34 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), |_, a, _, b| a.value().span_to(b.value(), true) ); + b.rule_4("entre et (interval)", + b.reg(r#"entre"#)?, + datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), + b.reg(r#"et"#)?, + datetime_check!(|datetime: &DatetimeValue| form!(Form::TimeOfDay(_))(datetime)), + |_, a, _, b| a.value().span_to(b.value(), true) + ); + b.rule_4("entre et (interval)", + b.reg(r#"d[eu]"#)?, + datetime_check!(|datetime: &DatetimeValue| form!(Form::TimeOfDay(_))(datetime)), + b.reg(r#"(?:jusqu')?(?:à|au)"#)?, + datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), + |_, a, _, b| a.value().span_to(b.value(), true) + ); + b.rule_4("entre et (interval)", + b.reg(r#"d[eu]"#)?, + datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), + b.reg(r#"(?:jusqu')?(?:à|au)"#)?, + datetime_check!(|datetime: &DatetimeValue| form!(Form::TimeOfDay(_))(datetime)), + |_, a, _, b| a.value().span_to(b.value(), true) + ); + b.rule_4("entre et (interval)", + b.reg(r#"entre"#)?, + datetime_check!(|datetime: &DatetimeValue| form!(Form::TimeOfDay(_))(datetime)), + b.reg(r#"et"#)?, + datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), + |_, a, _, b| a.value().span_to(b.value(), true) + ); // Specific case with years b.rule_5("de - (interval)", b.reg(r#"depuis|d[e'u]?"#)?, From 586d80dc93193eb19ee3cc37b9c345e581b3a6fd Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Tue, 23 Jul 2019 15:14:53 +0200 Subject: [PATCH 024/107] =?UTF-8?q?Correction=20rule=20'entre=20?= =?UTF-8?q?=20et=20'=20to=20support=20'=C3=A0=20partir=20de'.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- grammar/fr/src/rules_datetime.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index db8e5cb6..31f33370 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -706,7 +706,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { } ); b.rule_1_terminal("coucher du soleil", - b.reg(r#"coucher du soleil|cr[eé]puscule|tomb[ée]e de la nuit"#)?, + b.reg(r#"coucher d[eu] soleil|cr[eé]puscule|tomb[ée]e de la nuit"#)?, |_| { Ok(helpers::hour(19, false)? .span_to(&helpers::hour(22, false)?, false)? @@ -1126,14 +1126,14 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { |_, a, _, b| a.value().span_to(b.value(), true) ); b.rule_4("entre et (interval)", - b.reg(r#"d[eu]"#)?, + b.reg(r#"(?:[aà] partir )?d[eu]"#)?, datetime_check!(|datetime: &DatetimeValue| form!(Form::TimeOfDay(_))(datetime)), b.reg(r#"(?:jusqu')?(?:à|au)"#)?, datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), |_, a, _, b| a.value().span_to(b.value(), true) ); b.rule_4("entre et (interval)", - b.reg(r#"d[eu]"#)?, + b.reg(r#"(?:[aà] partir )?d[eu]"#)?, datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), b.reg(r#"(?:jusqu')?(?:à|au)"#)?, datetime_check!(|datetime: &DatetimeValue| form!(Form::TimeOfDay(_))(datetime)), From ca9eda4d8863d8b13e84b0104bb5cc4f088d9596 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Tue, 23 Jul 2019 16:13:48 +0200 Subject: [PATCH 025/107] Add reverse order for rule . --- grammar/fr/src/rules_datetime.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 31f33370..17a87c51 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -779,6 +779,11 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), |a, b| a.value().intersect(b.value()) ); + b.rule_2("intersect ", + datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), + datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), + |a, b| a.value().intersect(b.value()) + ); b.rule_2(" du matin", datetime_check!(form!(Form::TimeOfDay(_))), b.reg(r#"(?:(?:du|dans|de) )?(?:(?:au|le|la) )?mat(?:in[ée]?e?)?(?: pile| exactement| pr[eé]cises?)?"#)?, @@ -1212,7 +1217,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { |_, a, _, b| a.value().smart_span_to(b.value(), false) ); b.rule_2("jusqu'à ", - b.reg(r#"(?:n[ ']importe quand )?jusqu'(?:au|[aà]|en)"#)?, + b.reg(r#"(?:n[ ']importe quand )?jusqu'(?:au|[aà]|en)?"#)?, datetime_check!(|datetime: &DatetimeValue| !datetime.latent), |_, datetime| Ok(datetime.value().clone().mark_before_end()) ); From f0ddf29a2fd816ae2c19f532051a68e5beeee98c Mon Sep 17 00:00:00 2001 From: Rosa Stern Date: Sun, 9 Jun 2019 16:02:53 +0200 Subject: [PATCH 026/107] Add moment and values utils to improve grammar. --- moment/src/period.rs | 8 ++++++++ values/src/dimension.rs | 20 ++++++++++++++++++++ values/src/helpers.rs | 19 ++++++++++--------- 3 files changed, 38 insertions(+), 9 deletions(-) diff --git a/moment/src/period.rs b/moment/src/period.rs index 79412424..fa7a7683 100644 --- a/moment/src/period.rs +++ b/moment/src/period.rs @@ -151,6 +151,14 @@ impl Period { .and_then(|(g, _)| Grain::from_usize(g)) } + pub fn coarser_grain(&self) -> Option { + use enum_primitive::FromPrimitive; + self.0 + .iter() + .min_by_key(|&(g, _)| g) + .and_then(|(g, _)| Grain::from_usize(g)) + } + pub fn comps(&self) -> Vec { use enum_primitive::FromPrimitive; self.0.iter() diff --git a/values/src/dimension.rs b/values/src/dimension.rs index 8c1dc342..3043cddd 100644 --- a/values/src/dimension.rs +++ b/values/src/dimension.rs @@ -642,6 +642,22 @@ impl Form { &Form::Span => None, } } + + pub fn is_day(&self) -> bool { + match self { + &Form::Cycle(grain) => { + match grain { + Grain::Day => true, + _ => false, + } + } + &Form::MonthDay(_) => true, + &Form::DayOfWeek { .. } => true, + &Form::DayOfMonth => true, + &Form::Celebration => true, + _ => false, + } + } } #[derive(Debug, Clone, Copy, PartialEq)] @@ -837,6 +853,10 @@ impl DurationValue { self.period.finer_grain().unwrap_or(Grain::Second) } + pub fn get_coarser_grain(&self) -> Grain { + self.period.coarser_grain().unwrap_or(Grain::Second) + } + pub fn from_addition(self, from_addition: FromAddition) -> DurationValue { DurationValue { from_addition: Some(from_addition), .. self} } diff --git a/values/src/helpers.rs b/values/src/helpers.rs index 9e4181f6..4d759886 100644 --- a/values/src/helpers.rs +++ b/values/src/helpers.rs @@ -403,6 +403,7 @@ impl MomentToRuleError for MomentResult { } pub fn year(y: i32) -> RuleResult { + // year between 0 and 99 will be normalized after 1950, e.g. 45 => 2045, 60 => 1960, 99 => 1999 let y = normalize_year(y)?; Ok(DatetimeValue::constraint(Year::new(y)).form(Form::Year(y))) } @@ -522,13 +523,19 @@ pub fn cycle_n_not_immediate(grain: Grain, n: i64) -> RuleResult Ok(DatetimeValue::constraint(Cycle::rc(grain).take_not_immediate(n)).form(Form::Cycle(grain))) } +pub fn weekend() -> RuleResult { + let friday = day_of_week(Weekday::Fri)?.intersect(&hour(18, false)?)?; + let monday = day_of_week(Weekday::Mon)?.intersect(&hour(0, false)?)?; + Ok(friday.span_to(&monday, false)?.datetime_kind(DatetimeKind::DatePeriod)) +} pub fn easter() -> RuleResult { fn offset(i: &Interval, _: &Context) -> Option> { let (year, month, day) = computer_easter(i.start.year()); Some(Interval::ymd(year, month, day)) } - Ok(DatetimeValue::constraint(Month::new(3).invalid_if_err()?.translate_with(offset))) + Ok(DatetimeValue::constraint(Month::new(3).invalid_if_err()?.translate_with(offset)) + .datetime_kind(DatetimeKind::Date)) // otherwise grain is Month; not the cleanest but does the job } pub fn computer_easter(year: i32) -> (i32, u32, u32) { @@ -569,10 +576,7 @@ impl DurationValue { pub fn in_present(&self) -> RuleResult { self.check_period()?; let grain = self.get_grain(); - let datetime_kind = match grain.is_date_grain() { - true => DatetimeKind::Date, - false => DatetimeKind::Time, - }; + let datetime_kind = if grain.is_date_grain() { DatetimeKind::Date } else { DatetimeKind::Time }; Ok(DatetimeValue::constraint(Cycle::rc(Grain::Second) .take_the_nth(0) .shift_by(self.period.clone())).precision(self.precision) @@ -582,10 +586,7 @@ impl DurationValue { pub fn in_present_day(&self) -> RuleResult { self.check_period()?; let grain = self.get_grain(); - let datetime_kind = match grain.is_date_grain() { - true => DatetimeKind::Date, - false => DatetimeKind::Time, - }; + let datetime_kind = if grain.is_date_grain() { DatetimeKind::Date } else { DatetimeKind::Time }; Ok(DatetimeValue::constraint(Cycle::rc(Grain::Day) .take_the_nth(0) .shift_by(self.period.clone())).precision(self.precision) From 36ceb2cdb56b9065a5759a7a74358c2711dcb850 Mon Sep 17 00:00:00 2001 From: Rosa Stern Date: Sun, 9 Jun 2019 16:03:41 +0200 Subject: [PATCH 027/107] Improve French grammar for new Datetime specs (1/2). --- grammar/fr/src/rules.rs | 666 ++++++++++++++++++++++++++----------- grammar/fr/src/training.rs | 22 +- 2 files changed, 488 insertions(+), 200 deletions(-) diff --git a/grammar/fr/src/rules.rs b/grammar/fr/src/rules.rs index deb3e149..aa65cbe2 100644 --- a/grammar/fr/src/rules.rs +++ b/grammar/fr/src/rules.rs @@ -311,6 +311,9 @@ pub fn rules_duration(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.reg(r#"exactement|précisément|pile"#)?, |duration, _| Ok(duration.value().clone().precision(Precision::Exact)) ); + // Ambiguous w/ time-of-day w/ "pour" + // Duration has less priority than Datetime types, therefore duration will be output only + // if the output kind filter is set for Duration b.rule_2("pendant ", b.reg(r#"pendant|durant|pour"#)?, duration_check!(), @@ -321,30 +324,6 @@ pub fn rules_duration(b: &mut RuleSetBuilder) -> RustlingResult<()> { duration_check!(), |_, duration| Ok(duration.value().clone().prefixed()) ); - b.rule_2("il y a ", - b.reg(r#"il y a"#)?, - duration_check!(), - |_, duration| duration.value().ago() - ); - b.rule_2("depuis ", - b.reg(r#"depuis|[cç]a fait"#)?, - duration_check!(), - |_, duration| { - duration.value().ago()? - .span_to(&helpers::cycle_nth(Grain::Second, 0)?, false) - }); - b.rule_3(" apres ", - duration_check!(), - b.reg(r#"apr[eè]s"#)?, - datetime_check!(), - |duration, _, datetime| duration.value().after(datetime.value()) - ); - b.rule_3(" avant ", - duration_check!(), - b.reg(r#"avant"#)?, - datetime_check!(), - |duration, _, datetime| duration.value().before(datetime.value()) - ); Ok(()) } @@ -373,6 +352,10 @@ pub fn rules_cycle(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.reg(r#"mois"#)?, |_| CycleValue::new(Grain::Month) ); + b.rule_1_terminal("trimestre (cycle)", + b.reg(r#"trimestre"#)?, + |_| CycleValue::new(Grain::Quarter) + ); b.rule_1("année (cycle)", b.reg(r#"an(?:n[ée]e?)?s?"#)?, |_| CycleValue::new(Grain::Year) @@ -381,8 +364,9 @@ pub fn rules_cycle(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.reg(r#"trimestres?"#)?, |_| CycleValue::new(Grain::Quarter) ); + // Cycle patterns relative to now b.rule_2("ce|dans le ", - b.reg(r#"(?:cet?t?e?s?)|(?:dans l[ae']? ?)"#)?, + b.reg(r#"(?:(?:dans )?l[ea' ]|cet?(?:te)?)"#)?, cycle_check!(), |_, cycle| helpers::cycle_nth(cycle.value().grain, 0) ); @@ -392,41 +376,158 @@ pub fn rules_cycle(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.reg(r#"-?ci"#)?, |_, cycle, _| helpers::cycle_nth(cycle.value().grain, 0) ); - + b.rule_2(" dernier", + cycle_check!(), + b.reg(r#"derni[èe]re?|pass[ée]e?|pr[eé]c[eé]dente?|(?:d')? ?avant"#)?, + |cycle, _| helpers::cycle_nth(cycle.value().grain, -1) + ); b.rule_3("le dernier", b.reg(r#"l[ae']? ?"#)?, cycle_check!(), b.reg(r#"derni[èe]re?|pass[ée]e?"#)?, |_, cycle, _| helpers::cycle_nth(cycle.value().grain, -1) ); + b.rule_3("n derniers ", + integer_check_by_range!(2, 9999), + b.reg(r#"derni.re?s?"#)?, + cycle_check!(), + |integer, _, cycle| { + let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, -1 * integer.value().value)?; + // All grains except Day will trigger the right datetime_kind + if cycle.value().grain == Grain::Day { + res = res.datetime_kind(DatetimeKind::DatePeriod); + } + Ok(res) + } + ); + b.rule_4("(pendant/durant/dans) les n derniers ", + b.reg(r#"(?:pendant |durant |dans )?[cld]es"#)?, + integer_check_by_range!(2, 9999), + b.reg(r#"derni.re?s?"#)?, + cycle_check!(), + |_, integer, _, cycle| { + let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, -1 * integer.value().value)?; + // All grains except Day will trigger the right datetime_kind + if cycle.value().grain == Grain::Day { + res = res.datetime_kind(DatetimeKind::DatePeriod); + } + Ok(res) + } + ); + b.rule_3("n passes|precedents", + integer_check_by_range!(2, 9999), + cycle_check!(), + b.reg(r#"pass[eèé][eèé]?s?|pr[eé]c[eé]dente?s?|(?:d')? ?avant|plus t[oô]t"#)?, + |integer, cycle, _| { + let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, -1 * integer.value().value)?; + // All grains except Day will trigger the right datetime_kind + if cycle.value().grain == Grain::Day { + res = res.datetime_kind(DatetimeKind::DatePeriod); + } + Ok(res) + } + ); + b.rule_4("(pendant/durant/dans) les n passes|precedents", + b.reg(r#"(?:pendant |durant |dans )?[cld]es"#)?, + integer_check_by_range!(2, 9999), + cycle_check!(), + b.reg(r#"pass[eèé][eèé]?s?|pr[eé]c[eé]dente?s?|(?:d')? ?avant|plus t[oô]t"#)?, + |_, integer, cycle, _| helpers::cycle_n_not_immediate(cycle.value().grain, -1 * integer.value().value) + ); + // Incorrect resolution if some follows the expression, + // e.g. "suivant le " (unsupported) + b.rule_2(" prochain|suivant|d'après", + cycle_check!(), + b.reg(r#"prochaine?|suivante?|qui suit|(?:d')? ?apr[eèé]s"#)?, + |cycle, _| helpers::cycle_nth(cycle.value().grain, 1) + ); b.rule_3("le prochain|suivant|d'après", b.reg(r#"l[ae']? ?|une? ?"#)?, cycle_check!(), b.reg(r#"prochaine?|suivante?|qui suit|(?:d'? ?)?apr[eèé]s"#)?, |_, cycle, _| helpers::cycle_nth(cycle.value().grain, 1) ); - b.rule_2(" dernier", + b.rule_3("n prochains ", + integer_check_by_range!(2, 9999), + b.reg(r#"prochaine?s?|suivante?s?|apr[eèé]s"#)?, cycle_check!(), - b.reg(r#"derni[èe]re?|pass[ée]e?|pr[eé]c[eé]dente?|(?:d')? ?avant"#)?, - |cycle, _| helpers::cycle_nth(cycle.value().grain, -1) + |integer, _, cycle| { + let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, integer.value().value)?; + // All grains except Day will trigger the right datetime_kind + if cycle.value().grain == Grain::Day { + res = res.datetime_kind(DatetimeKind::DatePeriod); + } + Ok(res) + } ); - b.rule_2(" prochain|suivant|d'après", + b.rule_4("(pendant/durant/dans) les n prochains ", + b.reg(r#"(?:pendant |durant |dans )?[cld]es"#)?, + integer_check_by_range!(2, 9999), + b.reg(r#"prochaine?s?|suivante?s?|apr[eèé]s"#)?, cycle_check!(), - b.reg(r#"prochaine?|suivante?|qui suit|(?:d')? ?apr[eèé]s"#)?, - |cycle, _| helpers::cycle_nth(cycle.value().grain, 1) + |_, integer, _, cycle| { + let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, integer.value().value)?; + // All grains except Day will trigger the right datetime_kind + if cycle.value().grain == Grain::Day { + res = res.datetime_kind(DatetimeKind::DatePeriod); + } + Ok(res) + } + ); + b.rule_3("n suivants", + integer_check_by_range!(2, 9999), + cycle_check!(), + b.reg(r#"prochaine?s?|suivante?s?|apr[eèé]s|qui sui(?:t|ves?)|plus tard"#)?, + |integer, cycle, _| { + let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, integer.value().value)?; + // All grains except Day will trigger the right datetime_kind + if cycle.value().grain == Grain::Day { + res = res.datetime_kind(DatetimeKind::DatePeriod); + } + Ok(res) + } + ); + b.rule_4("(pendant/durant/dans) les n suivants", + b.reg(r#"(?:pendant |durant |dans )?[cld]es"#)?, + integer_check_by_range!(2, 9999), + cycle_check!(), + b.reg(r#"prochaine?s?|suivante?s?|apr[eèé]s|qui sui(?:t|ves?)|plus tard"#)?, + |_, integer, cycle, _| { + let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, integer.value().value)?; + // All grains except Day will trigger the right datetime_kind + if cycle.value().grain == Grain::Day { + res = res.datetime_kind(DatetimeKind::DatePeriod); + } + Ok(res) + } ); b.rule_3("n avant", integer_check_by_range!(2, 9999), cycle_check!(), b.reg(r#"(?:d')? ?avant|plus t[oô]t"#)?, - |integer, cycle, _| helpers::cycle_nth(cycle.value().grain, -1 * integer.value().value) + |integer, cycle, _| { + let mut res = helpers::cycle_nth(cycle.value().grain, -1 * integer.value().value)?; + // All grains except Day will trigger the right datetime_kind + if cycle.value().grain == Grain::Day { + res = res.datetime_kind(DatetimeKind::DatePeriod); + } + Ok(res) + } ); b.rule_3("n après", integer_check_by_range!(2, 9999), cycle_check!(), b.reg(r#"(?:d')? ?apr[eèé]s|qui sui(?:t|ves?)|plus tard"#)?, - |integer, cycle, _| helpers::cycle_nth(cycle.value().grain, integer.value().value) + |integer, cycle, _| { + let mut res = helpers::cycle_nth(cycle.value().grain, integer.value().value)?; + // All grains except Day will trigger the right datetime_kind + if cycle.value().grain == Grain::Day { + res = res.datetime_kind(DatetimeKind::DatePeriod); + } + Ok(res) + } ); + // Cycle patterns relative to another datetime b.rule_4("le après|suivant ", b.reg(r#"l[ea']? ?"#)?, cycle_check!(), @@ -441,59 +542,6 @@ pub fn rules_cycle(b: &mut RuleSetBuilder) -> RustlingResult<()> { datetime_check!(), |_, cycle, _, datetime| helpers::cycle_nth_after(cycle.value().grain, -1, datetime.value()) ); - b.rule_4("les n derniers ", - b.reg(r#"[cld]es"#)?, - integer_check_by_range!(2, 9999), - b.reg(r#"derni.re?s?"#)?, - cycle_check!(), - |_, integer, _, cycle| helpers::cycle_n_not_immediate(cycle.value().grain, -1 * integer.value().value) - ); - b.rule_3("n derniers ", - integer_check_by_range!(2, 9999), - b.reg(r#"derni.re?s?"#)?, - cycle_check!(), - |integer, _, cycle| helpers::cycle_n_not_immediate(cycle.value().grain, -1 * integer.value().value) - ); - - b.rule_4("les n prochains ", - b.reg(r#"[cld]es"#)?, - integer_check_by_range!(2, 9999), - b.reg(r#"prochaine?s?|suivante?s?|apr[eèé]s"#)?, - cycle_check!(), - |_, integer, _, cycle| helpers::cycle_n_not_immediate(cycle.value().grain, integer.value().value) - ); - b.rule_3("n prochains ", - integer_check_by_range!(2, 9999), - b.reg(r#"prochaine?s?|suivante?s?|apr[eèé]s"#)?, - cycle_check!(), - |integer, _, cycle| helpers::cycle_n_not_immediate(cycle.value().grain, integer.value().value) - ); - b.rule_4("les n passes|precedents", - b.reg(r#"[cld]es"#)?, - integer_check_by_range!(2, 9999), - cycle_check!(), - b.reg(r#"pass[eèé][eèé]?s?|pr[eé]c[eé]dente?s?|(?:d')? ?avant|plus t[oô]t"#)?, - |_, integer, cycle, _| helpers::cycle_n_not_immediate(cycle.value().grain, -1 * integer.value().value) - ); - b.rule_3("n passes|precedents", - integer_check_by_range!(2, 9999), - cycle_check!(), - b.reg(r#"pass[eèé][eèé]?s?|pr[eé]c[eé]dente?s?|(?:d')? ?avant|plus t[oô]t"#)?, - |integer, cycle, _| helpers::cycle_n_not_immediate(cycle.value().grain, -1 * integer.value().value) - ); - b.rule_4("les n suivants", - b.reg(r#"[cld]es"#)?, - integer_check_by_range!(2, 9999), - cycle_check!(), - b.reg(r#"prochaine?s?|suivante?s?|apr[eèé]s|qui sui(?:t|ves?)|plus tard"#)?, - |_, integer, cycle, _| helpers::cycle_n_not_immediate(cycle.value().grain, integer.value().value) - ); - b.rule_3("n suivants", - integer_check_by_range!(2, 9999), - cycle_check!(), - b.reg(r#"prochaine?s?|suivante?s?|apr[eèé]s|qui sui(?:t|ves?)|plus tard"#)?, - |integer, cycle, _| helpers::cycle_n_not_immediate(cycle.value().grain, integer.value().value) - ); b.rule_4(" de ", ordinal_check_by_range!(1, 9999), cycle_check!(), @@ -521,7 +569,7 @@ pub fn rules_cycle(b: &mut RuleSetBuilder) -> RustlingResult<()> { datetime_check!(), |_, datetime| helpers::cycle_nth_after_not_immediate(Grain::Day, 1, datetime.value()) ); - b.rule_2("le veille du ", + b.rule_2("la veille du ", b.reg(r#"(la )?veille du"#)?, datetime_check!(), |_, datetime| helpers::cycle_nth_after_not_immediate(Grain::Day, -1, datetime.value()) @@ -618,7 +666,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { |_| helpers::month(8) ); b.rule_1_terminal("named-month", -// b.reg(r#"septembre|sept?\.?"#)?, // "sept" with no dot forbidden (confusion with nb "sept" in "à trois heures trente sept") +// b.reg(r#"septembre|sept?\.?"#)?, // "sept" with no dot forbidden (confusion with nb "sept" in "à trois heures trente sept") b.reg(r#"septembre|sept\.|sep\.?"#)?, |_| helpers::month(9) ); @@ -635,11 +683,11 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { |_| helpers::month(12) ); b.rule_1_terminal("noel", - b.reg(r#"(?:jour de )?no[eë]l"#)?, + b.reg(r#"(?:(?:le )?jour de )?no[eë]l"#)?, |_| Ok(helpers::month_day(12, 25)?.form(Form::Celebration)) ); b.rule_1_terminal("soir de noël", - b.reg(r#"(soir(?:ée)?|veille) de no[eë]l"#)?, + b.reg(r#"(?:l[ea] )?(?:soir(?:ée)?|veille|r[eé]veillon) de no[eë]l"#)?, |_| { let start = helpers::month_day(12, 24)?.intersect(&helpers::hour(18, false)?)?; let end = helpers::month_day(12, 25)?.intersect(&helpers::hour(0, false)?)?; @@ -647,6 +695,10 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { .form(Form::Celebration)) } ); + b.rule_1_terminal("saint sylvestre", + b.reg(r#"(?:l[ea] )?(?:saint[- ]sylvestre|r[eé]veillon)"#)?, + |_| Ok(helpers::month_day(12, 31)?.form(Form::Celebration)) + ); b.rule_1_terminal("jour de l'an", b.reg(r#"(?:le )?(?:jour de l'|nouvel )an"#)?, |_| Ok(helpers::month_day(1, 1)?.form(Form::Celebration)) @@ -693,8 +745,8 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { .form(Form::Celebration)) ); - b.rule_1_terminal("pencôte", - b.reg(r#"(?:la f[eê]te de la |la |le lundi de la )?penc[oô]te"#)?, + b.rule_1_terminal("pentecôte", + b.reg(r#"(?:la f[eê]te de |(?:le )?lundi de )?(?:la )?pentec[oô]te"#)?, |_| Ok(helpers::cycle_nth_after(Grain::Day, 49, &helpers::easter()?)? .form(Form::Celebration)) ); @@ -731,14 +783,21 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { |_| Ok(helpers::month_day(8, 15)? .form(Form::Celebration)) ); + b.rule_2("à ", + b.reg(r#"au|[aà](?:l['a])?"#)?, + datetime_check!(form!(Form::Celebration)), + |_, a| Ok(a.value().clone()) + ); b.rule_1_terminal("maintenant", - b.reg(r#"maintenant|(?:tout de suite)"#)?, + b.reg(r#"maintenant|tout de suite|en ce moment"#)?, |_| helpers::cycle_nth(Grain::Second, 0) ); b.rule_1_terminal("aujourd'hui", - b.reg(r#"(?:aujourd'? ?hui)|(?:ce jour)|(?:dans la journ[ée]e?)|(?:en ce moment)"#)?, + b.reg(r#"(?:aujourd'? ?hui)|(?:ce jour)|(?:dans la journ[ée]e?)"#)?, |_| helpers::cycle_nth(Grain::Day, 0) ); + // FIXME: "le lendemain" interpreted as demain, not as relative to another date + // but there is a rule "le lendemain du " - inconsistent b.rule_1_terminal("demain", b.reg(r#"(?:demain)|(?:le lendemain)"#)?, |_| helpers::cycle_nth(Grain::Day, 1) @@ -773,17 +832,30 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.rule_2("ce ", b.reg(r#"ce"#)?, datetime_check!(), - |_, datetime| datetime.value().the_nth(0) + |_, datetime| Ok(datetime.value().the_nth(0)? + .datetime_kind(datetime.value().datetime_kind.clone())) ); b.rule_2(" prochain", datetime_check!(form!(Form::DayOfWeek{..})), b.reg(r#"prochain"#)?, |datetime, _| datetime.value().the_nth_not_immediate(0) ); + b.rule_2(" prochain", + datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), + b.reg(r#"prochaine?"#)?, + |datetime, _| datetime.value().the_nth_not_immediate(0) + ); + b.rule_2("au prochain ", + b.reg(r#"(au|[aà] la) prochaine?"#)?, + datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), + |_, datetime| datetime.value().the_nth_not_immediate(0) + ); b.rule_2(" prochain", - datetime_check!(), + // The direction check is to avoid application of datetime_check(month) on rule result + // "avant " + datetime_check!(|datetime: &DatetimeValue| form!(Form::Month(_))(datetime) && !datetime.direction.is_some()), b.reg(r#"prochain"#)?, - |datetime, _| datetime.value().the_nth(1) + |datetime, _| datetime.value().the_nth_not_immediate(0) ); b.rule_2(" suivant|d'après", datetime_check!(), @@ -833,45 +905,41 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.reg(r#"week(?:\s|-)?end (?:d['eu]|en|du mois de)"#)?, datetime_check!(form!(Form::Month(_))), |ordinal, _, datetime| { - let week_day_start = helpers::day_of_week(Weekday::Fri)?.intersect(&helpers::hour(18, false)?)?; - let week_day_end = helpers::day_of_week(Weekday::Mon)?.intersect(&helpers::hour(0, false)?)?; - let week_day = week_day_start.span_to(&week_day_end, false)?; - let week_ends_of_time = datetime.value().intersect(&week_day)?; - week_ends_of_time.the_nth(ordinal.value().value - 1) + let weekend = helpers::weekend()?; + let nth_week_end = datetime.value().intersect(&weekend)?; + nth_week_end.the_nth(ordinal.value().value - 1) } ); b.rule_2("dernier week-end de ", b.reg(r#"(?:le )?dernier week(?:\s|-)?end (?:du mois d[e']|d['eu]|en)"#)?, datetime_check!(form!(Form::Month(_))), |_, datetime| { - let week_day_start = helpers::day_of_week(Weekday::Fri)?.intersect(&helpers::hour(18, false)?)?; - let week_day_end = helpers::day_of_week(Weekday::Mon)?.intersect(&helpers::hour(0, false)?)?; - let week_day = week_day_start.span_to(&week_day_end, false)?; - week_day.last_of(datetime.value()) + let weekend = helpers::weekend()?; + weekend.last_of(datetime.value()) } ); + // FIXME: change latency ranges for years? E.g. latent until 1900? b.rule_1("year", integer_check_by_range!(1000, 2100), - |integer| { - helpers::year(integer.value().value as i32) - } + |integer| helpers::year(integer.value().value as i32) ); b.rule_1("year (latent)", integer_check_by_range!(-1000, 999), - |integer| { - Ok(helpers::year(integer.value().value as i32)?.latent()) - } + |integer| Ok(helpers::year(integer.value().value as i32)?.latent()) + ); + b.rule_2("l'année ", + b.reg(r#"l[' ]an(?:n[eé]+)?"#)?, + integer_check!(), + |_, integer| helpers::year(integer.value().value as i32) ); b.rule_2("en ", - b.reg(r#"(?:en(?: l'an)?|de l'ann[eé])"#)?, + b.reg(r#"en"#)?, datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::Year(_))(datetime)), |_, year| Ok(year.value().clone()) ); b.rule_1("year (latent)", integer_check_by_range!(2101, 3000), - |integer| { - Ok(helpers::year(integer.value().value as i32)?.latent()) - } + |integer| Ok(helpers::year(integer.value().value as i32)?.latent()) ); b.rule_1_terminal("day of month (premier)", b.reg(r#"premier|prem\.?|1er|1 er"#)?, @@ -882,11 +950,11 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { integer_check_by_range!(1, 31), |_, integer| helpers::day_of_month(integer.value().value as u32) ); - b.rule_4("le à ", + b.rule_4("le à ", b.reg(r#"le"#)?, integer_check_by_range!(1, 31), b.reg(r#"[aà]"#)?, - datetime_check!(), + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(_))(datetime)), |_, integer, _, datetime| { let day_of_month = helpers::day_of_month(integer.value().value as u32)?; day_of_month.intersect(&datetime.value()) @@ -895,39 +963,42 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.rule_2(" ", integer_check_by_range!(1, 31), datetime_check!(form!(Form::Month(_))), - |integer, month| month.value().intersect(&helpers::day_of_month(integer.value().value as u32)?) + |integer, month| Ok(month.value() + .intersect(&helpers::day_of_month(integer.value().value as u32)?)? + .form(Form::DayOfMonth)) ); b.rule_2(" ", datetime_check!(form!(Form::DayOfWeek{..})), // Weird it is not used in the production of the rule integer_check_by_range!(1, 31), |_, integer| helpers::day_of_month(integer.value().value as u32) ); - b.rule_3(" à )", + b.rule_4(" à )", datetime_check!(form!(Form::DayOfWeek{..})), // Weird it is not used in the production of the rule integer_check_by_range!(1, 31), - datetime_check!(form!(Form::TimeOfDay(_))), - |_, integer, tod| helpers::day_of_month(integer.value().value as u32) + b.reg(r#"[aà]"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(_))(datetime)), + |_, integer, _, tod| helpers::day_of_month(integer.value().value as u32) ?.intersect(tod.value()) ); - b.rule_1("time-of-day (latent)", + b.rule_1(" (latent)", integer_check_by_range!(1, 23), |integer| Ok(helpers::hour(integer.value().value as u32, integer.value().value < 12)?.latent()) ); - b.rule_1("time-of-day (latent)", + b.rule_1(" (latent)", integer_check_by_range!(0, 0), |_| Ok(helpers::hour(0, false)?.latent()) ); b.rule_1_terminal("midi", - b.reg(r#"midi"#)?, + b.reg(r#"midi(?: pile| exactement| pr[eé]cises)?"#)?, |_| helpers::hour(12, false) ); b.rule_1_terminal("minuit", - b.reg(r#"minuit"#)?, + b.reg(r#"minuit(?: pile| exactement| pr[eé]cises)?"#)?, |_| helpers::hour(0, false) ); b.rule_2(" heures", datetime_check!(form!(Form::TimeOfDay(TimeOfDayForm::Hour { .. }))), - b.reg(r#"h\.?(?:eure)?s?"#)?, + b.reg(r#"h\.?(?:eure)?s?(?: pile| exactement| pr[eé]cises?)?"#)?, |a, _| Ok(a.value().clone().not_latent()) ); b.rule_2(" (heures) pile", @@ -1010,6 +1081,16 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { datetime.value().form_time_of_day()?.is_12_clock() ) ); + b.rule_3(" (as relative minutes) exactly", + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(TimeOfDayForm::Hour { .. }))(datetime)), + relative_minute_check!(), + b.reg(r#"pile|exactement|pr[ée]cises?"#)?, + |datetime, minutes, _| helpers::hour_relative_minute( + datetime.value().form_time_of_day()?.full_hour(), + minutes.value().0, + datetime.value().form_time_of_day()?.is_12_clock() + ) + ); b.rule_3(" moins (as relative minutes)", datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(TimeOfDayForm::Hour { .. }))(datetime)), b.reg(r#"moins(?: le)?"#)?, @@ -1020,6 +1101,17 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { datetime.value().form_time_of_day()?.is_12_clock() ) ); + b.rule_4(" moins (as relative minutes) exactly ", + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(TimeOfDayForm::Hour { .. }))(datetime)), + b.reg(r#"moins(?: le)?"#)?, + relative_minute_check!(), + b.reg(r#"pile|exactement|pr[ée]cises?"#)?, + |datetime, _, minutes, _| helpers::hour_relative_minute( + datetime.value().form_time_of_day()?.full_hour(), + -1 * minutes.value().0, + datetime.value().form_time_of_day()?.is_12_clock() + ) + ); b.rule_3(" et|passé de ", datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(TimeOfDayForm::Hour { .. }))(datetime)), b.reg(r#"et|pass[ée]e?s? de"#)?, @@ -1030,7 +1122,78 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { datetime.value().form_time_of_day()?.is_12_clock() ) ); - // Written dates in numeric formats + b.rule_4(" et|passé de exactly", + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(TimeOfDayForm::Hour { .. }))(datetime)), + b.reg(r#"et|pass[ée]e?s? de"#)?, + relative_minute_check!(), + b.reg(r#"pile|exactement|pr[ée]cises?"#)?, + |datetime, _, minutes, _| helpers::hour_relative_minute( + datetime.value().form_time_of_day()?.full_hour(), + minutes.value().0, + datetime.value().form_time_of_day()?.is_12_clock() + ) + ); + b.rule_4(" de exactly", + datetime_check!(form!(Form::TimeOfDay(_))), + b.reg(r#"(?:d[eu] |dans )(?:l[ 'a])?"#)?, + datetime_check!(form!(Form::PartOfDay(_))), + b.reg(r#"pile|exactement|pr[eé]cises?"#)?, + |a, _, b, _| Ok(a.value().intersect(b.value())?.form(a.value().form.clone())) + ); + // Adding "pour" here makes time-of-day ambiguous w/ Duration + // Duration has less priority than Datetime types, therefore duration will be output only + // if the output kind filter is set for Duration + b.rule_2("à ", + b.reg(r#"[aà]|pour"#)?, + datetime_check!(form!(Form::TimeOfDay(_))), + |_, a| Ok(a.value().clone().not_latent()) + ); + b.rule_2("vers ", + b.reg(r#"(?:plut[ôo]t )?(?:vers|autour de|[aà] environ|aux alentours de)"#)?, + datetime_check!(form!(Form::TimeOfDay(_))), + |_, a| Ok(a.value().clone().not_latent().precision(Precision::Approximate)) + ); + // Written time/date in numeric formats + b.rule_1_terminal("hh(:|h)mm (time-of-day)", + b.reg(r#"((?:[01]?\d)|(?:2[0-3]))[:h]([0-5]\d)"#)?, + |text_match| { + let hour: u32 = text_match.group(1).parse()?; + let minute: u32 = text_match.group(2).parse()?; + helpers::hour_minute(hour, minute, hour < 12) + } + ); + b.rule_3_terminal("hh(:|h)mm - hh(:|h)mm (time-of-day interval)", + b.reg(r#"((?:[01]?\d)|(?:2[0-3]))[:h]([0-5]\d)"#)?, + b.reg(r#" ?\- ?"#)?, + b.reg(r#"((?:[01]?\d)|(?:2[0-3]))[:h]([0-5]\d)"#)?, + |a, _, b| { + let hour_start: u32 = a.group(1).parse()?; + let minute_start: u32 = a.group(2).parse()?; + let hour_end: u32 = b.group(1).parse()?; + let minute_end: u32 = b.group(2).parse()?; + let start = helpers::hour_minute(hour_start, minute_start, hour_start < 12)?; + let end = helpers::hour_minute(hour_end, minute_end, hour_end < 12)?; + start.smart_span_to(&end, false) + } + ); + b.rule_1_terminal("hh:mm:ss", + b.reg(r#"((?:[01]?\d)|(?:2[0-3]))[:.]([0-5]\d)[:.]([0-5]\d)"#)?, + |text_match| helpers::hour_minute_second( + text_match.group(1).parse()?, + text_match.group(2).parse()?, + text_match.group(3).parse()?, + false + ) + + ); + b.rule_1_terminal("hhmm (military time-of-day)", + b.reg(r#"((?:[01]\d)|(?:2[0-3]))([0-5]\d)"#)?, + |text_match| Ok(helpers::hour_minute( + text_match.group(1).parse()?, + text_match.group(2).parse()?, + false + )?.latent()) + ); b.rule_1_terminal("yyyy-mm-dd - ISO", b.reg(r#"(\d{4})[-/](0?[1-9]|1[0-2])[-/](3[01]|[12]\d|0?[1-9])"#)?, |text_match| helpers::year_month_day( @@ -1054,7 +1217,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { text_match.group(2).parse()?, text_match.group(1).parse()?) ); - // End of Written dates in numeric formats + // End of Written time/date in numeric formats b.rule_1_terminal("matin", b.reg(r#"mat(?:in[ée]?e?)?"#)?, |_| Ok(helpers::hour(4, false)? @@ -1259,7 +1422,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { .span_to(&helpers::hour(23, false)?, false)? .form(Form::Meal)) ); - b.rule_1_terminal("nuit", + b.rule_1_terminal("nuit", b.reg(r#"nuit"#)?, |_| { Ok(helpers::hour(22, false)? @@ -1268,63 +1431,83 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { .form(Form::PartOfDay(PartOfDayForm::Night))) } ); - b.rule_2("a l'heure ", + b.rule_2("a l'heure de ", b.reg(r#"(?:[àa] )?l[' ]heure du|au moment du|pendant l[ea']|au|pour l[ea']|l[ea']"#)?, - datetime_check!(|datetime: &DatetimeValue| datetime.latent && form!(Form::Meal)(datetime)), + datetime_check!(|datetime: &DatetimeValue| form!(Form::Meal)(datetime)), |_, a| Ok(a.value().clone().not_latent()) ); - b.rule_2(" ", - datetime_check!(), + b.rule_2(" ", + datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), datetime_check!(form!(Form::Meal)), |a, b| a.value().intersect(b.value()) ); - b.rule_2("du|dans le ", - b.reg(r#"pendant(?: l[ae']?)?|durant(?: l[ae']?)?|du|(?:[aà]|dans) l[ae']?|au|en|l[ae']|d[èe]s(?: l[ae']?)?"#)?, - datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), + b.rule_2("prep? & article ", // This is very catch-all/junky + b.reg(r#"(?:pendant |durant |dans |d[eè]s )?l[ae']?|en|au"#)?, + datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime)), |_, a| Ok(a.value().clone().not_latent()) ); b.rule_2("ce ", b.reg(r#"cet?t?e?"#)?, datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), - |_, a| Ok(helpers::cycle_nth(Grain::Day, 0)?.intersect(a.value())?.form(a.value().form.clone())) + |_, datetime| Ok(helpers::cycle_nth(Grain::Day, 0)? + .intersect(datetime.value())? + .form(datetime.value().form.clone()) + .datetime_kind(DatetimeKind::DatetimeComplement { date_and_time: true, today: true })) ); - b.rule_2(" ", - datetime_check!(excluding_form!(Form::TimeOfDay(_))), + b.rule_2(" ", + datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), |a, b| a.value().intersect(b.value()) ); - b.rule_2(" du matin", + b.rule_2(" du matin", datetime_check!(form!(Form::TimeOfDay(_))), - b.reg(r#"(?:(?:du|dans|de) )?(?:(?:au|le|la) )?mat(?:in[ée]?e?)?"#)?, + b.reg(r#"(?:(?:du|dans|de) )?(?:(?:au|le|la) )?mat(?:in[ée]?e?)?(?: pile| exactement| pr[eé]cises?)?"#)?, |a, _| { let period = helpers::hour(0, false)? .span_to(&helpers::hour(12, false)?, false)?; - a.value().intersect(&period) + Ok(a.value().intersect(&period)?.form(a.value().form.clone())) + } + ); + b.rule_2(" de l'apres-midi", + datetime_check!(form!(Form::TimeOfDay(_))), + b.reg(r#"(?:dans |de )?l[' ]apr[eè]s[\- ]midi(?: pile| exactement| pr[eé]cises?)?"#)?, + |a, _| { + let period = helpers::hour(12, false)? + .span_to(&helpers::hour(19, false)?, false)?; + Ok(a.value().intersect(&period)?.form(a.value().form.clone())) } ); - b.rule_2(" du soir", + b.rule_2(" du soir", datetime_check!(form!(Form::TimeOfDay(_))), - b.reg(r#"(?:(?:du|dans|de) )?(?:(?:au|le|la) )?soir[ée]?e?"#)?, + b.reg(r#"(?:(?:du|dans|de) )?(?:(?:au|le|la) )?soir[ée]?e?(?: pile| exactement| pr[eé]cises?)?"#)?, |a, _| { let period = helpers::hour(16, false)? .span_to(&helpers::hour(0, false)?, false)?; - a.value().intersect(&period) + Ok(a.value().intersect(&period)?.form(a.value().form.clone())) } ); - b.rule_3(" du ", + b.rule_3(" du ", datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), b.reg(r#"du"#)?, - datetime_check!(), + datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), |a, _, b| b.value().intersect(a.value()) ); - b.rule_1_terminal("week-end", - b.reg(r#"week(?:\s|-)?end"#)?, + b.rule_1_terminal("(ce/le) week-end", + b.reg(r#"(?:[cl]e )?week(?:\s|-)?end"#)?, + |_| helpers::weekend() + ); + b.rule_1_terminal("le week-end dernier", + b.reg(r#"le week(?:\s|-)?end dernier"#)?, |_| { - let friday = helpers::day_of_week(Weekday::Fri)? - .intersect(&helpers::hour(18, false)?)?; - let monday = helpers::day_of_week(Weekday::Mon)? - .intersect(&helpers::hour(0, false)?)?; - friday.span_to(&monday, false) + let weekend = helpers::weekend()?; + Ok(weekend.the_nth(-1)?.datetime_kind(DatetimeKind::DatePeriod)) + } + ); + b.rule_1_terminal("le week-end prochain", + b.reg(r#"le week(?:\s|-)?end prochain|le prochain week(?:\s|-)?end"#)?, + |_| { + let weekend = helpers::weekend()?; + Ok(weekend.the_nth(1)?.datetime_kind(DatetimeKind::DatePeriod)) } ); b.rule_1_terminal("début de semaine", @@ -1370,7 +1553,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { ); b.rule_4("dd-dd (interval)", b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, - b.reg(r#"\-|au|jusqu'au"#)?, + b.reg(r#"\-|(?:jusqu')?au"#)?, b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, datetime_check!(form!(Form::Month(_))), |a, _, b, month| { @@ -1381,7 +1564,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { ); b.rule_4("-dd (interval)", datetime_check!(), - b.reg(r#"\-|au|jusqu'au"#)?, + b.reg(r#"\-|(?:jusqu')?au"#)?, b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, datetime_check!(form!(Form::Month(_))), |datetime, _, text_match, month| { @@ -1392,7 +1575,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { ); b.rule_5("- dd (interval)", datetime_check!(), - b.reg(r#"\-|au|jusqu'au"#)?, + b.reg(r#"\-|(?:jusqu')?au"#)?, datetime_check!(form!(Form::DayOfWeek{..})), b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, datetime_check!(form!(Form::Month(_))), @@ -1405,7 +1588,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.rule_6(" 1er- dd (interval)", datetime_check!(form!(Form::DayOfWeek{..})), b.reg(r#"premier|prem\.?|1er|1 er"#)?, - b.reg(r#"\-|au|jusqu'au"#)?, + b.reg(r#"\-|(?:jusqu')?au"#)?, datetime_check!(form!(Form::DayOfWeek{..})), b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, datetime_check!(form!(Form::Month(_))), @@ -1418,7 +1601,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.rule_6("du dd- dd (interval)", b.reg(r#"du"#)?, b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, - b.reg(r#"\-|au|jusqu'au"#)?, + b.reg(r#"\-|(?:jusqu')?au"#)?, datetime_check!(form!(Form::DayOfWeek{..})), b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, datetime_check!(form!(Form::Month(_))), @@ -1431,7 +1614,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.rule_6("du dd- dd (interval)", b.reg(r#"du"#)?, datetime_check!(), - b.reg(r#"\-|au|jusqu'au"#)?, + b.reg(r#"\-|(?:jusqu')?au"#)?, datetime_check!(form!(Form::DayOfWeek{..})), b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, datetime_check!(form!(Form::Month(_))), @@ -1444,7 +1627,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.rule_4("la nuit ", b.reg(r#"(dans|pendant|durant) la nuit (?:du|de)"#)?, datetime_check!(form!(Form::DayOfWeek{..})), - b.reg(r#"\-|au|jusqu'au"#)?, + b.reg(r#"\-|(?:jusqu')?au"#)?, datetime_check!(form!(Form::DayOfWeek{..})), |_, start, _, end| { let start = start.value().intersect(&helpers::hour(22, false)?)?; @@ -1467,7 +1650,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.rule_4_terminal("du dd au dd(interval)", b.reg(r#"du"#)?, b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, - b.reg(r#"au|jusqu'au"#)?, + b.reg(r#"(?:jusqu')?au"#)?, b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, |_, a, _, b| { let start = helpers::day_of_month(a.group(1).parse()?)?; @@ -1520,9 +1703,11 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { start.span_to(&end, true) } ); + + /* Intervals */ b.rule_3(" - (interval)", datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), - b.reg(r#"\-|au|[aà]|jusqu'(?:au|[aà])"#)?, + b.reg(r#" \- |(?:jusqu')?(?:au|[aà])"#)?, datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), |a, _, b| a.value().span_to(b.value(), true) ); @@ -1535,7 +1720,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.rule_4("de - (interval)", b.reg(r#"depuis|d[e'u]?"#)?, datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), - b.reg(r#"\-|au|[aà]|jusqu'(?:au|[aà])"#)?, + b.reg(r#" \- |(?:jusqu')?(?:au|[aà])"#)?, datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), |_, a, _, b| a.value().span_to(b.value(), true) ); @@ -1550,7 +1735,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.rule_5("de - (interval)", b.reg(r#"depuis|d[e'u]?"#)?, datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), - b.reg(r#"\-|au|[aà]|jusqu'(?:au|[aà])"#)?, + b.reg(r#" \- |(?:jusqu')?(?:au|[aà])"#)?, datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime) && datetime.is_coarse_grain_greater_than(Grain::Year)), datetime_check!(form!(Form::Year(_))), |_, a, _, b, year| a.value().span_to(b.value(), true)?.intersect(year.value()) @@ -1565,17 +1750,31 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { ); b.rule_3(" - (interval)", datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(_))(datetime)), - b.reg(r#" \- |[aà]|au|jusqu'(?:au|[aà])"#)?, + b.reg(r#" \- |(?:jusqu')?(?:au|[aà])"#)?, datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(_))(datetime)), |a, _, b| a.value().smart_span_to(b.value(), false) ); b.rule_4("de - (interval)", - b.reg(r#"(?:midi )?de"#)?, + b.reg(r#"(?:[aà] partir )?d['e]"#)?, datetime_check!(form!(Form::TimeOfDay(_))), - b.reg(r#"[aà]|au|jusqu'(?:au|[aà])"#)?, + b.reg(r#"(?:jusqu')?(?:au|[aà])"#)?, datetime_check!(form!(Form::TimeOfDay(_))), |_, a, _, b| a.value().smart_span_to(b.value(), false) ); + b.rule_2("de maintenant - (interval)", + b.reg(r#"(?:[aà] partir )?de maintenant (?:jusqu')?(?:au|[aà])"#)?, + datetime_check!(form!(Form::TimeOfDay(_))), + |_, a| helpers::cycle_nth(Grain::Second, 0)?.smart_span_to(a.value(), false) + ); + b.rule_3("de - maintenant (interval)", + b.reg(r#"(?:[aà] partir )?d['e]"#)?, + datetime_check!(form!(Form::TimeOfDay(_))), + b.reg(r#"(?:jusqu')?(?:au|[aà]) maintenant"#)?, + |_, a, _| { + let now = helpers::cycle_nth(Grain::Second, 0)?; + a.value().smart_span_to(&now, false) + } + ); b.rule_4("entre et (interval)", b.reg(r#"entre"#)?, datetime_check!(form!(Form::TimeOfDay(_))), @@ -1583,33 +1782,29 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { datetime_check!(form!(Form::TimeOfDay(_))), |_, a, _, b| a.value().smart_span_to(b.value(), false) ); - b.rule_2("d'ici ", - b.reg(r#"d'ici|dans l(?:'|es?)"#)?, - duration_check!(), - |_, duration| { - let start = helpers::cycle_nth(Grain::Second, 0)?; - let end = duration.value().in_present()?; - start.span_to(&end, false) - } - ); - b.rule_2("avant ", - b.reg(r#"(?:n[ ']importe quand )?jusqu'(?:a|à)"#)?, - datetime_check!(), + b.rule_2("jusqu'à ", + b.reg(r#"(?:n[ ']importe quand )?jusqu'(?:au|[aà]|en)"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent), |_, datetime| Ok(datetime.value().clone().mark_before_end()) ); - b.rule_2("avant ", + b.rule_2("avant ", b.reg(r#"(?:n[ ']importe quand )?avant"#)?, - datetime_check!(), + datetime_check!(|datetime: &DatetimeValue| !datetime.latent), |_, datetime| Ok(datetime.value().clone().mark_before_start()) ); - b.rule_2("après ", - b.reg(r#"apr(?:e|è)s"#)?, - datetime_check!(), + b.rule_2("après ", + b.reg(r#"apr[eè]s"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent), |_, datetime| Ok(datetime.value().clone().mark_after_end()) ); - b.rule_2("après ", - b.reg(r#"(?:a|à) partir de"#)?, - datetime_check!(), + b.rule_2("à partir de ", + b.reg(r#"[aà] partir d['eu]"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent), + |_, datetime| Ok(datetime.value().clone().mark_after_start()) + ); + b.rule_2("à partir de ", + b.reg(r#"[aà] partir d['eu](?:l[ 'a])?"#)?, + datetime_check!(form!(Form::PartOfDay(_))), |_, datetime| Ok(datetime.value().clone().mark_after_start()) ); b.rule_2("après le ", @@ -1618,10 +1813,95 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { |_, integer| Ok(helpers::day_of_month(integer.value().value as u32)?.mark_after_end()) ); b.rule_2("après le ", - b.reg(r#"(?:a|à) partir du"#)?, + b.reg(r#"[aà] partir d['eu]"#)?, integer_check_by_range!(1, 31), |_, integer| Ok(helpers::day_of_month(integer.value().value as u32)?.mark_after_start()) ); + b.rule_2("il y a ", + b.reg(r#"il y a"#)?, + duration_check!(), + |_, duration| duration.value().ago() + ); + // With "depuis/d'ici", interpretation of duration ambiguous with time-of-day we choose time-of-day + // I.e. "x heures (y)", but not "x heures et y minutes", "x minutes", etc. + // FIXME: some time-of-day patterns should be removed, e.g. "x heures y minutes" - they are not + // proper time-of-day, but they can be duration expressions + // TODO: check if Grain::Second here breaks DatePeriod - cf. implem. of equivalent in English? + b.rule_2("depuis ", + b.reg(r#"depuis|[cç]a fait"#)?, + duration_check!(), + |_, duration| { + if duration.value().get_coarser_grain() == Grain::Hour { + return Err(RuleError::Invalid.into()) + } + duration.value().ago()? + .span_to(&helpers::cycle_nth(Grain::Second, 0)?, false) + }); + b.rule_2("depuis ", + b.reg(r#"depuis"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent), + |_, datetime| Ok(datetime.value().the_nth(-1)?.mark_after_start()) + ); + b.rule_2("d'ici ", + b.reg(r#"d'ici|dans l(?:'|es?)"#)?, + duration_check!(), + |_, duration| { + let duration_grain = duration.value().get_coarser_grain(); + // Priority to d'ici + if duration_grain == Grain::Hour && + // FIXME: There must be a better way to do this check! + duration.value().period.0.get(Grain::Hour as usize).unwrap_or(&0) <= &23 { + return Err(RuleError::Invalid.into()) + } + let grain = if duration_grain.is_date_grain() { Grain::Day } else { Grain::Second }; + let start = helpers::cycle_nth(grain, 0)?; + let end = if grain == Grain::Day { duration.value().in_present_day()? } else { duration.value().in_present()? }; + start.span_to(&end, true) + } + ); + b.rule_2("dans le ", + b.reg(r#"dans l(?:'|es?)"#)?, + duration_check!(), + |_, duration| { + let duration_grain = duration.value().get_coarser_grain(); + let grain = if duration_grain.is_date_grain() { Grain::Day } else { Grain::Second }; + let start = helpers::cycle_nth(grain, 0)?; + let end = if grain == Grain::Day { duration.value().in_present_day()? } else { duration.value().in_present()? }; + start.span_to(&end, false) + } + ); + b.rule_2("d'ici ", + b.reg(r#"d'ici"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(_))(datetime)), + |_, tod| { + // FIXME: This adds one second to the value of now+then + let now = helpers::cycle_nth(Grain::Second, 0)?; + let then = tod.value().clone().mark_before_start(); + now.span_to(&then, false) + } + ); + b.rule_2("d'ici ", + b.reg(r#"d'ici"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), + |_, date| { + // FIXME: This adds one second to the value of now+then + let today = helpers::cycle_nth(Grain::Day, 0)?; + let then = date.value().clone().mark_before_start(); + today.span_to(&then, false) + } + ); + b.rule_3(" apres ", + duration_check!(), + b.reg(r#"apr[eè]s"#)?, + datetime_check!(), + |duration, _, datetime| duration.value().after(datetime.value()) + ); + b.rule_3(" avant ", + duration_check!(), + b.reg(r#"avant"#)?, + datetime_check!(), + |duration, _, datetime| duration.value().before(datetime.value()) + ); Ok(()) } @@ -1721,6 +2001,10 @@ pub fn rules_numbers(b: &mut RuleSetBuilder) -> RustlingResult<()> { }; IntegerValue::new(value) }); + b.rule_1_terminal("quelques", + b.reg(r#"quelques"#)?, + |_| IntegerValue::new_with_grain(3, 1) + ); b.rule_1_terminal("number (20..60)", b.reg(r#"(vingt|trente|quarante|cinquante|soixante)"#)?, |text_match| { diff --git a/grammar/fr/src/training.rs b/grammar/fr/src/training.rs index 51adc39d..5b2e435b 100644 --- a/grammar/fr/src/training.rs +++ b/grammar/fr/src/training.rs @@ -48,8 +48,8 @@ pub fn examples_finance(v: &mut Vec<::rustling::train::Example>) { pub fn examples_datetime(v: &mut Vec<::rustling::train::Example>) { let c = ResolverContext::new(Interval::starting_at(Moment(Local.ymd(2013, 2, 12).and_hms(4, 30, 0)), Grain::Second)); - example!(v, check_moment!(c, [2013, 2, 12, 4, 30, 00]), "maintenant", "tout de suite"); - example!(v, check_moment!(c, [2013, 2, 12]), "aujourd'hui", "ce jour", "dans la journée", "en ce moment"); + example!(v, check_moment!(c, [2013, 2, 12, 4, 30, 00]), "maintenant", "tout de suite", "en ce moment"); + example!(v, check_moment!(c, [2013, 2, 12]), "aujourd'hui", "ce jour", "dans la journée"); example!(v, check_moment!(c, [2013, 2, 11]), "hier", "le jour d'avant", "le jour précédent", "la veille"); example!(v, check_moment!(c, [2013, 2, 10]), "avant-hier"); example!(v, check_moment!(c, [2013, 2, 13]), "demain", "jour suivant", "le jour d'après", "le lendemain", "un jour après"); @@ -65,16 +65,19 @@ pub fn examples_datetime(v: &mut Vec<::rustling::train::Example>) { example!(v, check_moment!(c, [2013, 3, 1]), "le 1er mars", "premier mars", "le 1 mars", "vendredi 1er mars"); example!(v, check_moment!(c, [2013, 3, 1]), "le premier mars 2013", "1/3/2013", "2013-03-01"); example!(v, check_moment!(c, [2013, 3, 2]), "le 2 mars", "2 mars", "le 2/3"); - example!(v, check_moment!(c, [2013, 3, 2, 5]), "le 2 mars à 5h", "2 mars à 5h", "le 2/3 à 5h", "le 2 mars à 5h du matin", "le 2 mars vers 5h", "2 mars vers 5h", "2 mars à environ 5h", "2 mars aux alentours de 5h", "2 mars autour de 5h", "le 2/3 vers 5h"); + example!(v, check_moment!(c, [2013, 3, 2, 5]), "le 2 mars à 5h", "2 mars à 5h", "le 2/3 à 5h", "le 2 mars à 5h du matin"); + example!(v, check_moment_with_precision!(c, [2013, 3, 2, 5], Precision::Approximate), "le 2 mars vers 5h", "2 mars vers 5h", "2 mars à environ 5h", "2 mars aux alentours de 5h", "2 mars autour de 5h", "le 2/3 vers 5h"); example!(v, check_moment!(c, [2013, 3, 2]), "le 2"); - example!(v, check_moment!(c, [2013, 3, 2, 5]), "le 2 à 5h", "le 2 vers 5h", "le 2 à 5h du mat"); + example!(v, check_moment!(c, [2013, 3, 2, 5]), "le 2 à 5h", "le 2 à 5h du mat"); + example!(v, check_moment_with_precision!(c, [2013, 3, 2, 5], Precision::Approximate), "le 2 vers 5h"); example!(v, check_moment!(c, [2013, 3, 3]), "le 3 mars", "3 mars", "le 3/3"); example!(v, check_moment!(c, [2013, 4, 5]), "le 5 avril", "5 avril"); example!(v, check_moment!(c, [2015, 3, 3]), "le 3 mars 2015", "3 mars 2015", "3/3/2015", "2015-3-3", "2015-03-03"); example!(v, check_moment!(c, [2013, 2, 15]), "le 15 février", "15 février"); example!(v, check_moment!(c, [2013, 2, 15]), "15/02/2013", "15 fev 2013"); example!(v, check_moment!(c, [2013, 2, 16]), "le 16"); - example!(v, check_moment!(c, [2013, 2, 16, 18]), "le 16 à 18h", "le 16 vers 18h", "le 16 plutôt vers 18h", "le 16 à 6h du soir", "le 16 vers 6h du soir", "le 16 vers 6h dans la soirée", "samedi 16 à 18h"); + example!(v, check_moment!(c, [2013, 2, 16, 18]), "le 16 à 18h", "le 16 à 6h du soir", "samedi 16 à 18h"); + example!(v, check_moment_with_precision!(c, [2013, 2, 16, 18], Precision::Approximate), "le 16 vers 18h", "le 16 plutôt vers 18h", "le 16 vers 6h du soir", "le 16 vers 6h dans la soirée"); example!(v, check_moment!(c, [2013, 2, 17]), "17 février", "le 17 février", "17/2", "17/02", "le 17/02", "17 02", "17 2", "le 17 02", "le 17 2"); example!(v, check_moment!(c, [2013, 2, 13]), "mercredi 13"); //when today is Tuesday 12, "mercredi 13" should be tomorrow example!(v, check_moment!(c, [2014, 2, 20]), "20/02/2014", "20/2/2014", "20/02/14", "le 20/02/14", "le 20/2/14", "20 02 2014", "20 02 14", "20 2 2014", "20 2 14", "le 20 02 2014", "le 20 02 14", "le 20 2 2014", "le 20 2 14"); @@ -115,7 +118,8 @@ pub fn examples_datetime(v: &mut Vec<::rustling::train::Example>) { example!(v, check_moment!(c, [2015, 10, 31]), "dernier jour d'octobre 2015", "le dernier jour d'octobre 2015"); example!(v, check_moment!(c, [2014, 9, 22], Grain::Week), "dernière semaine de septembre 2014", "la dernière semaine de septembre 2014"); //Hours - example!(v, check_moment!(c, [2013, 2, 12, 15]), "à quinze heures", "à 15 heures", "à 3 heures cet après-midi", "15h", "15H", "vers 15 heures", "à environ 15 heures"); + example!(v, check_moment!(c, [2013, 2, 12, 15]), "à quinze heures", "à 15 heures", "à 3 heures cet après-midi", "15h", "15H"); + example!(v, check_moment_with_precision!(c, [2013, 2, 12, 15], Precision::Approximate), "vers 15 heures", "à environ 15 heures"); example!(v, check_moment!(c, [2013, 2, 12, 15, 0]), "15:00", "15h00", "15H00"); example!(v, check_moment!(c, [2013, 2, 13, 00]), "minuit"); example!(v, check_moment!(c, [2013, 2, 12, 12]), "midi", "aujourd'hui à midi"); @@ -225,10 +229,10 @@ pub fn examples_datetime(v: &mut Vec<::rustling::train::Example>) { example!(v, check_moment_with_direction!(c, [2013, 2, 15, 12], Direction::After), "vendredi à partir de midi"); example!(v, check_moment_span!(c, [2013, 2, 20], [2013, 2, 20, 18]), "le 20 jusqu'à 18h"); example!(v, check_moment_span!(c, [2014, 9, 14], [2014, 9, 21]), "14 - 20 sept. 2014", "14 - 20 sep 2014"); // but not "14 - 20 sept 2014" - example!(v, check_moment_span!(c, [2013, 2, 12, 4, 30, 0], [2013, 2, 26]), "d'ici 2 semaines"); + example!(v, check_moment_span!(c, [2013, 2, 12], [2013, 2, 27]), "d'ici 2 semaines"); //15j != 2 semaines - example!(v, check_moment_span!(c, [2013, 2, 12, 4, 30, 0], [2013, 5, 12]), "d'ici 3 mois"); - example!(v, check_moment_span!(c, [2013, 2, 12, 4, 30, 0], [2013, 2, 27]), "dans les 15 jours"); + example!(v, check_moment_span!(c, [2013, 2, 12], [2013, 5, 13]), "d'ici 3 mois"); + example!(v, check_moment_span!(c, [2013, 2, 12], [2013, 2, 28]), "dans les 15 jours"); example!(v, check_moment_span!(c, [2013, 2, 12, 5], [2013, 2, 12, 7]), "de 5 à 7"); example!(v, check_moment_span!(c, [2013, 2, 14, 9], [2013, 2, 14, 11]), "jeudi de 9h à 11h"); example!(v, check_moment_span!(c, [2013, 2, 12, 12], [2013, 2, 12, 14]), "entre midi et 2"); From 3f37a1dabed3a2875231392cf63cc5373ac232a1 Mon Sep 17 00:00:00 2001 From: Rosa Stern Date: Sun, 9 Jun 2019 16:42:16 +0200 Subject: [PATCH 028/107] Detail --- grammar/en/src/rules_datetime.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/grammar/en/src/rules_datetime.rs b/grammar/en/src/rules_datetime.rs index 5ac3eb27..220b7112 100644 --- a/grammar/en/src/rules_datetime.rs +++ b/grammar/en/src/rules_datetime.rs @@ -1266,6 +1266,7 @@ pub fn rules_datetime_with_duration(b: &mut RuleSetBuilder) -> Rustli } +// FIXME: rename "rules_datetime_with_cycle" pub fn rules_datetime_with_nth_cycle(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.rule_2("this ", From 43b4a4e9288481d8cae1c370d9e54cce72aa43ff Mon Sep 17 00:00:00 2001 From: Rosa Stern Date: Sun, 9 Jun 2019 16:43:04 +0200 Subject: [PATCH 029/107] Split French grammar per entity type. --- grammar/fr/src/lib.rs | 23 +- grammar/fr/src/rules.rs | 2548 -------------------------- grammar/fr/src/rules_amount.rs | 243 +++ grammar/fr/src/rules_celebrations.rs | 114 ++ grammar/fr/src/rules_datetime.rs | 1443 +++++++++++++++ grammar/fr/src/rules_duration.rs | 117 ++ grammar/fr/src/rules_number.rs | 561 ++++++ 7 files changed, 2493 insertions(+), 2556 deletions(-) delete mode 100644 grammar/fr/src/rules.rs create mode 100644 grammar/fr/src/rules_amount.rs create mode 100644 grammar/fr/src/rules_celebrations.rs create mode 100644 grammar/fr/src/rules_datetime.rs create mode 100644 grammar/fr/src/rules_duration.rs create mode 100644 grammar/fr/src/rules_number.rs diff --git a/grammar/fr/src/lib.rs b/grammar/fr/src/lib.rs index 00c89d87..1672f388 100644 --- a/grammar/fr/src/lib.rs +++ b/grammar/fr/src/lib.rs @@ -3,7 +3,11 @@ extern crate rustling; extern crate rustling_ontology_values; extern crate rustling_ontology_moment; -pub mod rules; +mod rules_datetime; +mod rules_celebrations; +mod rules_duration; +mod rules_number; +mod rules_amount; pub mod training; use rustling_ontology_values::DimensionKind::*; @@ -12,13 +16,16 @@ pub fn rule_set() -> ::rustling::RustlingResult<::rustling::RuleSet) -> RustlingResult<()> { - b.rule_2(" per cent", - number_check!(), - b.reg(r"(?:%|p\.c\.|p. cents?|pour[ -]?cents?)")?, - |number, _| Ok(PercentageValue(number.value().value())) - ); - Ok(()) -} - -pub fn rules_finance(b: &mut RuleSetBuilder) -> RustlingResult<()> { - b.rule_2("intersect (X cents)", - amount_of_money_check!(), - amount_of_money_check!(|money: &AmountOfMoneyValue| money.unit == Some("cent")), - |a, b| helpers::compose_money(a.value(), b.value())); - b.rule_3("intersect (and X cents)", - amount_of_money_check!(), - b.reg(r#"et"#)?, - amount_of_money_check!(|money: &AmountOfMoneyValue| money.unit == Some("cent")), - |a, _, b| helpers::compose_money(&a.value(), &b.value())); - b.rule_2("intersect", - amount_of_money_check!(|money: &AmountOfMoneyValue| money.unit != Some("cent")), - number_check!(), - |a, b| helpers::compose_money_number(&a.value(), &b.value())); - b.rule_1_terminal("$", - b.reg(r#"\$|dollars?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("$") }) - ); - b.rule_1_terminal("EUR", - b.reg(r#"€|(?:[e€]uro?s?)"#)?, - |_| Ok(MoneyUnitValue { unit: Some("EUR") }) - ); - b.rule_1_terminal("£", - b.reg(r#"£|livres?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("£") }) - ); - b.rule_1_terminal("USD", - b.reg(r#"us[d\$]|dollars? am[eé]ricains?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("USD") }) - ); - b.rule_1_terminal("AUD", - b.reg(r#"au[d\$]|dollars? australiens?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("AUD") }) - ); - b.rule_1_terminal("CAD", - b.reg(r#"cad|dollars? canadiens?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("CAD") }) - ); - b.rule_1_terminal("HKD", - b.reg(r#"hkd|dollars? de hong[- ]kong"#)?, - |_| Ok(MoneyUnitValue { unit: Some("HKD") }) - ); - b.rule_1_terminal("KR", - b.reg(r#"kr|couronnes?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("KR") }) - ); - b.rule_1_terminal("DKK", - b.reg(r#"dkk|couronnes? danoises?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("DKK") }) - ); - b.rule_1_terminal("NOK", - b.reg(r#"nok|couronnes? norv[ée]giennes?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("NOK") }) - ); - b.rule_1_terminal("SEK", - b.reg(r#"sek|couronnes? su[ée]doises?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("SEK") }) - ); - b.rule_1_terminal("CHF", - b.reg(r#"chf|francs? suisses?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("CHF") }) - ); - b.rule_1_terminal("RUB", - b.reg(r#"rub|roubles?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("RUB") }) - ); - b.rule_1_terminal("INR", - b.reg(r#"inr|roupies?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("INR") }) - ); - b.rule_1_terminal("JPY", - b.reg(r#"jpy|yens?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("JPY") }) - ); - b.rule_1_terminal("RMB|CNH|CNY", - b.reg(r#"cny|cnh|rmb|yuans?|renmimbis?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("CNY") }) - ); - b.rule_1_terminal("¥", - b.reg(r#"¥"#)?, - |_| Ok(MoneyUnitValue { unit: Some("¥") }) - ); - b.rule_1_terminal("KRW", - b.reg(r#"₩|krw|wons? (?:sud[- ])?cor[ée]ns?|wons?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("KRW") }) - ); - b.rule_1_terminal("Bitcoin", - b.reg(r#"฿|bitcoins?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("฿") }) - ); - b.rule_1_terminal("GBP", - b.reg(r#"gbp|livres? sterlings?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("GBP") }) - ); - b.rule_1_terminal("cent", - b.reg(r#"centimes?|cents?|penn(?:y|ies)|fens?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("cent") }) - ); - b.rule_1_terminal("unnamed currency", - b.reg(r#"(?:balle)s?"#)?, - |_| Ok(MoneyUnitValue { unit: None }) - ); - b.rule_2(" ", - number_check!(), - money_unit!(), - |a, b| { - Ok(AmountOfMoneyValue { - value: a.value().value(), - unit: b.value().unit, - ..AmountOfMoneyValue::default() - }) - }); - b.rule_3(" de ", // "un million de dollars" - integer_check!(|integer: &IntegerValue| !integer.group), - b.reg(r#"d[e']"#)?, - money_unit!(), - |a, _, b| { - Ok(AmountOfMoneyValue { - value: a.value().value as f32, - precision: Exact, - unit: b.value().unit, - ..AmountOfMoneyValue::default() - }) - }); - b.rule_3(" de ", // "une douzaine de dollars" - integer_check!(|integer: &IntegerValue| integer.group), - b.reg(r#"d[e']"#)?, - money_unit!(), - |a, _, b| { - Ok(AmountOfMoneyValue { - value: a.value().value as f32, - precision: Approximate, - unit: b.value().unit, - ..AmountOfMoneyValue::default() - }) - }); - b.rule_2("about ", - b.reg(r#"(?:autour|pas loin|pr[eè]s|aux alentours) d[e']|environ|presque|(?:approximative|quasi)ment"#)?, - amount_of_money_check!(), - |_, a| { - Ok(AmountOfMoneyValue { - precision: Approximate, - ..a.value().clone() - }) - }); - b.rule_2("exactly ", - b.reg(r#"(?:tr[eè]s )?exactement|pr[eé]cis[eé]ment|pile(?: poil)?"#)?, - amount_of_money_check!(), - |_, a| { - Ok(AmountOfMoneyValue { - precision: Exact, - ..a.value().clone() - }) - }); - b.rule_2("exactly ", - amount_of_money_check!(), - b.reg(r#"pile(?: poil)?|tout rond"#)?, - |a, _| { - Ok(AmountOfMoneyValue { - precision: Exact, - ..a.value().clone() - }) - } - ); - Ok(()) -} - -pub fn rules_duration(b: &mut RuleSetBuilder) -> RustlingResult<()> { - b.rule_1_terminal("seconde (unit-of-duration)", - b.reg(r#"sec(?:onde)?s?"#)?, - |_| Ok(UnitOfDurationValue::new(Grain::Second)) - ); - b.rule_1_terminal("minute (unit-of-duration)", - b.reg(r#"min(?:ute)?s?"#)?, - |_| Ok(UnitOfDurationValue::new(Grain::Minute)) - ); - b.rule_1_terminal("heure (unit-of-duration)", - b.reg(r#"h(?:eure)?s?"#)?, - |_| Ok(UnitOfDurationValue::new(Grain::Hour)) - ); - b.rule_1_terminal("jour (unit-of-duration)", - b.reg(r#"jour(?:n[ée]e?)?s?"#)?, - |_| Ok(UnitOfDurationValue::new(Grain::Day)) - ); - b.rule_1_terminal("semaine (unit-of-duration)", - b.reg(r#"semaines?"#)?, - |_| Ok(UnitOfDurationValue::new(Grain::Week)) - ); - b.rule_1_terminal("mois (unit-of-duration)", - b.reg(r#"mois?"#)?, - |_| Ok(UnitOfDurationValue::new(Grain::Month)) - ); - b.rule_1_terminal("année (unit-of-duration)", - b.reg(r#"an(?:n[ée]e?)?s?"#)?, - |_| Ok(UnitOfDurationValue::new(Grain::Year)) - ); - b.rule_1_terminal("trimestre (unit-of-duration)", - b.reg(r#"trimestres?"#)?, - |_| Ok(UnitOfDurationValue::new(Grain::Quarter)) - ); - b.rule_1_terminal("un quart heure", - b.reg(r#"(1/4\s?h(?:eure)?|(?:un|1) quart d'heure)"#)?, - |_| Ok(DurationValue::new(PeriodComp::minutes(15).into())) - ); - b.rule_1_terminal("une demi heure", - b.reg(r#"(?:1/2\s?h(?:eure)?|(?:1|une) demi(?:e)?(?:\s|-)heure)"#)?, - |_| Ok(DurationValue::new(PeriodComp::minutes(30).into())) - ); - b.rule_1_terminal("trois quarts d'heure", - b.reg(r#"(?:3/4\s?h(?:eure)?|(?:3|trois) quart(?:s)? d'heure)"#)?, - |_| Ok(DurationValue::new(PeriodComp::minutes(45).into())) - ); - b.rule_2(" ", - integer_check_by_range!(0), - unit_of_duration_check!(), - |integer, unit| Ok(DurationValue::new(PeriodComp::new(unit.value().grain, integer.value().value).into())) - ); - b.rule_3(" de ", - integer_check!(|integer: &IntegerValue| integer.value >= 0 && integer.group), - b.reg(r#"d[e']"#)?, - unit_of_duration_check!(), - |integer, _, unit| Ok(DurationValue::new(PeriodComp::new(unit.value().grain, integer.value().value).into())) - ); - b.rule_4(" h ", - integer_check_by_range!(0), - b.reg(r#"h(?:eures?)?"#)?, - integer_check_by_range!(0,59), - b.reg(r#"m(?:inutes?)?"#)?, - |hour, _, minute, _| { - let hour_period = Period::from(PeriodComp::new(Grain::Hour, hour.value().clone().value)); - let minute_period = Period::from(PeriodComp::new(Grain::Minute, minute.value().clone().value)); - Ok(DurationValue::new(hour_period + minute_period)) - } - ); - b.rule_3(" et quart", - integer_check_by_range!(0), - unit_of_duration_check!(), - b.reg(r#"et quart"#)?, - |integer, uod, _| { - let quarter_period: Period = uod.value().grain.quarter_period().map(|a| a.into()).ok_or_else(|| RuleError::Invalid)?; - Ok(DurationValue::new(quarter_period + PeriodComp::new(uod.value().grain, integer.value().value))) - } - ); - b.rule_3(" et demie", - integer_check_by_range!(0), - unit_of_duration_check!(), - b.reg(r#"et demie?"#)?, - |integer, uod, _| { - let half_period: Period = uod.value().grain.half_period().map(|a| a.into()).ok_or_else(|| RuleError::Invalid)?; - Ok(DurationValue::new(half_period + PeriodComp::new(uod.value().grain, integer.value().value))) - } - ); - b.rule_3(" et ", - duration_check!(|duration: &DurationValue| !duration.suffixed), - b.reg(r#"et"#)?, - duration_check!(|duration: &DurationValue| !duration.prefixed), - |a, _, b| Ok(a.value() + b.value()) - ); - b.rule_2(" ", - duration_check!(|duration: &DurationValue| !duration.suffixed), - duration_check!(|duration: &DurationValue| !duration.prefixed), - |a, b| Ok(a.value() + b.value()) - ); - b.rule_2(" ", - duration_check!(|duration: &DurationValue| !duration.prefixed), - integer_check_by_range!(0), - |duration, integer| helpers::compose_duration_with_integer(duration.value(), integer.value()) - ); - b.rule_2("dans ", - b.reg(r#"dans"#)?, - duration_check!(), - |_, duration| duration.value().in_present() - ); - b.rule_2(" plus tard", - duration_check!(), - b.reg(r"plus tard")?, - |duration, _| duration.value().in_present() - ); - b.rule_2("environ ", - b.reg(r#"environ|approximativement|à peu près|presque"#)?, - duration_check!(), - |_, duration| Ok(duration.value().clone().precision(Precision::Approximate)) - ); - b.rule_2(" environ", - duration_check!(), - b.reg(r#"environ|approximativement|à peu près"#)?, - |duration, _| Ok(duration.value().clone().precision(Precision::Approximate)) - ); - b.rule_2("exactement ", - b.reg(r#"exactement|précisément"#)?, - duration_check!(), - |_, duration| Ok(duration.value().clone().precision(Precision::Exact)) - ); - b.rule_2(" exactement", - duration_check!(), - b.reg(r#"exactement|précisément|pile"#)?, - |duration, _| Ok(duration.value().clone().precision(Precision::Exact)) - ); - // Ambiguous w/ time-of-day w/ "pour" - // Duration has less priority than Datetime types, therefore duration will be output only - // if the output kind filter is set for Duration - b.rule_2("pendant ", - b.reg(r#"pendant|durant|pour"#)?, - duration_check!(), - |_, duration| Ok(duration.value().clone().prefixed()) - ); - b.rule_2("une durée de ", - b.reg(r#"une dur[ée]e d['e]"#)?, - duration_check!(), - |_, duration| Ok(duration.value().clone().prefixed()) - ); - Ok(()) -} - -pub fn rules_cycle(b: &mut RuleSetBuilder) -> RustlingResult<()> { - b.rule_1_terminal("seconde (cycle)", - b.reg(r#"secondes?"#)?, - |_| CycleValue::new(Grain::Second) - ); - b.rule_1_terminal("minute (cycle)", - b.reg(r#"minutes?"#)?, - |_| CycleValue::new(Grain::Minute) - ); - b.rule_1_terminal("heure (cycle)", - b.reg(r#"heures?"#)?, - |_| CycleValue::new(Grain::Hour) - ); - b.rule_1_terminal("jour (cycle)", - b.reg(r#"jour(?:n[ée]e?)?s?"#)?, - |_| CycleValue::new(Grain::Day) - ); - b.rule_1_terminal("semaine (cycle)", - b.reg(r#"semaines?"#)?, - |_| CycleValue::new(Grain::Week) - ); - b.rule_1("mois (cycle)", - b.reg(r#"mois"#)?, - |_| CycleValue::new(Grain::Month) - ); - b.rule_1_terminal("trimestre (cycle)", - b.reg(r#"trimestre"#)?, - |_| CycleValue::new(Grain::Quarter) - ); - b.rule_1("année (cycle)", - b.reg(r#"an(?:n[ée]e?)?s?"#)?, - |_| CycleValue::new(Grain::Year) - ); - b.rule_1_terminal("trimestre (cycle)", - b.reg(r#"trimestres?"#)?, - |_| CycleValue::new(Grain::Quarter) - ); - // Cycle patterns relative to now - b.rule_2("ce|dans le ", - b.reg(r#"(?:(?:dans )?l[ea' ]|cet?(?:te)?)"#)?, - cycle_check!(), - |_, cycle| helpers::cycle_nth(cycle.value().grain, 0) - ); - b.rule_3("ce (la ou ci)", - b.reg(r#"cet?t?e?s?"#)?, - cycle_check!(), - b.reg(r#"-?ci"#)?, - |_, cycle, _| helpers::cycle_nth(cycle.value().grain, 0) - ); - b.rule_2(" dernier", - cycle_check!(), - b.reg(r#"derni[èe]re?|pass[ée]e?|pr[eé]c[eé]dente?|(?:d')? ?avant"#)?, - |cycle, _| helpers::cycle_nth(cycle.value().grain, -1) - ); - b.rule_3("le dernier", - b.reg(r#"l[ae']? ?"#)?, - cycle_check!(), - b.reg(r#"derni[èe]re?|pass[ée]e?"#)?, - |_, cycle, _| helpers::cycle_nth(cycle.value().grain, -1) - ); - b.rule_3("n derniers ", - integer_check_by_range!(2, 9999), - b.reg(r#"derni.re?s?"#)?, - cycle_check!(), - |integer, _, cycle| { - let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, -1 * integer.value().value)?; - // All grains except Day will trigger the right datetime_kind - if cycle.value().grain == Grain::Day { - res = res.datetime_kind(DatetimeKind::DatePeriod); - } - Ok(res) - } - ); - b.rule_4("(pendant/durant/dans) les n derniers ", - b.reg(r#"(?:pendant |durant |dans )?[cld]es"#)?, - integer_check_by_range!(2, 9999), - b.reg(r#"derni.re?s?"#)?, - cycle_check!(), - |_, integer, _, cycle| { - let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, -1 * integer.value().value)?; - // All grains except Day will trigger the right datetime_kind - if cycle.value().grain == Grain::Day { - res = res.datetime_kind(DatetimeKind::DatePeriod); - } - Ok(res) - } - ); - b.rule_3("n passes|precedents", - integer_check_by_range!(2, 9999), - cycle_check!(), - b.reg(r#"pass[eèé][eèé]?s?|pr[eé]c[eé]dente?s?|(?:d')? ?avant|plus t[oô]t"#)?, - |integer, cycle, _| { - let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, -1 * integer.value().value)?; - // All grains except Day will trigger the right datetime_kind - if cycle.value().grain == Grain::Day { - res = res.datetime_kind(DatetimeKind::DatePeriod); - } - Ok(res) - } - ); - b.rule_4("(pendant/durant/dans) les n passes|precedents", - b.reg(r#"(?:pendant |durant |dans )?[cld]es"#)?, - integer_check_by_range!(2, 9999), - cycle_check!(), - b.reg(r#"pass[eèé][eèé]?s?|pr[eé]c[eé]dente?s?|(?:d')? ?avant|plus t[oô]t"#)?, - |_, integer, cycle, _| helpers::cycle_n_not_immediate(cycle.value().grain, -1 * integer.value().value) - ); - // Incorrect resolution if some follows the expression, - // e.g. "suivant le " (unsupported) - b.rule_2(" prochain|suivant|d'après", - cycle_check!(), - b.reg(r#"prochaine?|suivante?|qui suit|(?:d')? ?apr[eèé]s"#)?, - |cycle, _| helpers::cycle_nth(cycle.value().grain, 1) - ); - b.rule_3("le prochain|suivant|d'après", - b.reg(r#"l[ae']? ?|une? ?"#)?, - cycle_check!(), - b.reg(r#"prochaine?|suivante?|qui suit|(?:d'? ?)?apr[eèé]s"#)?, - |_, cycle, _| helpers::cycle_nth(cycle.value().grain, 1) - ); - b.rule_3("n prochains ", - integer_check_by_range!(2, 9999), - b.reg(r#"prochaine?s?|suivante?s?|apr[eèé]s"#)?, - cycle_check!(), - |integer, _, cycle| { - let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, integer.value().value)?; - // All grains except Day will trigger the right datetime_kind - if cycle.value().grain == Grain::Day { - res = res.datetime_kind(DatetimeKind::DatePeriod); - } - Ok(res) - } - ); - b.rule_4("(pendant/durant/dans) les n prochains ", - b.reg(r#"(?:pendant |durant |dans )?[cld]es"#)?, - integer_check_by_range!(2, 9999), - b.reg(r#"prochaine?s?|suivante?s?|apr[eèé]s"#)?, - cycle_check!(), - |_, integer, _, cycle| { - let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, integer.value().value)?; - // All grains except Day will trigger the right datetime_kind - if cycle.value().grain == Grain::Day { - res = res.datetime_kind(DatetimeKind::DatePeriod); - } - Ok(res) - } - ); - b.rule_3("n suivants", - integer_check_by_range!(2, 9999), - cycle_check!(), - b.reg(r#"prochaine?s?|suivante?s?|apr[eèé]s|qui sui(?:t|ves?)|plus tard"#)?, - |integer, cycle, _| { - let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, integer.value().value)?; - // All grains except Day will trigger the right datetime_kind - if cycle.value().grain == Grain::Day { - res = res.datetime_kind(DatetimeKind::DatePeriod); - } - Ok(res) - } - ); - b.rule_4("(pendant/durant/dans) les n suivants", - b.reg(r#"(?:pendant |durant |dans )?[cld]es"#)?, - integer_check_by_range!(2, 9999), - cycle_check!(), - b.reg(r#"prochaine?s?|suivante?s?|apr[eèé]s|qui sui(?:t|ves?)|plus tard"#)?, - |_, integer, cycle, _| { - let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, integer.value().value)?; - // All grains except Day will trigger the right datetime_kind - if cycle.value().grain == Grain::Day { - res = res.datetime_kind(DatetimeKind::DatePeriod); - } - Ok(res) - } - ); - b.rule_3("n avant", - integer_check_by_range!(2, 9999), - cycle_check!(), - b.reg(r#"(?:d')? ?avant|plus t[oô]t"#)?, - |integer, cycle, _| { - let mut res = helpers::cycle_nth(cycle.value().grain, -1 * integer.value().value)?; - // All grains except Day will trigger the right datetime_kind - if cycle.value().grain == Grain::Day { - res = res.datetime_kind(DatetimeKind::DatePeriod); - } - Ok(res) - } - ); - b.rule_3("n après", - integer_check_by_range!(2, 9999), - cycle_check!(), - b.reg(r#"(?:d')? ?apr[eèé]s|qui sui(?:t|ves?)|plus tard"#)?, - |integer, cycle, _| { - let mut res = helpers::cycle_nth(cycle.value().grain, integer.value().value)?; - // All grains except Day will trigger the right datetime_kind - if cycle.value().grain == Grain::Day { - res = res.datetime_kind(DatetimeKind::DatePeriod); - } - Ok(res) - } - ); - // Cycle patterns relative to another datetime - b.rule_4("le après|suivant ", - b.reg(r#"l[ea']? ?"#)?, - cycle_check!(), - b.reg(r#"suivante?|apr[eèé]s"#)?, - datetime_check!(), - |_, cycle, _, datetime| helpers::cycle_nth_after(cycle.value().grain, 1, datetime.value()) - ); - b.rule_4("le avant|précédent ", - b.reg(r#"l[ea']? ?"#)?, - cycle_check!(), - b.reg(r#"avant|pr[ée]c[ée]dent"#)?, - datetime_check!(), - |_, cycle, _, datetime| helpers::cycle_nth_after(cycle.value().grain, -1, datetime.value()) - ); - b.rule_4(" de ", - ordinal_check_by_range!(1, 9999), - cycle_check!(), - b.reg(r#"d['eu]|en"#)?, - datetime_check!(), - |ordinal, cycle, _, datetime| helpers::cycle_nth_after_not_immediate(cycle.value().grain, ordinal.value().value - 1, datetime.value()) - ); - b.rule_5("le de ", - b.reg(r#"l[ea]"#)?, - ordinal_check_by_range!(1, 9999), - cycle_check!(), - b.reg(r#"d['eu]|en"#)?, - datetime_check!(), - |_, ordinal, cycle, _, datetime| helpers::cycle_nth_after_not_immediate(cycle.value().grain, ordinal.value().value - 1, datetime.value()) - ); - b.rule_4("le de ", - b.reg(r#"l[ea]"#)?, - cycle_check!(), - b.reg(r#"d['eu]|en"#)?, - datetime_check!(), - |_, cycle, _, datetime| helpers::cycle_nth_after_not_immediate(cycle.value().grain, 0, datetime.value()) - ); - b.rule_2("le lendemain du ", - b.reg(r#"(?:le|au)? ?lendemain du"#)?, - datetime_check!(), - |_, datetime| helpers::cycle_nth_after_not_immediate(Grain::Day, 1, datetime.value()) - ); - b.rule_2("la veille du ", - b.reg(r#"(la )?veille du"#)?, - datetime_check!(), - |_, datetime| helpers::cycle_nth_after_not_immediate(Grain::Day, -1, datetime.value()) - ); - Ok(()) -} - -pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { - b.rule_2("intersect", - datetime_check!(|datetime: &DatetimeValue| !datetime.latent), - datetime_check!(|datetime: &DatetimeValue| !datetime.latent), - |a, b| a.value().intersect(b.value()) - ); - b.rule_3("intersect by 'de' or ','", - datetime_check!(|datetime: &DatetimeValue| !datetime.latent), - b.reg(r#"de|,"#)?, - datetime_check!(|datetime: &DatetimeValue| !datetime.latent), - |a, _, b| a.value().intersect(b.value()) - ); - b.rule_3("intersect by 'mais/par exemple/plutôt'", - datetime_check!(|datetime: &DatetimeValue| !datetime.latent), - b.reg(r#"mais|par exemple|plutôt|plutot"#)?, - datetime_check!(|datetime: &DatetimeValue| !datetime.latent), - |a, _, b| a.value().intersect(b.value()) - ); - b.rule_2("en ", - b.reg(r#"en|au mois d[e']|du mois d[e']"#)?, - datetime_check!(form!(Form::Month(_))), - |_, a| Ok(a.value().clone()) - ); - b.rule_2("pour ", - b.reg(r#"pour"#)?, - datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), - |_, a| Ok(a.value().clone()) - ); - b.rule_1_terminal("named-day", - b.reg(r#"lun\.?(?:di)?"#)?, - |_| helpers::day_of_week(Weekday::Mon) - ); - b.rule_1_terminal("named-day", - b.reg(r#"mar\.?(?:di)?"#)?, - |_| helpers::day_of_week(Weekday::Tue) - ); - b.rule_1_terminal("named-day", - b.reg(r#"mer\.?(?:credi)?"#)?, - |_| helpers::day_of_week(Weekday::Wed) - ); - b.rule_1_terminal("named-day", - b.reg(r#"jeu\.?(?:di)?"#)?, - |_| helpers::day_of_week(Weekday::Thu) - ); - b.rule_1_terminal("named-day", - b.reg(r#"ven\.?(?:dredi)?"#)?, - |_| helpers::day_of_week(Weekday::Fri) - ); - b.rule_1_terminal("named-day", - b.reg(r#"sam\.?(?:edi)?"#)?, - |_| helpers::day_of_week(Weekday::Sat) - ); - b.rule_1_terminal("named-day", - b.reg(r#"dim\.?(?:anche)?"#)?, - |_| helpers::day_of_week(Weekday::Sun) - ); - b.rule_1_terminal("named-month", - b.reg(r#"janvier|janv\.?"#)?, - |_| helpers::month(1) - ); - b.rule_1_terminal("named-month", - b.reg(r#"fevrier|février|fev|fév\.?"#)?, - |_| helpers::month(2) - ); - b.rule_1_terminal("named-month", - b.reg(r#"mars|mar\.?"#)?, - |_| helpers::month(3) - ); - b.rule_1_terminal("named-month", - b.reg(r#"avril|avr\.?"#)?, - |_| helpers::month(4) - ); - b.rule_1_terminal("named-month", - b.reg(r#"mai"#)?, - |_| helpers::month(5) - ); - b.rule_1_terminal("named-month", - b.reg(r#"juin|jun\.?"#)?, - |_| helpers::month(6) - ); - b.rule_1_terminal("named-month", - b.reg(r#"juillet|juil?\."#)?, - |_| helpers::month(7) - ); - b.rule_1_terminal("named-month", - b.reg(r#"aout|août|aou\.?"#)?, - |_| helpers::month(8) - ); - b.rule_1_terminal("named-month", -// b.reg(r#"septembre|sept?\.?"#)?, // "sept" with no dot forbidden (confusion with nb "sept" in "à trois heures trente sept") - b.reg(r#"septembre|sept\.|sep\.?"#)?, - |_| helpers::month(9) - ); - b.rule_1_terminal("named-month", - b.reg(r#"octobre|oct\.?"#)?, - |_| helpers::month(10) - ); - b.rule_1_terminal("named-month", - b.reg(r#"novembre|nov\.?"#)?, - |_| helpers::month(11) - ); - b.rule_1_terminal("named-month", - b.reg(r#"décembre|decembre|déc\.?|dec\.?"#)?, - |_| helpers::month(12) - ); - b.rule_1_terminal("noel", - b.reg(r#"(?:(?:le )?jour de )?no[eë]l"#)?, - |_| Ok(helpers::month_day(12, 25)?.form(Form::Celebration)) - ); - b.rule_1_terminal("soir de noël", - b.reg(r#"(?:l[ea] )?(?:soir(?:ée)?|veille|r[eé]veillon) de no[eë]l"#)?, - |_| { - let start = helpers::month_day(12, 24)?.intersect(&helpers::hour(18, false)?)?; - let end = helpers::month_day(12, 25)?.intersect(&helpers::hour(0, false)?)?; - Ok(start.span_to(&end, false)? - .form(Form::Celebration)) - } - ); - b.rule_1_terminal("saint sylvestre", - b.reg(r#"(?:l[ea] )?(?:saint[- ]sylvestre|r[eé]veillon)"#)?, - |_| Ok(helpers::month_day(12, 31)?.form(Form::Celebration)) - ); - b.rule_1_terminal("jour de l'an", - b.reg(r#"(?:le )?(?:jour de l'|nouvel )an"#)?, - |_| Ok(helpers::month_day(1, 1)?.form(Form::Celebration)) - ); - b.rule_1_terminal("toussaint", - b.reg(r#"(?:(?:la |la journée de la |jour de la )?toussaint|jour des morts)"#)?, - |_| Ok(helpers::month_day(11, 1)?.form(Form::Celebration)) - ); - b.rule_1_terminal("Armistice", - b.reg(r#"(?:pour )?l'armistice"#)?, - |_| Ok(helpers::month_day(11, 11)?.form(Form::Celebration)) - ); - b.rule_1_terminal("Saint Etienne (Alsace)", - b.reg(r#"(?:(?:le jour|la f[eê]te) de )?la (?:saint|st) [eé]tienne"#)?, - |_| Ok(helpers::month_day(12, 26)?.form(Form::Celebration)) - ); - b.rule_1_terminal("jeudi saint", - b.reg(r#"(?:le )?jeudi saint"#)?, - |_| Ok(helpers::cycle_nth_after(Grain::Day, -3, &helpers::easter()?)? - .form(Form::Celebration)) - ); - b.rule_1_terminal("vendredi saint", - b.reg(r#"(?:le )?vendredi saint"#)?, - |_| Ok(helpers::cycle_nth_after(Grain::Day, -2, &helpers::easter()?)? - .form(Form::Celebration)) - ); - b.rule_1_terminal("samedi saint", - b.reg(r#"(?:le )?samedi saint"#)?, - |_| Ok(helpers::cycle_nth_after(Grain::Day, -1, &helpers::easter()?)? - .form(Form::Celebration)) - ); - b.rule_1_terminal("pâques", - b.reg(r#"(?:la f[eê]te de |le jour de |le dimanche de )?p[âa]ques"#)?, - |_| Ok(helpers::easter()?.form(Form::Celebration)) - ); - b.rule_1_terminal("le lundi de pâques", - b.reg(r#"le lundi de p[âa]ques"#)?, - |_| Ok(helpers::cycle_nth_after(Grain::Day, 1, &helpers::easter()?)? - .form(Form::Celebration)) - ); - b.rule_1_terminal("ascension", - b.reg(r#"(?:la f[eê]te de l'|le jeudi de l'|l'|le jour de l')ascension"#)?, - |_| Ok(helpers::cycle_nth_after(Grain::Day, 39, &helpers::easter()?)? - .form(Form::Celebration)) - - ); - b.rule_1_terminal("pentecôte", - b.reg(r#"(?:la f[eê]te de |(?:le )?lundi de )?(?:la )?pentec[oô]te"#)?, - |_| Ok(helpers::cycle_nth_after(Grain::Day, 49, &helpers::easter()?)? - .form(Form::Celebration)) - ); - b.rule_1_terminal("1er mai", - b.reg(r#"(?:la )?f(e|ê)te du travail"#)?, - |_| Ok(helpers::month_day(5, 1)?.form(Form::Celebration)) - ); - b.rule_1_terminal("fêtes des pères", - b.reg(r#"(?:la )?f[eê]te des p[eè]res"#)?, - |_| { - let sundays_of_june = helpers::month(6)?.intersect(&helpers::day_of_week(Weekday::Sun)?)?; - let second_week_of_june = helpers::cycle_nth_after(Grain::Week, 2, &helpers::month_day(6, 1)?)?; - Ok(sundays_of_june.intersect(&second_week_of_june)? // third sunday of June - .form(Form::Celebration)) - } - ); - b.rule_1_terminal("fêtes des mères", - b.reg(r#"(?:la )?f[eê]te des m[eè]res"#)?, - |_| { - // It is the last last sunday of may - // If it is the same day as the Pentecost, it is the first sunday of june - // This case is not supported for now - Ok(helpers::day_of_week(Weekday::Sun)?.last_of(&helpers::month(5)?)? - .form(Form::Celebration)) - } - ); - b.rule_1_terminal("fête nationale", - b.reg(r#"(?:la )?f[eê]te (?:nationale|du (?:14|quatorze) juillet)"#)?, - |_| Ok(helpers::month_day(7, 14)? - .form(Form::Celebration)) - ); - b.rule_1_terminal("assomption", - b.reg(r#"(?:la f[eê]te de |le jour de )?l'assomption"#)?, - |_| Ok(helpers::month_day(8, 15)? - .form(Form::Celebration)) - ); - b.rule_2("à ", - b.reg(r#"au|[aà](?:l['a])?"#)?, - datetime_check!(form!(Form::Celebration)), - |_, a| Ok(a.value().clone()) - ); - b.rule_1_terminal("maintenant", - b.reg(r#"maintenant|tout de suite|en ce moment"#)?, - |_| helpers::cycle_nth(Grain::Second, 0) - ); - b.rule_1_terminal("aujourd'hui", - b.reg(r#"(?:aujourd'? ?hui)|(?:ce jour)|(?:dans la journ[ée]e?)"#)?, - |_| helpers::cycle_nth(Grain::Day, 0) - ); - // FIXME: "le lendemain" interpreted as demain, not as relative to another date - // but there is a rule "le lendemain du " - inconsistent - b.rule_1_terminal("demain", - b.reg(r#"(?:demain)|(?:le lendemain)"#)?, - |_| helpers::cycle_nth(Grain::Day, 1) - ); - b.rule_1_terminal("hier", - b.reg(r#"hier|la veille"#)?, - |_| helpers::cycle_nth(Grain::Day, -1) - ); - b.rule_1_terminal("fin du mois", - b.reg(r#"(?:(?:[aà] )?la )?fin (?:du|de) mois"#)?, - |_| { - let month = helpers::cycle_nth(Grain::Month, 1)?; - Ok(helpers::cycle_nth_after(Grain::Day, -10, &month)? - .span_to(&month, false)? - .latent() - .form(Form::PartOfMonth)) - } - ); - b.rule_1_terminal("après-demain", - b.reg(r#"apr(?:e|è)s[- ]?demain"#)?, - |_| helpers::cycle_nth(Grain::Day, 2) - ); - b.rule_1_terminal("avant-hier", - b.reg(r#"avant[- ]?hier"#)?, - |_| helpers::cycle_nth(Grain::Day, -2) - ); - b.rule_2("ce ", - b.reg(r#"ce"#)?, - datetime_check!(form!(Form::DayOfWeek{..})), - |_, datetime| datetime.value().the_nth_not_immediate(0) - ); - b.rule_2("ce ", - b.reg(r#"ce"#)?, - datetime_check!(), - |_, datetime| Ok(datetime.value().the_nth(0)? - .datetime_kind(datetime.value().datetime_kind.clone())) - ); - b.rule_2(" prochain", - datetime_check!(form!(Form::DayOfWeek{..})), - b.reg(r#"prochain"#)?, - |datetime, _| datetime.value().the_nth_not_immediate(0) - ); - b.rule_2(" prochain", - datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), - b.reg(r#"prochaine?"#)?, - |datetime, _| datetime.value().the_nth_not_immediate(0) - ); - b.rule_2("au prochain ", - b.reg(r#"(au|[aà] la) prochaine?"#)?, - datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), - |_, datetime| datetime.value().the_nth_not_immediate(0) - ); - b.rule_2(" prochain", - // The direction check is to avoid application of datetime_check(month) on rule result - // "avant " - datetime_check!(|datetime: &DatetimeValue| form!(Form::Month(_))(datetime) && !datetime.direction.is_some()), - b.reg(r#"prochain"#)?, - |datetime, _| datetime.value().the_nth_not_immediate(0) - ); - b.rule_2(" suivant|d'après", - datetime_check!(), - b.reg(r#"suivante?s?|d'apr[eéè]s"#)?, - |datetime, _| datetime.value().the_nth(1) - ); - b.rule_2(" dernier|passé", - datetime_check!(), - b.reg(r#"derni[eéè]re?|pass[ée]e?"#)?, - |datetime, _| datetime.value().the_nth(-1) - ); - b.rule_2(" en huit", - datetime_check!(form!(Form::DayOfWeek{..})), - b.reg(r#"en (?:huit|8)"#)?, - |datetime, _| datetime.value().the_nth(1) - ); - b.rule_2(" en quinze", - datetime_check!(form!(Form::DayOfWeek{..})), - b.reg(r#"en (quinze|15)"#)?, - |datetime, _| datetime.value().the_nth(2) - ); - b.rule_4("dernier de (latent)", - b.reg(r#"derni[eéè]re?"#)?, - datetime_check!(form!(Form::DayOfWeek{..})), - b.reg(r#"d['e]"#)?, - datetime_check!(), - |_, dow, _, datetime| dow.value().last_of(datetime.value()) - ); - b.rule_4("dernier de (latent)", - b.reg(r#"derni[eéè]re?"#)?, - cycle_check!(), - b.reg(r#"d['e]"#)?, - datetime_check!(), - |_, cycle, _, datetime| cycle.value().last_of(datetime.value()) - ); - b.rule_4(" de ", - ordinal_check!(), // the first - datetime_check!(), // Thursday - b.reg(r#"d[e']"#)?, // of - datetime_check!(), // march - |ordinal, a, _, b| { - b.value().intersect(a.value())?.the_nth(ordinal.value().value - 1) - } - ); - b.rule_3(" week-end de ", - ordinal_check!(), - b.reg(r#"week(?:\s|-)?end (?:d['eu]|en|du mois de)"#)?, - datetime_check!(form!(Form::Month(_))), - |ordinal, _, datetime| { - let weekend = helpers::weekend()?; - let nth_week_end = datetime.value().intersect(&weekend)?; - nth_week_end.the_nth(ordinal.value().value - 1) - } - ); - b.rule_2("dernier week-end de ", - b.reg(r#"(?:le )?dernier week(?:\s|-)?end (?:du mois d[e']|d['eu]|en)"#)?, - datetime_check!(form!(Form::Month(_))), - |_, datetime| { - let weekend = helpers::weekend()?; - weekend.last_of(datetime.value()) - } - ); - // FIXME: change latency ranges for years? E.g. latent until 1900? - b.rule_1("year", - integer_check_by_range!(1000, 2100), - |integer| helpers::year(integer.value().value as i32) - ); - b.rule_1("year (latent)", - integer_check_by_range!(-1000, 999), - |integer| Ok(helpers::year(integer.value().value as i32)?.latent()) - ); - b.rule_2("l'année ", - b.reg(r#"l[' ]an(?:n[eé]+)?"#)?, - integer_check!(), - |_, integer| helpers::year(integer.value().value as i32) - ); - b.rule_2("en ", - b.reg(r#"en"#)?, - datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::Year(_))(datetime)), - |_, year| Ok(year.value().clone()) - ); - b.rule_1("year (latent)", - integer_check_by_range!(2101, 3000), - |integer| Ok(helpers::year(integer.value().value as i32)?.latent()) - ); - b.rule_1_terminal("day of month (premier)", - b.reg(r#"premier|prem\.?|1er|1 er"#)?, - |_| helpers::day_of_month(1) - ); - b.rule_2("le (non ordinal)", - b.reg(r#"le"#)?, - integer_check_by_range!(1, 31), - |_, integer| helpers::day_of_month(integer.value().value as u32) - ); - b.rule_4("le à ", - b.reg(r#"le"#)?, - integer_check_by_range!(1, 31), - b.reg(r#"[aà]"#)?, - datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(_))(datetime)), - |_, integer, _, datetime| { - let day_of_month = helpers::day_of_month(integer.value().value as u32)?; - day_of_month.intersect(&datetime.value()) - } - ); - b.rule_2(" ", - integer_check_by_range!(1, 31), - datetime_check!(form!(Form::Month(_))), - |integer, month| Ok(month.value() - .intersect(&helpers::day_of_month(integer.value().value as u32)?)? - .form(Form::DayOfMonth)) - ); - b.rule_2(" ", - datetime_check!(form!(Form::DayOfWeek{..})), // Weird it is not used in the production of the rule - integer_check_by_range!(1, 31), - |_, integer| helpers::day_of_month(integer.value().value as u32) - ); - b.rule_4(" à )", - datetime_check!(form!(Form::DayOfWeek{..})), // Weird it is not used in the production of the rule - integer_check_by_range!(1, 31), - b.reg(r#"[aà]"#)?, - datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(_))(datetime)), - |_, integer, _, tod| helpers::day_of_month(integer.value().value as u32) - ?.intersect(tod.value()) - ); - b.rule_1(" (latent)", - integer_check_by_range!(1, 23), - |integer| Ok(helpers::hour(integer.value().value as u32, integer.value().value < 12)?.latent()) - ); - b.rule_1(" (latent)", - integer_check_by_range!(0, 0), - |_| Ok(helpers::hour(0, false)?.latent()) - ); - b.rule_1_terminal("midi", - b.reg(r#"midi(?: pile| exactement| pr[eé]cises)?"#)?, - |_| helpers::hour(12, false) - ); - b.rule_1_terminal("minuit", - b.reg(r#"minuit(?: pile| exactement| pr[eé]cises)?"#)?, - |_| helpers::hour(0, false) - ); - b.rule_2(" heures", - datetime_check!(form!(Form::TimeOfDay(TimeOfDayForm::Hour { .. }))), - b.reg(r#"h\.?(?:eure)?s?(?: pile| exactement| pr[eé]cises?)?"#)?, - |a, _| Ok(a.value().clone().not_latent()) - ); - b.rule_2(" (heures) pile", - datetime_check!(form!(Form::TimeOfDay(TimeOfDayForm::Hour { .. }))), - b.reg(r#"pile"#)?, - |a, _| Ok(a.value().clone().not_latent()) - ); - b.rule_2("à|vers ", - b.reg(r#"(?:vers|autour de|[aà] environ|aux alentours de|[aà])"#)?, - datetime_check!(form!(Form::TimeOfDay(_))), - |_, a| Ok(a.value().clone().not_latent()) - ); - b.rule_1_terminal("hh(:|h)mm (time-of-day)", - b.reg(r#"((?:[01]?\d)|(?:2[0-3]))[:h]([0-5]\d)"#)?, - |text_match| { - let hour: u32 = text_match.group(1).parse()?; - let minute: u32 = text_match.group(2).parse()?; - helpers::hour_minute(hour, minute, hour < 12) - } - ); - b.rule_3_terminal("hh(:|h)mm - hh(:|h)mm (time-of-day interval)", - b.reg(r#"((?:[01]?\d)|(?:2[0-3]))[:h]([0-5]\d)"#)?, - b.reg(r#" ?\- ?"#)?, - b.reg(r#"((?:[01]?\d)|(?:2[0-3]))[:h]([0-5]\d)"#)?, - |a, _, b| { - let hour_start: u32 = a.group(1).parse()?; - let minute_start: u32 = a.group(2).parse()?; - let hour_end: u32 = b.group(1).parse()?; - let minute_end: u32 = b.group(2).parse()?; - let start = helpers::hour_minute(hour_start, minute_start, hour_start < 12)?; - let end = helpers::hour_minute(hour_end, minute_end, hour_end < 12)?; - start.smart_span_to(&end, false) - } - ); - b.rule_1_terminal("hh:mm:ss", - b.reg(r#"((?:[01]?\d)|(?:2[0-3]))[:.]([0-5]\d)[:.]([0-5]\d)"#)?, - |text_match| helpers::hour_minute_second( - text_match.group(1).parse()?, - text_match.group(2).parse()?, - text_match.group(3).parse()?, - false - ) - - ); - b.rule_1_terminal("hhmm (military time-of-day)", - b.reg(r#"((?:[01]?\d)|(?:2[0-3]))([0-5]\d)"#)?, - |text_match| Ok(helpers::hour_minute( - text_match.group(1).parse()?, - text_match.group(2).parse()?, - false - )?.latent()) - ); - b.rule_1_terminal("quart (relative minutes)", - b.reg(r#"(?:un )?quart"#)?, - |_| Ok(RelativeMinuteValue(15)) - ); - b.rule_1_terminal("demi (relative minutes)", - b.reg(r#"demie?"#)?, - |_| Ok(RelativeMinuteValue(30)) - ); - b.rule_1_terminal("trois quarts (relative minutes)", - b.reg(r#"(?:3|trois) quarts?"#)?, - |_| Ok(RelativeMinuteValue(45)) - ); - b.rule_1("number (as relative minutes)", - integer_check_by_range!(1, 59), - |a| Ok(RelativeMinuteValue(a.value().value as i32)) - ); - b.rule_2("number minutes (as relative minutes)", - integer_check_by_range!(1, 59), - b.reg(r#"min\.?(?:ute)?s?"#)?, - |a, _| Ok(RelativeMinuteValue(a.value().value as i32)) - ); - b.rule_2(" (as relative minutes)", - datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(TimeOfDayForm::Hour { .. }))(datetime)), - relative_minute_check!(), - |datetime, minutes| helpers::hour_relative_minute( - datetime.value().form_time_of_day()?.full_hour(), - minutes.value().0, - datetime.value().form_time_of_day()?.is_12_clock() - ) - ); - b.rule_3(" (as relative minutes) exactly", - datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(TimeOfDayForm::Hour { .. }))(datetime)), - relative_minute_check!(), - b.reg(r#"pile|exactement|pr[ée]cises?"#)?, - |datetime, minutes, _| helpers::hour_relative_minute( - datetime.value().form_time_of_day()?.full_hour(), - minutes.value().0, - datetime.value().form_time_of_day()?.is_12_clock() - ) - ); - b.rule_3(" moins (as relative minutes)", - datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(TimeOfDayForm::Hour { .. }))(datetime)), - b.reg(r#"moins(?: le)?"#)?, - relative_minute_check!(), - |datetime, _, minutes| helpers::hour_relative_minute( - datetime.value().form_time_of_day()?.full_hour(), - -1 * minutes.value().0, - datetime.value().form_time_of_day()?.is_12_clock() - ) - ); - b.rule_4(" moins (as relative minutes) exactly ", - datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(TimeOfDayForm::Hour { .. }))(datetime)), - b.reg(r#"moins(?: le)?"#)?, - relative_minute_check!(), - b.reg(r#"pile|exactement|pr[ée]cises?"#)?, - |datetime, _, minutes, _| helpers::hour_relative_minute( - datetime.value().form_time_of_day()?.full_hour(), - -1 * minutes.value().0, - datetime.value().form_time_of_day()?.is_12_clock() - ) - ); - b.rule_3(" et|passé de ", - datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(TimeOfDayForm::Hour { .. }))(datetime)), - b.reg(r#"et|pass[ée]e?s? de"#)?, - relative_minute_check!(), - |datetime, _, minutes| helpers::hour_relative_minute( - datetime.value().form_time_of_day()?.full_hour(), - minutes.value().0, - datetime.value().form_time_of_day()?.is_12_clock() - ) - ); - b.rule_4(" et|passé de exactly", - datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(TimeOfDayForm::Hour { .. }))(datetime)), - b.reg(r#"et|pass[ée]e?s? de"#)?, - relative_minute_check!(), - b.reg(r#"pile|exactement|pr[ée]cises?"#)?, - |datetime, _, minutes, _| helpers::hour_relative_minute( - datetime.value().form_time_of_day()?.full_hour(), - minutes.value().0, - datetime.value().form_time_of_day()?.is_12_clock() - ) - ); - b.rule_4(" de exactly", - datetime_check!(form!(Form::TimeOfDay(_))), - b.reg(r#"(?:d[eu] |dans )(?:l[ 'a])?"#)?, - datetime_check!(form!(Form::PartOfDay(_))), - b.reg(r#"pile|exactement|pr[eé]cises?"#)?, - |a, _, b, _| Ok(a.value().intersect(b.value())?.form(a.value().form.clone())) - ); - // Adding "pour" here makes time-of-day ambiguous w/ Duration - // Duration has less priority than Datetime types, therefore duration will be output only - // if the output kind filter is set for Duration - b.rule_2("à ", - b.reg(r#"[aà]|pour"#)?, - datetime_check!(form!(Form::TimeOfDay(_))), - |_, a| Ok(a.value().clone().not_latent()) - ); - b.rule_2("vers ", - b.reg(r#"(?:plut[ôo]t )?(?:vers|autour de|[aà] environ|aux alentours de)"#)?, - datetime_check!(form!(Form::TimeOfDay(_))), - |_, a| Ok(a.value().clone().not_latent().precision(Precision::Approximate)) - ); - // Written time/date in numeric formats - b.rule_1_terminal("hh(:|h)mm (time-of-day)", - b.reg(r#"((?:[01]?\d)|(?:2[0-3]))[:h]([0-5]\d)"#)?, - |text_match| { - let hour: u32 = text_match.group(1).parse()?; - let minute: u32 = text_match.group(2).parse()?; - helpers::hour_minute(hour, minute, hour < 12) - } - ); - b.rule_3_terminal("hh(:|h)mm - hh(:|h)mm (time-of-day interval)", - b.reg(r#"((?:[01]?\d)|(?:2[0-3]))[:h]([0-5]\d)"#)?, - b.reg(r#" ?\- ?"#)?, - b.reg(r#"((?:[01]?\d)|(?:2[0-3]))[:h]([0-5]\d)"#)?, - |a, _, b| { - let hour_start: u32 = a.group(1).parse()?; - let minute_start: u32 = a.group(2).parse()?; - let hour_end: u32 = b.group(1).parse()?; - let minute_end: u32 = b.group(2).parse()?; - let start = helpers::hour_minute(hour_start, minute_start, hour_start < 12)?; - let end = helpers::hour_minute(hour_end, minute_end, hour_end < 12)?; - start.smart_span_to(&end, false) - } - ); - b.rule_1_terminal("hh:mm:ss", - b.reg(r#"((?:[01]?\d)|(?:2[0-3]))[:.]([0-5]\d)[:.]([0-5]\d)"#)?, - |text_match| helpers::hour_minute_second( - text_match.group(1).parse()?, - text_match.group(2).parse()?, - text_match.group(3).parse()?, - false - ) - - ); - b.rule_1_terminal("hhmm (military time-of-day)", - b.reg(r#"((?:[01]\d)|(?:2[0-3]))([0-5]\d)"#)?, - |text_match| Ok(helpers::hour_minute( - text_match.group(1).parse()?, - text_match.group(2).parse()?, - false - )?.latent()) - ); - b.rule_1_terminal("yyyy-mm-dd - ISO", - b.reg(r#"(\d{4})[-/](0?[1-9]|1[0-2])[-/](3[01]|[12]\d|0?[1-9])"#)?, - |text_match| helpers::year_month_day( - text_match.group(1).parse()?, - text_match.group(2).parse()?, - text_match.group(3).parse()?) - ); - // Supporting these date formats also with whitespace as a separator for legacy - // But this seems too permissive? - b.rule_1_terminal("dd/mm/yy or dd/mm/yyyy", - b.reg(r#"(0?[1-9]|[12]\d|3[01])[-\./ ](0?[1-9]|1[0-2])[-\./ ](\d{2,4})"#)?, - |text_match| helpers::year_month_day( - text_match.group(3).parse()?, - text_match.group(2).parse()?, - text_match.group(1).parse()?, - ) - ); - b.rule_1_terminal("dd/mm", - b.reg(r#"(0?[1-9]|[12]\d|3[01])[\./ ](1[0-2]|0?[1-9])"#)?, - |text_match| helpers::month_day( - text_match.group(2).parse()?, - text_match.group(1).parse()?) - ); - // End of Written time/date in numeric formats - b.rule_1_terminal("matin", - b.reg(r#"mat(?:in[ée]?e?)?"#)?, - |_| Ok(helpers::hour(4, false)? - .span_to(&helpers::hour(12, false)?, false)? - .latent() - .form(Form::PartOfDay(PartOfDayForm::Morning))) - ); - b.rule_1_terminal("début de matinée", - b.reg(r#"(?:le matin (?:tr[eè]s )?t[ôo]t|(?:tr[eè]s )?t[ôo]t le matin|d[ée]but de matin[ée]e)"#)?, - |_| Ok(helpers::hour(4, false)? - .span_to(&helpers::hour(9, false)?, false)? - .latent() - .form(Form::PartOfDay(PartOfDayForm::Morning))) - ); - b.rule_1_terminal("petit dejeuner", - b.reg(r#"petit[- ]d[ée]jeuner"#)?, - |_| Ok(helpers::hour(5, false)? - .span_to(&helpers::hour(10, false)?, false)? - .latent() - .form(Form::Meal)) - ); - b.rule_1_terminal("milieu de matinée", - b.reg(r#"milieu de matin[ée]e"#)?, - |_| Ok(helpers::hour(9, false)? - .span_to(&helpers::hour(11, false)?, false)? - .latent() - .form(Form::PartOfDay(PartOfDayForm::Morning))) - ); - b.rule_1_terminal("brunch", - b.reg(r#"brunch"#)?, - |_| Ok(helpers::hour(10, false)? - .span_to(&helpers::hour(15, false)?, false)? - .latent() - .form(Form::Meal)) - ); - b.rule_1_terminal("fin de matinée", - b.reg(r#"fin de matin[ée]e"#)?, - |_| Ok(helpers::hour(10, false)? - .span_to(&helpers::hour(12, false)?, false)? - .latent() - .form(Form::PartOfDay(PartOfDayForm::Morning))) - ); - b.rule_1_terminal("déjeuner", - b.reg(r#"d[eéè]jeuner"#)?, - |_| Ok(helpers::hour(12, false)? - .span_to(&helpers::hour(14, false)?, false)? - .latent() - .form(Form::Meal)) - ); - b.rule_1_terminal("après le déjeuner", - b.reg(r#"apr[eè]s (?:le )?d[eéè]jeuner"#)?, - |_| { - let period = helpers::hour(13, false)? - .span_to(&helpers::hour(17, false)?, false)?; - Ok(helpers::cycle_nth(Grain::Day, 0)?.intersect(&period)?.form(Form::PartOfDay(PartOfDayForm::Afternoon))) - } - ); - b.rule_1_terminal("avant le déjeuner", - b.reg(r#"avant (?:le )?d[eéè]jeuner"#)?, - |_| { - let period = helpers::hour(10, false)? - .span_to(&helpers::hour(12, false)?, false)?; - Ok(helpers::cycle_nth(Grain::Day, 0)?.intersect(&period)?.form(Form::PartOfDay(PartOfDayForm::Morning))) - } - ); - b.rule_1_terminal("avant le travail", - b.reg(r#"avant le travail"#)?, - |_| { - let period = helpers::hour(7, false)? - .span_to(&helpers::hour(10, false)?, false)?; - Ok(helpers::cycle_nth(Grain::Day, 0)?.intersect(&period)?.form(Form::PartOfDay(PartOfDayForm::Morning))) - } - ); - b.rule_1_terminal("pendant le travail", - b.reg(r#"pendant le travail"#)?, - |_| { - let period = helpers::hour(9, false)? - .span_to(&helpers::hour(19, false)?, false)?; - Ok(helpers::cycle_nth(Grain::Day, 0)?.intersect(&period)?.form(Form::PartOfDay(PartOfDayForm::None))) - } - ); - b.rule_1_terminal("après le travail", - b.reg(r#"apr[eè]s (?:le )?travail"#)?, - |_| { - let period = helpers::hour(17, false)? - .span_to(&helpers::hour(21, false)?, false)?; - Ok(helpers::cycle_nth(Grain::Day, 0)?.intersect(&period)?.form(Form::PartOfDay(PartOfDayForm::Evening))) - } - ); - b.rule_1_terminal("après-midi", - b.reg(r#"apr[eéè]s?[ \-]?midi|aprem"#)?, - |_| { - Ok(helpers::hour(12, false)? - .span_to(&helpers::hour(19, false)?, false)? - .latent() - .form(Form::PartOfDay(PartOfDayForm::Afternoon))) - } - ); - b.rule_1_terminal("début d'après-midi", - b.reg(r#"d[ée]but (?:d'|de l')(?:apr[eéè]s?[ \-]?midi|aprem)"#)?, - |_| { - Ok(helpers::hour(12, false)? - .span_to(&helpers::hour(15, false)?, false)? - .latent() - .form(Form::PartOfDay(PartOfDayForm::Afternoon))) - } - ); - b.rule_1_terminal("milieu d'après-midi", - b.reg(r#"milieu (?:d'|de l')(?:apr[eéè]s?[ \-]?midi|aprem)"#)?, - |_| { - Ok(helpers::hour(15, false)? - .span_to(&helpers::hour(17, false)?, false)? - .latent() - .form(Form::PartOfDay(PartOfDayForm::Afternoon))) - } - ); - b.rule_1_terminal("gouter", - b.reg(r#"(?:(?:[àa] )?l[' ]heure du|au moment du|pendant le|pour le) go[uû]ter"#)?, - |_| Ok(helpers::hour(16, false)? - .span_to(&helpers::hour(18, false)?, false)? - .form(Form::Meal)) - ); - b.rule_1_terminal("thé", - b.reg(r#"(?:(?:[àa] )?l[' ]heure du|au moment du|pendant le|pour le) th[eé]"#)?, - |_| Ok(helpers::hour(15, false)? - .span_to(&helpers::hour(17, false)?, false)? - .form(Form::Meal)) - ); - b.rule_1_terminal("cafe", - b.reg(r#"(?:(?:[àa] )?l[' ]heure du|au moment du|pendant le|pour le) caf[eé]"#)?, - |_| Ok(helpers::hour(14, false)? - .span_to(&helpers::hour(16, false)?, false)? - .form(Form::Meal)) - ); - b.rule_1_terminal("fin d'après-midi", - b.reg(r#"fin (?:d'|de l')(?:apr[eéè]s?[ \-]?midi|aprem)"#)?, - |_| { - Ok(helpers::hour(17, false)? - .span_to(&helpers::hour(19, false)?, false)? - .latent() - .form(Form::PartOfDay(PartOfDayForm::Afternoon))) - } - ); - // TODO: APERO - b.rule_1_terminal("début de journée", - b.reg(r#"d[ée]but de (?:la )?journ[ée]e"#)?, - |_| { - Ok(helpers::hour(6, false)? - .span_to(&helpers::hour(10, false)?, false)? - .latent() - .form(Form::PartOfDay(PartOfDayForm::Morning))) - } - ); - b.rule_1_terminal("milieu de journée", - b.reg(r#"milieu de (?:la )?journ[ée]e"#)?, - |_| { - Ok(helpers::hour(11, false)? - .span_to(&helpers::hour(16, false)?, false)? - .latent() - .form(Form::PartOfDay(PartOfDayForm::None))) - } - ); - b.rule_1_terminal("fin de journée", - b.reg(r#"fin de (?:la )?journ[ée]e"#)?, - |_| { - Ok(helpers::hour(17, false)? - .span_to(&helpers::hour(21, false)?, false)? - .latent() - .form(Form::PartOfDay(PartOfDayForm::Evening))) - } - ); - b.rule_1_terminal("soir", - b.reg(r#"soir[ée]?e?"#)?, - |_| { - Ok(helpers::hour(18, false)? - .span_to(&helpers::hour(0, false)?, false)? - .latent() - .form(Form::PartOfDay(PartOfDayForm::Evening))) - } - ); - b.rule_1_terminal("début de soirée", - b.reg(r#"d[ée]but de (?:la )?soir[ée]e?"#)?, - |_| { - Ok(helpers::hour(18, false)? - .span_to(&helpers::hour(21, false)?, false)? - .latent() - .form(Form::PartOfDay(PartOfDayForm::Evening))) - } - ); - b.rule_1_terminal("fin de soirée", - b.reg(r#"fin de (?:la )?soir[ée]e?"#)?, - |_| { - Ok(helpers::hour(21, false)? - .span_to(&helpers::hour(0, false)?, false)? - .latent() - .form(Form::PartOfDay(PartOfDayForm::Evening))) - } - ); - b.rule_1_terminal("diner", - b.reg(r#"d[iî]ner|souper"#)?, - |_| Ok(helpers::hour(18, false)? - .span_to(&helpers::hour(23, false)?, false)? - .form(Form::Meal)) - ); - b.rule_1_terminal("nuit", - b.reg(r#"nuit"#)?, - |_| { - Ok(helpers::hour(22, false)? - .span_to(&helpers::hour(6, false)?, false)? - .latent() - .form(Form::PartOfDay(PartOfDayForm::Night))) - } - ); - b.rule_2("a l'heure de ", - b.reg(r#"(?:[àa] )?l[' ]heure du|au moment du|pendant l[ea']|au|pour l[ea']|l[ea']"#)?, - datetime_check!(|datetime: &DatetimeValue| form!(Form::Meal)(datetime)), - |_, a| Ok(a.value().clone().not_latent()) - ); - b.rule_2(" ", - datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), - datetime_check!(form!(Form::Meal)), - |a, b| a.value().intersect(b.value()) - ); - b.rule_2("prep? & article ", // This is very catch-all/junky - b.reg(r#"(?:pendant |durant |dans |d[eè]s )?l[ae']?|en|au"#)?, - datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime)), - |_, a| Ok(a.value().clone().not_latent()) - ); - b.rule_2("ce ", - b.reg(r#"cet?t?e?"#)?, - datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), - |_, datetime| Ok(helpers::cycle_nth(Grain::Day, 0)? - .intersect(datetime.value())? - .form(datetime.value().form.clone()) - .datetime_kind(DatetimeKind::DatetimeComplement { date_and_time: true, today: true })) - ); - b.rule_2(" ", - datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), - datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), - |a, b| a.value().intersect(b.value()) - ); - b.rule_2(" du matin", - datetime_check!(form!(Form::TimeOfDay(_))), - b.reg(r#"(?:(?:du|dans|de) )?(?:(?:au|le|la) )?mat(?:in[ée]?e?)?(?: pile| exactement| pr[eé]cises?)?"#)?, - |a, _| { - let period = helpers::hour(0, false)? - .span_to(&helpers::hour(12, false)?, false)?; - Ok(a.value().intersect(&period)?.form(a.value().form.clone())) - } - ); - b.rule_2(" de l'apres-midi", - datetime_check!(form!(Form::TimeOfDay(_))), - b.reg(r#"(?:dans |de )?l[' ]apr[eè]s[\- ]midi(?: pile| exactement| pr[eé]cises?)?"#)?, - |a, _| { - let period = helpers::hour(12, false)? - .span_to(&helpers::hour(19, false)?, false)?; - Ok(a.value().intersect(&period)?.form(a.value().form.clone())) - } - ); - b.rule_2(" du soir", - datetime_check!(form!(Form::TimeOfDay(_))), - b.reg(r#"(?:(?:du|dans|de) )?(?:(?:au|le|la) )?soir[ée]?e?(?: pile| exactement| pr[eé]cises?)?"#)?, - |a, _| { - let period = helpers::hour(16, false)? - .span_to(&helpers::hour(0, false)?, false)?; - Ok(a.value().intersect(&period)?.form(a.value().form.clone())) - } - ); - b.rule_3(" du ", - datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), - b.reg(r#"du"#)?, - datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), - |a, _, b| b.value().intersect(a.value()) - ); - b.rule_1_terminal("(ce/le) week-end", - b.reg(r#"(?:[cl]e )?week(?:\s|-)?end"#)?, - |_| helpers::weekend() - ); - b.rule_1_terminal("le week-end dernier", - b.reg(r#"le week(?:\s|-)?end dernier"#)?, - |_| { - let weekend = helpers::weekend()?; - Ok(weekend.the_nth(-1)?.datetime_kind(DatetimeKind::DatePeriod)) - } - ); - b.rule_1_terminal("le week-end prochain", - b.reg(r#"le week(?:\s|-)?end prochain|le prochain week(?:\s|-)?end"#)?, - |_| { - let weekend = helpers::weekend()?; - Ok(weekend.the_nth(1)?.datetime_kind(DatetimeKind::DatePeriod)) - } - ); - b.rule_1_terminal("début de semaine", - b.reg(r#"(?:en |au )?d[ée]but de (?:cette |la )?semaine"#)?, - |_| helpers::day_of_week(Weekday::Mon) - ?.span_to(&helpers::day_of_week(Weekday::Tue)?, false) - ); - b.rule_1_terminal("milieu de semaine", - b.reg(r#"(?:en |au )?milieu de (?:cette |la )?semaine"#)?, - |_| helpers::day_of_week(Weekday::Wed) - ?.span_to(&helpers::day_of_week(Weekday::Thu)?, false) - ); - b.rule_1_terminal("fin de semaine (Warning: this is the weekend in Quebec)", - b.reg(r#"(?:en |à la )?fin de (?:cette |la )?semaine"#)?, - |_| helpers::day_of_week(Weekday::Thu) - ?.span_to(&helpers::day_of_week(Weekday::Sun)?, false) - ); - b.rule_1_terminal("en semaine", - b.reg(r#"(?:pendant la |en )semaine"#)?, - |_| helpers::day_of_week(Weekday::Mon) - ?.span_to(&helpers::day_of_week(Weekday::Fri)?, false) - ); - b.rule_1_terminal("season", - b.reg(r#"(?:cet )?(?:été|ete)"#)?, - |_| helpers::month_day(6, 21)?.span_to(&helpers::month_day(9, 23)?, false) - ); - b.rule_1_terminal("season", - b.reg(r#"(?:cet )?automne"#)?, - |_| helpers::month_day(9, 23)?.span_to(&helpers::month_day(12, 21)?, false) - ); - b.rule_1_terminal("season", - b.reg(r#"(?:cet )?hiver"#)?, - |_| helpers::month_day(12, 21)?.span_to(&helpers::month_day(3, 20)?, false) - ); - b.rule_1_terminal("season", - b.reg(r#"(?:ce )?printemps"#)?, - |_| helpers::month_day(3, 20)?.span_to(&helpers::month_day(6, 21)?, false) - ); - b.rule_2("le ", - b.reg(r#"l[ea]"#)?, - datetime_check!(|datetime: &DatetimeValue| !datetime.latent), - |_, a| Ok(a.value().clone()) - ); - b.rule_4("dd-dd (interval)", - b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, - b.reg(r#"\-|(?:jusqu')?au"#)?, - b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, - datetime_check!(form!(Form::Month(_))), - |a, _, b, month| { - let start = month.value().intersect(&helpers::day_of_month(a.group(1).parse()?)?)?; - let end = month.value().intersect(&helpers::day_of_month(b.group(1).parse()?)?)?; - start.span_to(&end, true) - } - ); - b.rule_4("-dd (interval)", - datetime_check!(), - b.reg(r#"\-|(?:jusqu')?au"#)?, - b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, - datetime_check!(form!(Form::Month(_))), - |datetime, _, text_match, month| { - let start = month.value().intersect(datetime.value())?; - let end = month.value().intersect(&helpers::day_of_month(text_match.group(1).parse()?)?)?; - start.span_to(&end, true) - } - ); - b.rule_5("- dd (interval)", - datetime_check!(), - b.reg(r#"\-|(?:jusqu')?au"#)?, - datetime_check!(form!(Form::DayOfWeek{..})), - b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, - datetime_check!(form!(Form::Month(_))), - |datetime, _, _, text_match, month| { - let start = month.value().intersect(datetime.value())?; - let end = month.value().intersect(&helpers::day_of_month(text_match.group(1).parse()?)?)?; - start.span_to(&end, true) - } - ); - b.rule_6(" 1er- dd (interval)", - datetime_check!(form!(Form::DayOfWeek{..})), - b.reg(r#"premier|prem\.?|1er|1 er"#)?, - b.reg(r#"\-|(?:jusqu')?au"#)?, - datetime_check!(form!(Form::DayOfWeek{..})), - b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, - datetime_check!(form!(Form::Month(_))), - |_, _, _, _, text_match, month| { - let start = month.value().intersect(&helpers::day_of_month(1)?)?; - let end = month.value().intersect(&helpers::day_of_month(text_match.group(1).parse()?)?)?; - start.span_to(&end, true) - } - ); - b.rule_6("du dd- dd (interval)", - b.reg(r#"du"#)?, - b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, - b.reg(r#"\-|(?:jusqu')?au"#)?, - datetime_check!(form!(Form::DayOfWeek{..})), - b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, - datetime_check!(form!(Form::Month(_))), - |_, a, _, _, b, month| { - let start = month.value().intersect(&helpers::day_of_month(a.group(1).parse()?)?)?; - let end = month.value().intersect(&helpers::day_of_month(b.group(1).parse()?)?)?; - start.span_to(&end, true) - } - ); - b.rule_6("du dd- dd (interval)", - b.reg(r#"du"#)?, - datetime_check!(), - b.reg(r#"\-|(?:jusqu')?au"#)?, - datetime_check!(form!(Form::DayOfWeek{..})), - b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, - datetime_check!(form!(Form::Month(_))), - |_, datetime, _, _, text_match, month| { - let start = month.value().intersect(datetime.value())?; - let end = month.value().intersect(&helpers::day_of_month(text_match.group(1).parse()?)?)?; - start.span_to(&end, true) - } - ); - b.rule_4("la nuit ", - b.reg(r#"(dans|pendant|durant) la nuit (?:du|de)"#)?, - datetime_check!(form!(Form::DayOfWeek{..})), - b.reg(r#"\-|(?:jusqu')?au"#)?, - datetime_check!(form!(Form::DayOfWeek{..})), - |_, start, _, end| { - let start = start.value().intersect(&helpers::hour(22, false)?)?; - let end = end.value().intersect(&helpers::hour(6, false)?)?; - start.span_to(&end, false) - } - ); - b.rule_5("entre dd et dd (interval)", - b.reg(r#"entre(?: le)?"#)?, - b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, - b.reg(r#"et(?: le)?"#)?, - b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, - datetime_check!(form!(Form::Month(_))), - |_, a, _, b, month| { - let start = month.value().intersect(&helpers::day_of_month(a.group(1).parse()?)?)?; - let end = month.value().intersect(&helpers::day_of_month(b.group(1).parse()?)?)?; - start.span_to(&end, true) - } - ); - b.rule_4_terminal("du dd au dd(interval)", - b.reg(r#"du"#)?, - b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, - b.reg(r#"(?:jusqu')?au"#)?, - b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, - |_, a, _, b| { - let start = helpers::day_of_month(a.group(1).parse()?)?; - let end = helpers::day_of_month(b.group(1).parse()?)?; - start.span_to(&end, true) - } - ); - b.rule_2("fin (interval)", - b.reg(r#"fin(?: du mois d[e']? ?)?"#)?, - datetime_check!(form!(Form::Month(_))), - |_, month| { - let start = month.value().intersect(&helpers::day_of_month(25)?)?; - let end = helpers::cycle(Grain::Day)?.last_of(month.value())?; - start.span_to(&end, true) - } - ); - b.rule_2("début (interval)", - b.reg(r#"d[ée]but(?: du mois d[e'] ?)?"#)?, - datetime_check!(form!(Form::Month(_))), - |_, month| { - let start = month.value().intersect(&helpers::day_of_month(1)?)?; - let end = month.value().intersect(&helpers::day_of_month(5)?)?; - start.span_to(&end, true) - } - ); - b.rule_2("première quinzaine de (interval)", - b.reg(r#"(?:premi[èe]re|1 ?[èe]re) (?:quinzaine|15 ?aine) d[e']"#)?, - datetime_check!(form!(Form::Month(_))), - |_, month| { - let start = month.value().intersect(&helpers::day_of_month(1)?)?; - let end = month.value().intersect(&helpers::day_of_month(14)?)?; - start.span_to(&end, true) - } - ); - b.rule_2("deuxième quinzaine de (interval)", - b.reg(r#"(?:deuxi[èe]me|2 ?[èe]me) (?:quinzaine|15 ?aine) d[e']"#)?, - datetime_check!(form!(Form::Month(_))), - |_, month| { - let start = month.value().intersect(&helpers::day_of_month(15)?)?; - let end = helpers::cycle(Grain::Day)?.last_of(month.value())?; - start.span_to(&end, true) - } - ); - b.rule_2("", - b.reg(r#"mi[- ]"#)?, - datetime_check!(form!(Form::Month(_))), - |_, month| { - let start = month.value().intersect(&helpers::day_of_month(10)?)?; - let end = month.value().intersect(&helpers::day_of_month(19)?)?; - start.span_to(&end, true) - } - ); - - /* Intervals */ - b.rule_3(" - (interval)", - datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), - b.reg(r#" \- |(?:jusqu')?(?:au|[aà])"#)?, - datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), - |a, _, b| a.value().span_to(b.value(), true) - ); - b.rule_3(" avant (interval)", - datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), - b.reg(r#"jusqu'(?:au|[aà])|avant"#)?, - datetime_check!(form!(Form::TimeOfDay(_))), - |a, _, b| a.value().span_to(b.value(), false) - ); - b.rule_4("de - (interval)", - b.reg(r#"depuis|d[e'u]?"#)?, - datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), - b.reg(r#" \- |(?:jusqu')?(?:au|[aà])"#)?, - datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), - |_, a, _, b| a.value().span_to(b.value(), true) - ); - b.rule_4("entre et (interval)", - b.reg(r#"entre"#)?, - datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), - b.reg(r#"et"#)?, - datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), - |_, a, _, b| a.value().span_to(b.value(), true) - ); - // Specific case with years - b.rule_5("de - (interval)", - b.reg(r#"depuis|d[e'u]?"#)?, - datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), - b.reg(r#" \- |(?:jusqu')?(?:au|[aà])"#)?, - datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime) && datetime.is_coarse_grain_greater_than(Grain::Year)), - datetime_check!(form!(Form::Year(_))), - |_, a, _, b, year| a.value().span_to(b.value(), true)?.intersect(year.value()) - ); - b.rule_5("entre et (interval)", - b.reg(r#"entre"#)?, - datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), - b.reg(r#"et"#)?, - datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime) && datetime.is_coarse_grain_greater_than(Grain::Year)), - datetime_check!(form!(Form::Year(_))), - |_, a, _, b, year| a.value().span_to(b.value(), true)?.intersect(year.value()) - ); - b.rule_3(" - (interval)", - datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(_))(datetime)), - b.reg(r#" \- |(?:jusqu')?(?:au|[aà])"#)?, - datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(_))(datetime)), - |a, _, b| a.value().smart_span_to(b.value(), false) - ); - b.rule_4("de - (interval)", - b.reg(r#"(?:[aà] partir )?d['e]"#)?, - datetime_check!(form!(Form::TimeOfDay(_))), - b.reg(r#"(?:jusqu')?(?:au|[aà])"#)?, - datetime_check!(form!(Form::TimeOfDay(_))), - |_, a, _, b| a.value().smart_span_to(b.value(), false) - ); - b.rule_2("de maintenant - (interval)", - b.reg(r#"(?:[aà] partir )?de maintenant (?:jusqu')?(?:au|[aà])"#)?, - datetime_check!(form!(Form::TimeOfDay(_))), - |_, a| helpers::cycle_nth(Grain::Second, 0)?.smart_span_to(a.value(), false) - ); - b.rule_3("de - maintenant (interval)", - b.reg(r#"(?:[aà] partir )?d['e]"#)?, - datetime_check!(form!(Form::TimeOfDay(_))), - b.reg(r#"(?:jusqu')?(?:au|[aà]) maintenant"#)?, - |_, a, _| { - let now = helpers::cycle_nth(Grain::Second, 0)?; - a.value().smart_span_to(&now, false) - } - ); - b.rule_4("entre et (interval)", - b.reg(r#"entre"#)?, - datetime_check!(form!(Form::TimeOfDay(_))), - b.reg(r#"et"#)?, - datetime_check!(form!(Form::TimeOfDay(_))), - |_, a, _, b| a.value().smart_span_to(b.value(), false) - ); - b.rule_2("jusqu'à ", - b.reg(r#"(?:n[ ']importe quand )?jusqu'(?:au|[aà]|en)"#)?, - datetime_check!(|datetime: &DatetimeValue| !datetime.latent), - |_, datetime| Ok(datetime.value().clone().mark_before_end()) - ); - b.rule_2("avant ", - b.reg(r#"(?:n[ ']importe quand )?avant"#)?, - datetime_check!(|datetime: &DatetimeValue| !datetime.latent), - |_, datetime| Ok(datetime.value().clone().mark_before_start()) - ); - b.rule_2("après ", - b.reg(r#"apr[eè]s"#)?, - datetime_check!(|datetime: &DatetimeValue| !datetime.latent), - |_, datetime| Ok(datetime.value().clone().mark_after_end()) - ); - b.rule_2("à partir de ", - b.reg(r#"[aà] partir d['eu]"#)?, - datetime_check!(|datetime: &DatetimeValue| !datetime.latent), - |_, datetime| Ok(datetime.value().clone().mark_after_start()) - ); - b.rule_2("à partir de ", - b.reg(r#"[aà] partir d['eu](?:l[ 'a])?"#)?, - datetime_check!(form!(Form::PartOfDay(_))), - |_, datetime| Ok(datetime.value().clone().mark_after_start()) - ); - b.rule_2("après le ", - b.reg(r#"apr(?:e|è)s le"#)?, - integer_check_by_range!(1, 31), - |_, integer| Ok(helpers::day_of_month(integer.value().value as u32)?.mark_after_end()) - ); - b.rule_2("après le ", - b.reg(r#"[aà] partir d['eu]"#)?, - integer_check_by_range!(1, 31), - |_, integer| Ok(helpers::day_of_month(integer.value().value as u32)?.mark_after_start()) - ); - b.rule_2("il y a ", - b.reg(r#"il y a"#)?, - duration_check!(), - |_, duration| duration.value().ago() - ); - // With "depuis/d'ici", interpretation of duration ambiguous with time-of-day we choose time-of-day - // I.e. "x heures (y)", but not "x heures et y minutes", "x minutes", etc. - // FIXME: some time-of-day patterns should be removed, e.g. "x heures y minutes" - they are not - // proper time-of-day, but they can be duration expressions - // TODO: check if Grain::Second here breaks DatePeriod - cf. implem. of equivalent in English? - b.rule_2("depuis ", - b.reg(r#"depuis|[cç]a fait"#)?, - duration_check!(), - |_, duration| { - if duration.value().get_coarser_grain() == Grain::Hour { - return Err(RuleError::Invalid.into()) - } - duration.value().ago()? - .span_to(&helpers::cycle_nth(Grain::Second, 0)?, false) - }); - b.rule_2("depuis ", - b.reg(r#"depuis"#)?, - datetime_check!(|datetime: &DatetimeValue| !datetime.latent), - |_, datetime| Ok(datetime.value().the_nth(-1)?.mark_after_start()) - ); - b.rule_2("d'ici ", - b.reg(r#"d'ici|dans l(?:'|es?)"#)?, - duration_check!(), - |_, duration| { - let duration_grain = duration.value().get_coarser_grain(); - // Priority to d'ici - if duration_grain == Grain::Hour && - // FIXME: There must be a better way to do this check! - duration.value().period.0.get(Grain::Hour as usize).unwrap_or(&0) <= &23 { - return Err(RuleError::Invalid.into()) - } - let grain = if duration_grain.is_date_grain() { Grain::Day } else { Grain::Second }; - let start = helpers::cycle_nth(grain, 0)?; - let end = if grain == Grain::Day { duration.value().in_present_day()? } else { duration.value().in_present()? }; - start.span_to(&end, true) - } - ); - b.rule_2("dans le ", - b.reg(r#"dans l(?:'|es?)"#)?, - duration_check!(), - |_, duration| { - let duration_grain = duration.value().get_coarser_grain(); - let grain = if duration_grain.is_date_grain() { Grain::Day } else { Grain::Second }; - let start = helpers::cycle_nth(grain, 0)?; - let end = if grain == Grain::Day { duration.value().in_present_day()? } else { duration.value().in_present()? }; - start.span_to(&end, false) - } - ); - b.rule_2("d'ici ", - b.reg(r#"d'ici"#)?, - datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(_))(datetime)), - |_, tod| { - // FIXME: This adds one second to the value of now+then - let now = helpers::cycle_nth(Grain::Second, 0)?; - let then = tod.value().clone().mark_before_start(); - now.span_to(&then, false) - } - ); - b.rule_2("d'ici ", - b.reg(r#"d'ici"#)?, - datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), - |_, date| { - // FIXME: This adds one second to the value of now+then - let today = helpers::cycle_nth(Grain::Day, 0)?; - let then = date.value().clone().mark_before_start(); - today.span_to(&then, false) - } - ); - b.rule_3(" apres ", - duration_check!(), - b.reg(r#"apr[eè]s"#)?, - datetime_check!(), - |duration, _, datetime| duration.value().after(datetime.value()) - ); - b.rule_3(" avant ", - duration_check!(), - b.reg(r#"avant"#)?, - datetime_check!(), - |duration, _, datetime| duration.value().before(datetime.value()) - ); - Ok(()) -} - -pub fn rules_temperature(b: &mut RuleSetBuilder) -> RustlingResult<()> { - b.rule_1("number as temp", - number_check!(), - |a| { - Ok(TemperatureValue { - value: a.value().value(), - unit: None, - latent: true, - }) - }); - b.rule_2(" degrees", - temperature_check!(), - b.reg(r#"(?:deg(?:r[éeè])?s?\.?)|°"#)?, - |a, _| { - Ok(TemperatureValue { - value: a.value().value, - unit: Some("degree"), - latent: false, - }) - }); - b.rule_2(" Celcius", - temperature_check!(), - b.reg(r#"centigrades?|c(?:el[cs]?(?:ius)?)?\.?"#)?, - |a, _| { - Ok(TemperatureValue { - value: a.value().value, - unit: Some("celsius"), - latent: false, - }) - }); - b.rule_2(" Fahrenheit", - temperature_check!(), - b.reg(r#"f(?:ah?reh?n(?:h?eit)?)?\.?"#)?, - |a, _| { - Ok(TemperatureValue { - value: a.value().value, - unit: Some("fahrenheit"), - latent: false, - }) - }); - b.rule_2(" Kelvin", - temperature_check!(), - b.reg(r#"k(?:elvin)?\.?"#)?, - |a, _| { - Ok(TemperatureValue { - value: a.value().value, - unit: Some("kelvin"), - latent: false, - }) - }); - b.rule_2(" en dessous de zero", - temperature_check!(|temp: &TemperatureValue| !temp.latent), - b.reg(r#"en dessous de (?:0|z[ée]ro)"#)?, - |a, _| { - Ok(TemperatureValue { - value: -1.0 * a.value().value, - latent: false, - ..*a.value() - }) - }); - Ok(()) -} - -pub fn rules_numbers(b: &mut RuleSetBuilder) -> RustlingResult<()> { - b.rule_2("intersect", - number_check!(|number: &NumberValue| number.grain().unwrap_or(0) > 1), - number_check!(), - |a, b| helpers::compose_numbers(&a.value(), &b.value())); - b.rule_1_terminal( - "number (0..16)", - b.reg(r#"(z[eé]ro|une?|deux|trois|quatre|cinq|six|sept|huit|neuf|dix|onze|douze|treize|quatorze|quinze|seize)"#)?, - |text_match| { - let value = match text_match.group(1).as_ref() { - "zéro" => 0, - "zero" => 0, - "un" => 1, - "une" => 1, - "deux" => 2, - "trois" => 3, - "quatre" => 4, - "cinq" => 5, - "six" => 6, - "sept" => 7, - "huit" => 8, - "neuf" => 9, - "dix" => 10, - "onze" => 11, - "douze" => 12, - "treize" => 13, - "quatorze" => 14, - "quinze" => 15, - "seize" => 16, - _ => return Err(RuleError::Invalid.into()), - }; - IntegerValue::new(value) - }); - b.rule_1_terminal("quelques", - b.reg(r#"quelques"#)?, - |_| IntegerValue::new_with_grain(3, 1) - ); - b.rule_1_terminal("number (20..60)", - b.reg(r#"(vingt|trente|quarante|cinquante|soixante)"#)?, - |text_match| { - let value = match text_match.group(1).as_ref() { - "vingt" => 20, - "trente" => 30, - "quarante" => 40, - "cinquante" => 50, - "soixante" => 60, - _ => return Err(RuleError::Invalid.into()), - }; - IntegerValue::new(value) - }); - b.rule_2("number (17..19)", - integer_check_by_range!(10, 10), - integer_check_by_range!(7, 9), - |_, b| IntegerValue::new(b.value().value + 10)); - b.rule_3("number (17..19)", - integer_check_by_range!(10, 10), - b.reg(r"-")?, - integer_check_by_range!(7, 9), - |_, _, b| IntegerValue::new(b.value().value + 10)); - b.rule_2_terminal("number 80", - b.reg(r#"quatre"#)?, - b.reg(r#"vingts?"#)?, - |_, _| IntegerValue::new(80)); - b.rule_3_terminal("number 80", - b.reg(r#"quatre"#)?, - b.reg(r"-")?, - b.reg(r#"vingts?"#)?, - |_, _, _| IntegerValue::new(80)); - b.rule_3("numbers 21 31 41 51", - integer_check_by_range!(20, 50, |integer: &IntegerValue| integer.value % 10 == 0), - b.reg(r#"-?et-?"#)?, - integer_check_by_range!(1, 1), - |a, _, b| IntegerValue::new(a.value().value + b.value().value)); - b.rule_2("numbers 22..29 32..39 .. 52..59", - integer_check_by_range!(20, 50, |integer: &IntegerValue| integer.value % 10 == 0), - integer_check_by_range!(2, 9), - |a, b| IntegerValue::new(a.value().value + b.value().value)); - b.rule_3("numbers 22..29 32..39 .. 52..59", - integer_check_by_range!(20, 50, |integer: &IntegerValue| integer.value % 10 == 0), - b.reg(r"-")?, - integer_check_by_range!(2, 9), - |a, _, b| IntegerValue::new(a.value().value + b.value().value)); - b.rule_3("numbers 61 71", - integer_check_by_range!(60, 60), - b.reg(r#"-?et-?"#)?, - integer_check_by_range!(1, - 11, - |integer: &IntegerValue| integer.value == 1 || integer.value == 11), - |a, _, b| IntegerValue::new(a.value().value + b.value().value)); - b.rule_2("numbers 81 91", - integer_check_by_range!(80, 80), - integer_check_by_range!(1, - 11, - |integer: &IntegerValue| integer.value == 1 || integer.value == 11), - |a, b| IntegerValue::new(a.value().value + b.value().value)); - b.rule_3("numbers 81 91", - integer_check_by_range!(80, 80), - b.reg(r#"-"#)?, - integer_check_by_range!(1, - 11, - |integer: &IntegerValue| integer.value == 1 || integer.value == 11), - |a, _, b| IntegerValue::new(a.value().value + b.value().value)); - b.rule_2("numbers 62..69 .. 92..99", - integer_check_by_range!(60, - 80, - |integer: &IntegerValue| integer.value == 60 || integer.value == 80), - integer_check_by_range!(2, 19), - |a, b| IntegerValue::new(a.value().value + b.value().value)); - b.rule_3("numbers 62..69 .. 92..99", - integer_check_by_range!(60, - 80, - |integer: &IntegerValue| integer.value == 60 || integer.value == 80), - b.reg(r"-")?, - integer_check_by_range!(2, 19), - |a, _, b| IntegerValue::new(a.value().value + b.value().value)); - b.rule_1_terminal("hundred", - b.reg(r#"cents?"#)?, - |_| IntegerValue::new_with_grain(100, 2) - ); - b.rule_1_terminal("thousand", - b.reg(r#"milles?"#)?, - |_| IntegerValue::new_with_grain(1000, 3) - ); - b.rule_1_terminal("million", - b.reg(r#"millions?"#)?, - |_| IntegerValue::new_with_grain(1000000, 6) - ); - b.rule_1_terminal("billion", - b.reg(r#"milliards?"#)?, - |_| IntegerValue::new_with_grain(1000000000, 9) - ); - b.rule_2("number hundreds", - integer_check_by_range!(1, 99), - b.reg(r#"cents?"#)?, - |a, _| { - Ok(IntegerValue { - value: a.value().value * 100, - grain: Some(2), - ..IntegerValue::default() - }) - }); - b.rule_2("number thousands", - integer_check_by_range!(1, 999), - b.reg(r#"milles?"#)?, - |a, _| { - Ok(IntegerValue { - value: a.value().value * 1000, - grain: Some(3), - ..IntegerValue::default() - }) - }); - b.rule_2("number millions", - integer_check_by_range!(1, 999), - b.reg(r#"millions?"#)?, - |a, _| { - Ok(IntegerValue { - value: a.value().value * 1000000, - grain: Some(6), - ..IntegerValue::default() - }) - }); - b.rule_2("number billions", - integer_check_by_range!(1, 999), - b.reg(r#"milliards?"#)?, - |a, _| { - Ok(IntegerValue { - value: a.value().value * 1000000000, - grain: Some(9), - ..IntegerValue::default() - }) - }); - b.rule_1_terminal("integer (numeric)", - b.reg(r#"(\d{1,18})"#)?, - |text_match| { - let value: i64 = text_match.group(1).parse()?; - IntegerValue::new(value) - }); - b.rule_1_terminal("integer with thousands separator .", - b.reg(r#"(\d{1,3}(\.\d\d\d){1,5})"#)?, - |text_match| { - let reformatted_string = text_match.group(1).replace(".", ""); - let value: i64 = reformatted_string.parse()?; - IntegerValue::new(value) - }); - b.rule_1_terminal("decimal number", - b.reg(r#"(\d*,\d+)"#)?, - |text_match| { - let reformatted_string = text_match.group(1).replace(",", "."); - let value: f32 = reformatted_string.parse()?; - FloatValue::new(value) - }); - b.rule_3("number dot number", - number_check!(|number: &NumberValue| !number.prefixed()), - b.reg(r#"virgule|point"#)?, - number_check!(|number: &NumberValue| !number.suffixed()), - |a, _, b| { - let power = b.value().value().to_string().chars().count(); - let coeff = 10.0_f32.powf(-1.0 * power as f32); - Ok(FloatValue { - value: b.value().value() * coeff + a.value().value(), - ..FloatValue::default() - }) - }); - b.rule_4("number dot zero ... number", - number_check!(|number: &NumberValue| !number.prefixed()), - b.reg(r#"virgule|point"#)?, - b.reg(r#"(?:(?:z[eé]ro )*(?:z[eé]ro))"#)?, - number_check!(|number: &NumberValue| !number.suffixed()), - |a, _, zeros, b| { - let power = zeros.group(0).split_whitespace().count() + b.value().value().to_string().chars().count(); - let coeff = 10.0_f32.powf(-1.0 * power as f32); - Ok(FloatValue { - value: b.value().value() * coeff + a.value().value(), - ..FloatValue::default() - }) - }); - b.rule_1_terminal("decimal with thousands separator", - b.reg(r#"(\d+(\.\d\d\d)+,\d+)"#)?, - |text_match| { - let reformatted_string = text_match.group(1).replace(".", "").replace(",", "."); - let value: f32 = reformatted_string.parse()?; - FloatValue::new(value) - }); - b.rule_2("numbers prefix with -, negative or minus", - b.reg(r#"-|moins"#)?, - number_check!(|number: &NumberValue| !number.prefixed()), - |_, a| -> RuleResult { - Ok(match a.value().clone() { - // checked - NumberValue::Integer(integer) => { - IntegerValue { - value: integer.value * -1, - prefixed: true, - ..integer - } - .into() - } - NumberValue::Float(float) => { - FloatValue { - value: float.value * -1.0, - prefixed: true, - ..float - } - .into() - } - }) - }); - b.rule_2("numbers prefix with +, positive", - b.reg(r#"\+"#)?, - number_check!(|number: &NumberValue| !number.prefixed()), - |_, a| -> RuleResult { - Ok(match a.value().clone() { - // checked - NumberValue::Integer(integer) => { - IntegerValue { - prefixed: true, - ..integer - } - .into() - } - NumberValue::Float(float) => { - FloatValue { - prefixed: true, - ..float - } - .into() - } - }) - } - ); - b.rule_2("numbers suffixes (K, M, G)", - number_check!(|number: &NumberValue| !number.suffixed()), - b.reg_neg_lh(r#"([kmg])"#, r#"^[\W\$€]"#)?, - |a, text_match| -> RuleResult { - let multiplier = match text_match.group(0).as_ref() { - "k" => 1000, - "m" => 1000000, - "g" => 1000000000, - _ => return Err(RuleError::Invalid.into()), - }; - Ok(match a.value().clone() { // checked - NumberValue::Integer(integer) => { - IntegerValue { - value: integer.value * multiplier, - suffixed: true, - ..integer - } - .into() - } - NumberValue::Float(float) => { - let product = float.value * (multiplier as f32); - if product.floor() == product { - IntegerValue { - value: product as i64, - suffixed: true, - ..IntegerValue::default() - } - .into() - } else { - FloatValue { - value: product, - suffixed: true, - ..float - } - .into() - } - } - }) - }); - b.rule_1_terminal("(douzaine ... soixantaine)", - b.reg(r#"(demi[ -]douz|diz|douz|quinz|vingt|trent|quarant|cinquant|soixant|cent)aines?"#)?, - |text_match| { - let value = match text_match.group(1).as_ref() { - "demi douz" => 6, - "demi-douz" => 6, - "diz" => 10, - "douz" => 12, - "quinz" => 15, - "vingt" => 20, - "trent" => 30, - "quarant" => 40, - "cinquant" => 50, - "soixant" => 60, - "cent" => 100, - _ => return Err(RuleError::Invalid.into()), - }; - Ok(IntegerValue { - value, - group: true, - .. IntegerValue::default() - }) - } - ); - b.rule_2("number dozen", - integer_check_by_range!(1, 9), - integer_check!(|integer: &IntegerValue| integer.group), - |a, b| { - Ok(IntegerValue { - value: a.value().value * b.value().value, - grain: b.value().grain, - group: true, - ..IntegerValue::default() - }) - }); - b.rule_1_terminal("ordinal 0", - b.reg(r#"z[eé]rot?i[eè]me"#)?, - |_| { - Ok(OrdinalValue::new(0)) - } - ); - b.rule_1_terminal("ordinal 1", - b.reg(r#"premi[eè]re?"#)?, - |_| { - Ok(OrdinalValue::new(1)) - } - ); - b.rule_1_terminal("ordinal 2", - b.reg(r#"seconde?|deuxi[eè]me"#)?, - |_| { - Ok(OrdinalValue::new(2)) - } - ); - b.rule_1_terminal( - "ordinals (premier..seizieme)", - b.reg(r#"(trois|quatr|cinqu|six|sept|huit|neuv|dix|onz|douz|treiz|quatorz|quinz|seiz)i[eè]me"#)?, - |text_match| { - let value = match text_match.group(1).as_ref() { - "trois" => 3, - "quatr" => 4, - "cinqu" => 5, - "six" => 6, - "sept" => 7, - "huit" => 8, - "neuv" => 9, - "dix" => 10, - "onz" => 11, - "douz" => 12, - "treiz" => 13, - "quatorz" => 14, - "quinz" => 15, - "seiz" => 16, - _ => return Err(RuleError::Invalid.into()), - }; - Ok(OrdinalValue::new(value)) - }); - b.rule_2("17ieme, 18ieme, 19ieme", - b.reg(r#"dix-?"#)?, - ordinal_check_by_range!(7, 9), - |_, ordinal| { - Ok(OrdinalValue::new(10 + ordinal.value().value)) - } - ); - b.rule_1_terminal("20ieme, 30ieme, 40ieme, 50ieme, 60ieme", - b.reg(r#"(vingt|trent|quarant|cinquant|soixant)i[èe]me"#)?, - |text_match| { - let value = match text_match.group(1).as_ref() { - "vingt" => 20, - "trent" => 30, - "quarant" => 40, - "cinquant" => 50, - "soixant" => 60, - _ => return Err(RuleError::Invalid.into()), - }; - Ok(OrdinalValue::new(value)) - } - ); - b.rule_1_terminal("80ieme", - b.reg(r#"quatre[- ]vingts?i[èe]me"#)?, - |_| { - Ok(OrdinalValue::new(80)) - } - ); - b.rule_2("22ieme...29ieme, 32ieme...39ieme, 42ieme...49ieme, 52ieme...59ieme", - integer_check_by_range!(20, 50, |integer: &IntegerValue| integer.value % 10 == 0), - ordinal_check_by_range!(2, 9), - |integer, ordinal| { - Ok(OrdinalValue::new(integer.value().value + ordinal.value().value)) - } - ); - b.rule_3("22ieme...29ieme, 32ieme...39ieme, 42ieme...49ieme, 52ieme...59ieme", - integer_check_by_range!(20, 50, |integer: &IntegerValue| integer.value % 10 == 0), - b.reg(r"-")?, - ordinal_check_by_range!(2, 9), - |integer, _, ordinal| { - Ok(OrdinalValue::new(integer.value().value + ordinal.value().value)) - } - ); - b.rule_2("62ieme...70ieme, 72ieme...79ieme, 90ieme, 92ieme...99ieme", - integer_check_by_range!(60, 80, |integer: &IntegerValue| integer.value == 60 || integer.value == 80), - ordinal_check_by_range!(2, 19), - |integer, ordinal| { - Ok(OrdinalValue::new(integer.value().value + ordinal.value().value)) - } - ); - b.rule_3("62ieme...70ieme, 72ieme...79ieme, 90ieme, 92ieme...99ieme", - integer_check_by_range!(60, 80, |integer: &IntegerValue| integer.value == 60 || integer.value == 80), - b.reg(r"-")?, - ordinal_check_by_range!(2, 19), - |integer, _, ordinal| { - Ok(OrdinalValue::new(integer.value().value + ordinal.value().value)) - } - ); - b.rule_2("21, 31, 41, 51, 61", - integer_check_by_range!(20, 60, |integer: &IntegerValue| integer.value % 10 == 0), - b.reg(r#"(?:et |-)uni[èe]me"#)?, - |integer, _| { - Ok(OrdinalValue::new(integer.value().value + 1)) - } - ); - b.rule_2("81", - integer_check_by_range!(80, 80), - b.reg(r#"(?:et )?uni[èe]me"#)?, - |integer, _| { - Ok(OrdinalValue::new(integer.value().value + 1)) - } - ); - b.rule_2("71, 91", - integer_check_by_range!(60, 60), - b.reg(r#"et onzi[eè]me"#)?, - |integer, _| { - Ok(OrdinalValue::new(integer.value().value + 11)) - } - ); - b.rule_2(" et demi", - integer_check_by_range!(0, 99), - b.reg(r#"et demie?"#)?, - |integer, _| { - FloatValue::new(integer.value().value as f32 + 0.5) - } - ); - b.rule_1_terminal("70, 80, 90 (Belgium and Switzerland)", - b.reg(r#"(sept|huit|non)ante"#)?, - |text_match| { - let value = match text_match.group(1).as_ref() { - "sept" => 70, - "huit" => 80, - "non" => 90, - _ => return Err(RuleError::Invalid.into()), - }; - IntegerValue::new(value) - } - ); - b.rule_1_terminal("71, 81, 91 (Belgium and Switzerland)", - b.reg(r#"(sept|huit|non)ante et une?"#)?, - |text_match| { - let value = match text_match.group(1).as_ref() { - "sept" => 71, - "huit" => 81, - "non" => 91, - _ => return Err(RuleError::Invalid.into()), - }; - IntegerValue::new(value) - } - ); - - b.rule_2("72..79, 82..89, 92..99, (Belgium and Switzerland)", - b.reg(r#"(sept|huit|non)ante"#)?, - integer_check_by_range!(2, 9), - |text_match, integer| { - let value = match text_match.group(1).as_ref() { - "sept" => 70, - "huit" => 80, - "non" => 90, - _ => return Err(RuleError::Invalid.into()), - }; - IntegerValue::new(value + integer.value().value) - } - ); - b.rule_1_terminal("ordinal (100, 1_000, 1_000_000)", - b.reg(r#"(cent|mill|million|milliard)i[èe]me"#)?, - |text_match| { - let (value, grain) = match text_match.group(1).as_ref() { - "cent" => (100, 2), - "mill" => (1_000, 3), - "million" => (1_000_000, 6), - "milliard" => (1_000_000_000, 9), - _ => return Err(RuleError::Invalid.into()), - }; - Ok(OrdinalValue::new_with_grain(value, grain)) - } - ); - - b.rule_2("ordinal (200..900, 2_000..9_000, 2_000_000..9_000_000_000)", - integer_check_by_range!(2, 999), - b.reg(r#"(cent|mill|million|milliard)i[èe]me"#)?, - |integer, text_match| { - let (value, grain) = match text_match.group(1).as_ref() { - "cent" => (100, 2), - "mill" => (1_000, 3), - "million" => (1_000_000, 6), - "milliard" => (1_000_000_000, 9), - _ => return Err(RuleError::Invalid.into()), - }; - Ok(OrdinalValue::new_with_grain(integer.value().value * value, grain)) - } - ); - - b.rule_2("ordinal (1_1_000..9_999_999_000)", - integer_check_by_range!(1000, 99_999_999_000), - ordinal_check!(|ordinal: &OrdinalValue| { - let grain = ordinal.grain.unwrap_or(0); - grain == 2 || grain % 3 == 0 - }), - |integer, ordinal| { - let grain = ordinal.value().grain.unwrap_or(0); - let next_grain = (grain / 3) * 3 + 3; - if integer.value().value % 10i64.pow(next_grain as u32) != 0 { return Err(RuleError::Invalid.into()); } - Ok(OrdinalValue::new(integer.value().value + ordinal.value().value)) - } - ); - - b.rule_2("ordinal (102...9_999_999)", - integer_check!(|integer: &IntegerValue| integer.value >= 100 || integer.value % 100 == 0), - ordinal_check_by_range!(2, 99), - |integer, ordinal| { - Ok(OrdinalValue::new(integer.value().value + ordinal.value().value)) - } - ); - b.rule_2("ordinal (101, 201, 301, ...)", - integer_check!(|integer: &IntegerValue| integer.value >= 100 || integer.value % 100 == 0), - b.reg(r#"(?:et |-)?uni[èe]me"#)?, - |integer, _| { - Ok(OrdinalValue::new(integer.value().value + 1)) - } - ); - b.rule_1_terminal("ordinal (digits)", - b.reg(r#"0*(\d+) ?(ere?|ère|ème|eme|ieme|ième)"#)?, - |text_match| { - let value: i64 = text_match.group(1).parse()?; - Ok(OrdinalValue::new(value)) - }); - b.rule_2("le ", - b.reg(r#"l[ea]"#)?, - ordinal_check!(), - |_, a| Ok((*a.value()).prefixed()) - ); - Ok(()) -} diff --git a/grammar/fr/src/rules_amount.rs b/grammar/fr/src/rules_amount.rs new file mode 100644 index 00000000..7c959d2f --- /dev/null +++ b/grammar/fr/src/rules_amount.rs @@ -0,0 +1,243 @@ +use rustling::*; +use rustling_ontology_values::dimension::*; +use rustling_ontology_values::dimension::Precision::*; +use rustling_ontology_values::helpers; + +pub fn rules_percentage(b: &mut RuleSetBuilder) -> RustlingResult<()> { + b.rule_2(" per cent", + number_check!(), + b.reg(r"(?:%|p\.c\.|p. cents?|pour[ -]?cents?)")?, + |number, _| Ok(PercentageValue(number.value().value())) + ); + Ok(()) +} + +pub fn rules_finance(b: &mut RuleSetBuilder) -> RustlingResult<()> { + b.rule_2("intersect (X cents)", + amount_of_money_check!(), + amount_of_money_check!(|money: &AmountOfMoneyValue| money.unit == Some("cent")), + |a, b| helpers::compose_money(a.value(), b.value())); + b.rule_3("intersect (and X cents)", + amount_of_money_check!(), + b.reg(r#"et"#)?, + amount_of_money_check!(|money: &AmountOfMoneyValue| money.unit == Some("cent")), + |a, _, b| helpers::compose_money(&a.value(), &b.value())); + b.rule_2("intersect", + amount_of_money_check!(|money: &AmountOfMoneyValue| money.unit != Some("cent")), + number_check!(), + |a, b| helpers::compose_money_number(&a.value(), &b.value())); + b.rule_1_terminal("$", + b.reg(r#"\$|dollars?"#)?, + |_| Ok(MoneyUnitValue { unit: Some("$") }) + ); + b.rule_1_terminal("EUR", + b.reg(r#"€|(?:[e€]uro?s?)"#)?, + |_| Ok(MoneyUnitValue { unit: Some("EUR") }) + ); + b.rule_1_terminal("£", + b.reg(r#"£|livres?"#)?, + |_| Ok(MoneyUnitValue { unit: Some("£") }) + ); + b.rule_1_terminal("USD", + b.reg(r#"us[d\$]|dollars? am[eé]ricains?"#)?, + |_| Ok(MoneyUnitValue { unit: Some("USD") }) + ); + b.rule_1_terminal("AUD", + b.reg(r#"au[d\$]|dollars? australiens?"#)?, + |_| Ok(MoneyUnitValue { unit: Some("AUD") }) + ); + b.rule_1_terminal("CAD", + b.reg(r#"cad|dollars? canadiens?"#)?, + |_| Ok(MoneyUnitValue { unit: Some("CAD") }) + ); + b.rule_1_terminal("HKD", + b.reg(r#"hkd|dollars? de hong[- ]kong"#)?, + |_| Ok(MoneyUnitValue { unit: Some("HKD") }) + ); + b.rule_1_terminal("KR", + b.reg(r#"kr|couronnes?"#)?, + |_| Ok(MoneyUnitValue { unit: Some("KR") }) + ); + b.rule_1_terminal("DKK", + b.reg(r#"dkk|couronnes? danoises?"#)?, + |_| Ok(MoneyUnitValue { unit: Some("DKK") }) + ); + b.rule_1_terminal("NOK", + b.reg(r#"nok|couronnes? norv[ée]giennes?"#)?, + |_| Ok(MoneyUnitValue { unit: Some("NOK") }) + ); + b.rule_1_terminal("SEK", + b.reg(r#"sek|couronnes? su[ée]doises?"#)?, + |_| Ok(MoneyUnitValue { unit: Some("SEK") }) + ); + b.rule_1_terminal("CHF", + b.reg(r#"chf|francs? suisses?"#)?, + |_| Ok(MoneyUnitValue { unit: Some("CHF") }) + ); + b.rule_1_terminal("RUB", + b.reg(r#"rub|roubles?"#)?, + |_| Ok(MoneyUnitValue { unit: Some("RUB") }) + ); + b.rule_1_terminal("INR", + b.reg(r#"inr|roupies?"#)?, + |_| Ok(MoneyUnitValue { unit: Some("INR") }) + ); + b.rule_1_terminal("JPY", + b.reg(r#"jpy|yens?"#)?, + |_| Ok(MoneyUnitValue { unit: Some("JPY") }) + ); + b.rule_1_terminal("RMB|CNH|CNY", + b.reg(r#"cny|cnh|rmb|yuans?|renmimbis?"#)?, + |_| Ok(MoneyUnitValue { unit: Some("CNY") }) + ); + b.rule_1_terminal("¥", + b.reg(r#"¥"#)?, + |_| Ok(MoneyUnitValue { unit: Some("¥") }) + ); + b.rule_1_terminal("KRW", + b.reg(r#"₩|krw|wons? (?:sud[- ])?cor[ée]ns?|wons?"#)?, + |_| Ok(MoneyUnitValue { unit: Some("KRW") }) + ); + b.rule_1_terminal("Bitcoin", + b.reg(r#"฿|bitcoins?"#)?, + |_| Ok(MoneyUnitValue { unit: Some("฿") }) + ); + b.rule_1_terminal("GBP", + b.reg(r#"gbp|livres? sterlings?"#)?, + |_| Ok(MoneyUnitValue { unit: Some("GBP") }) + ); + b.rule_1_terminal("cent", + b.reg(r#"centimes?|cents?|penn(?:y|ies)|fens?"#)?, + |_| Ok(MoneyUnitValue { unit: Some("cent") }) + ); + b.rule_1_terminal("unnamed currency", + b.reg(r#"(?:balle)s?"#)?, + |_| Ok(MoneyUnitValue { unit: None }) + ); + b.rule_2(" ", + number_check!(), + money_unit!(), + |a, b| { + Ok(AmountOfMoneyValue { + value: a.value().value(), + unit: b.value().unit, + ..AmountOfMoneyValue::default() + }) + }); + b.rule_3(" de ", // "un million de dollars" + integer_check!(|integer: &IntegerValue| !integer.group), + b.reg(r#"d[e']"#)?, + money_unit!(), + |a, _, b| { + Ok(AmountOfMoneyValue { + value: a.value().value as f32, + precision: Exact, + unit: b.value().unit, + ..AmountOfMoneyValue::default() + }) + }); + b.rule_3(" de ", // "une douzaine de dollars" + integer_check!(|integer: &IntegerValue| integer.group), + b.reg(r#"d[e']"#)?, + money_unit!(), + |a, _, b| { + Ok(AmountOfMoneyValue { + value: a.value().value as f32, + precision: Approximate, + unit: b.value().unit, + ..AmountOfMoneyValue::default() + }) + }); + b.rule_2("about ", + b.reg(r#"(?:autour|pas loin|pr[eè]s|aux alentours) d[e']|environ|presque|(?:approximative|quasi)ment"#)?, + amount_of_money_check!(), + |_, a| { + Ok(AmountOfMoneyValue { + precision: Approximate, + ..a.value().clone() + }) + }); + b.rule_2("exactly ", + b.reg(r#"(?:tr[eè]s )?exactement|pr[eé]cis[eé]ment|pile(?: poil)?"#)?, + amount_of_money_check!(), + |_, a| { + Ok(AmountOfMoneyValue { + precision: Exact, + ..a.value().clone() + }) + }); + b.rule_2("exactly ", + amount_of_money_check!(), + b.reg(r#"pile(?: poil)?|tout rond"#)?, + |a, _| { + Ok(AmountOfMoneyValue { + precision: Exact, + ..a.value().clone() + }) + } + ); + Ok(()) +} + +pub fn rules_temperature(b: &mut RuleSetBuilder) -> RustlingResult<()> { + b.rule_1("number as temp", + number_check!(), + |a| { + Ok(TemperatureValue { + value: a.value().value(), + unit: None, + latent: true, + }) + }); + b.rule_2(" degrees", + temperature_check!(), + b.reg(r#"(?:deg(?:r[éeè])?s?\.?)|°"#)?, + |a, _| { + Ok(TemperatureValue { + value: a.value().value, + unit: Some("degree"), + latent: false, + }) + }); + b.rule_2(" Celcius", + temperature_check!(), + b.reg(r#"centigrades?|c(?:el[cs]?(?:ius)?)?\.?"#)?, + |a, _| { + Ok(TemperatureValue { + value: a.value().value, + unit: Some("celsius"), + latent: false, + }) + }); + b.rule_2(" Fahrenheit", + temperature_check!(), + b.reg(r#"f(?:ah?reh?n(?:h?eit)?)?\.?"#)?, + |a, _| { + Ok(TemperatureValue { + value: a.value().value, + unit: Some("fahrenheit"), + latent: false, + }) + }); + b.rule_2(" Kelvin", + temperature_check!(), + b.reg(r#"k(?:elvin)?\.?"#)?, + |a, _| { + Ok(TemperatureValue { + value: a.value().value, + unit: Some("kelvin"), + latent: false, + }) + }); + b.rule_2(" en dessous de zero", + temperature_check!(|temp: &TemperatureValue| !temp.latent), + b.reg(r#"en dessous de (?:0|z[ée]ro)"#)?, + |a, _| { + Ok(TemperatureValue { + value: -1.0 * a.value().value, + latent: false, + ..*a.value() + }) + }); + Ok(()) +} \ No newline at end of file diff --git a/grammar/fr/src/rules_celebrations.rs b/grammar/fr/src/rules_celebrations.rs new file mode 100644 index 00000000..f2891a74 --- /dev/null +++ b/grammar/fr/src/rules_celebrations.rs @@ -0,0 +1,114 @@ +use rustling::*; +use rustling_ontology_values::dimension::*; +use rustling_ontology_values::helpers; +use rustling_ontology_moment::{Weekday, Grain}; + +pub fn rules_celebration(b: &mut RuleSetBuilder) -> RustlingResult<()> { + b.rule_1_terminal("noel", + b.reg(r#"(?:(?:le )?jour de )?no[eë]l"#)?, + |_| Ok(helpers::month_day(12, 25)?.form(Form::Celebration)) + ); + b.rule_1_terminal("soir de noël", + b.reg(r#"(?:l[ea] )?(?:soir(?:ée)?|veille|r[eé]veillon) de no[eë]l"#)?, + |_| { + let start = helpers::month_day(12, 24)?.intersect(&helpers::hour(18, false)?)?; + let end = helpers::month_day(12, 25)?.intersect(&helpers::hour(0, false)?)?; + Ok(start.span_to(&end, false)? + .form(Form::Celebration)) + } + ); + b.rule_1_terminal("saint sylvestre", + b.reg(r#"(?:l[ea] )?(?:saint[- ]sylvestre|r[eé]veillon)"#)?, + |_| Ok(helpers::month_day(12, 31)?.form(Form::Celebration)) + ); + b.rule_1_terminal("jour de l'an", + b.reg(r#"(?:le )?(?:jour de l'|nouvel )an"#)?, + |_| Ok(helpers::month_day(1, 1)?.form(Form::Celebration)) + ); + b.rule_1_terminal("toussaint", + b.reg(r#"(?:(?:la |la journée de la |jour de la )?toussaint|jour des morts)"#)?, + |_| Ok(helpers::month_day(11, 1)?.form(Form::Celebration)) + ); + b.rule_1_terminal("Armistice", + b.reg(r#"(?:pour )?l'armistice"#)?, + |_| Ok(helpers::month_day(11, 11)?.form(Form::Celebration)) + ); + b.rule_1_terminal("Saint Etienne (Alsace)", + b.reg(r#"(?:(?:le jour|la f[eê]te) de )?la (?:saint|st) [eé]tienne"#)?, + |_| Ok(helpers::month_day(12, 26)?.form(Form::Celebration)) + ); + b.rule_1_terminal("jeudi saint", + b.reg(r#"(?:le )?jeudi saint"#)?, + |_| Ok(helpers::cycle_nth_after(Grain::Day, -3, &helpers::easter()?)? + .form(Form::Celebration)) + ); + b.rule_1_terminal("vendredi saint", + b.reg(r#"(?:le )?vendredi saint"#)?, + |_| Ok(helpers::cycle_nth_after(Grain::Day, -2, &helpers::easter()?)? + .form(Form::Celebration)) + ); + b.rule_1_terminal("samedi saint", + b.reg(r#"(?:le )?samedi saint"#)?, + |_| Ok(helpers::cycle_nth_after(Grain::Day, -1, &helpers::easter()?)? + .form(Form::Celebration)) + ); + b.rule_1_terminal("pâques", + b.reg(r#"(?:la f[eê]te de |le jour de |le dimanche de )?p[âa]ques"#)?, + |_| Ok(helpers::easter()?.form(Form::Celebration)) + ); + b.rule_1_terminal("le lundi de pâques", + b.reg(r#"le lundi de p[âa]ques"#)?, + |_| Ok(helpers::cycle_nth_after(Grain::Day, 1, &helpers::easter()?)? + .form(Form::Celebration)) + ); + b.rule_1_terminal("ascension", + b.reg(r#"(?:la f[eê]te de l'|le jeudi de l'|l'|le jour de l')ascension"#)?, + |_| Ok(helpers::cycle_nth_after(Grain::Day, 39, &helpers::easter()?)? + .form(Form::Celebration)) + ); + b.rule_1_terminal("pentecôte", + b.reg(r#"(?:la f[eê]te de |(?:le )?lundi de )?(?:la )?pentec[oô]te"#)?, + |_| Ok(helpers::cycle_nth_after(Grain::Day, 49, &helpers::easter()?)? + .form(Form::Celebration)) + ); + b.rule_1_terminal("1er mai", + b.reg(r#"(?:la )?f(e|ê)te du travail"#)?, + |_| Ok(helpers::month_day(5, 1)?.form(Form::Celebration)) + ); + b.rule_1_terminal("fêtes des pères", + b.reg(r#"(?:la )?f[eê]te des p[eè]res"#)?, + |_| { + let sundays_of_june = helpers::month(6)?.intersect(&helpers::day_of_week(Weekday::Sun)?)?; + let second_week_of_june = helpers::cycle_nth_after(Grain::Week, 2, &helpers::month_day(6, 1)?)?; + Ok(sundays_of_june.intersect(&second_week_of_june)? // third sunday of June + .form(Form::Celebration)) + } + ); + b.rule_1_terminal("fêtes des mères", + b.reg(r#"(?:la )?f[eê]te des m[eè]res"#)?, + |_| { + // It is the last last sunday of may + // If it is the same day as the Pentecost, it is the first sunday of june + // This case is not supported for now + Ok(helpers::day_of_week(Weekday::Sun)?.last_of(&helpers::month(5)?)? + .form(Form::Celebration)) + } + ); + b.rule_1_terminal("fête nationale", + b.reg(r#"(?:la )?f[eê]te (?:nationale|du (?:14|quatorze) juillet)"#)?, + |_| Ok(helpers::month_day(7, 14)? + .form(Form::Celebration)) + ); + b.rule_1_terminal("assomption", + b.reg(r#"(?:la f[eê]te de |le jour de )?l'assomption"#)?, + |_| Ok(helpers::month_day(8, 15)? + .form(Form::Celebration)) + ); + b.rule_2("à ", + b.reg(r#"au|[aà](?:l['a])?"#)?, + datetime_check!(form!(Form::Celebration)), + |_, a| Ok(a.value().clone()) + ); + + Ok(()) +} \ No newline at end of file diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs new file mode 100644 index 00000000..93fb4ac5 --- /dev/null +++ b/grammar/fr/src/rules_datetime.rs @@ -0,0 +1,1443 @@ +use rustling::*; +use rustling_ontology_values::dimension::*; +use rustling_ontology_values::helpers; +use rustling_ontology_moment::{Weekday, Grain}; + + +pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { + b.rule_2("intersect", + datetime_check!(|datetime: &DatetimeValue| !datetime.latent), + datetime_check!(|datetime: &DatetimeValue| !datetime.latent), + |a, b| a.value().intersect(b.value()) + ); + b.rule_3("intersect by 'de' or ','", + datetime_check!(|datetime: &DatetimeValue| !datetime.latent), + b.reg(r#"de|,"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent), + |a, _, b| a.value().intersect(b.value()) + ); + b.rule_3("intersect by 'mais/par exemple/plutôt'", + datetime_check!(|datetime: &DatetimeValue| !datetime.latent), + b.reg(r#"mais|par exemple|plutôt|plutot"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent), + |a, _, b| a.value().intersect(b.value()) + ); + b.rule_2("en ", + b.reg(r#"en|au mois d[e']|du mois d[e']"#)?, + datetime_check!(form!(Form::Month(_))), + |_, a| Ok(a.value().clone()) + ); + b.rule_2("pour ", + b.reg(r#"pour"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), + |_, a| Ok(a.value().clone()) + ); + b.rule_1_terminal("named-day", + b.reg(r#"lun\.?(?:di)?"#)?, + |_| helpers::day_of_week(Weekday::Mon) + ); + b.rule_1_terminal("named-day", + b.reg(r#"mar\.?(?:di)?"#)?, + |_| helpers::day_of_week(Weekday::Tue) + ); + b.rule_1_terminal("named-day", + b.reg(r#"mer\.?(?:credi)?"#)?, + |_| helpers::day_of_week(Weekday::Wed) + ); + b.rule_1_terminal("named-day", + b.reg(r#"jeu\.?(?:di)?"#)?, + |_| helpers::day_of_week(Weekday::Thu) + ); + b.rule_1_terminal("named-day", + b.reg(r#"ven\.?(?:dredi)?"#)?, + |_| helpers::day_of_week(Weekday::Fri) + ); + b.rule_1_terminal("named-day", + b.reg(r#"sam\.?(?:edi)?"#)?, + |_| helpers::day_of_week(Weekday::Sat) + ); + b.rule_1_terminal("named-day", + b.reg(r#"dim\.?(?:anche)?"#)?, + |_| helpers::day_of_week(Weekday::Sun) + ); + b.rule_1_terminal("named-month", + b.reg(r#"janvier|janv\.?"#)?, + |_| helpers::month(1) + ); + b.rule_1_terminal("named-month", + b.reg(r#"fevrier|février|fev|fév\.?"#)?, + |_| helpers::month(2) + ); + b.rule_1_terminal("named-month", + b.reg(r#"mars|mar\.?"#)?, + |_| helpers::month(3) + ); + b.rule_1_terminal("named-month", + b.reg(r#"avril|avr\.?"#)?, + |_| helpers::month(4) + ); + b.rule_1_terminal("named-month", + b.reg(r#"mai"#)?, + |_| helpers::month(5) + ); + b.rule_1_terminal("named-month", + b.reg(r#"juin|jun\.?"#)?, + |_| helpers::month(6) + ); + b.rule_1_terminal("named-month", + b.reg(r#"juillet|juil?\."#)?, + |_| helpers::month(7) + ); + b.rule_1_terminal("named-month", + b.reg(r#"aout|août|aou\.?"#)?, + |_| helpers::month(8) + ); + b.rule_1_terminal("named-month", + b.reg(r#"septembre|sept\.|sep\.?"#)?, + |_| helpers::month(9) + ); + b.rule_1_terminal("named-month", + b.reg(r#"octobre|oct\.?"#)?, + |_| helpers::month(10) + ); + b.rule_1_terminal("named-month", + b.reg(r#"novembre|nov\.?"#)?, + |_| helpers::month(11) + ); + b.rule_1_terminal("named-month", + b.reg(r#"décembre|decembre|déc\.?|dec\.?"#)?, + |_| helpers::month(12) + ); + b.rule_1_terminal("maintenant", + b.reg(r#"maintenant|tout de suite|en ce moment"#)?, + |_| helpers::cycle_nth(Grain::Second, 0) + ); + b.rule_1_terminal("aujourd'hui", + b.reg(r#"(?:aujourd'? ?hui)|(?:ce jour)|(?:dans la journ[ée]e?)"#)?, + |_| helpers::cycle_nth(Grain::Day, 0) + ); + // FIXME: "le lendemain" interpreted as demain, not as relative to another date + // but there is a rule "le lendemain du " - inconsistent + b.rule_1_terminal("demain", + b.reg(r#"(?:demain)|(?:le lendemain)"#)?, + |_| helpers::cycle_nth(Grain::Day, 1) + ); + b.rule_1_terminal("hier", + b.reg(r#"hier|la veille"#)?, + |_| helpers::cycle_nth(Grain::Day, -1) + ); + b.rule_1_terminal("fin du mois", + b.reg(r#"(?:(?:[aà] )?la )?fin (?:du|de) mois"#)?, + |_| { + let month = helpers::cycle_nth(Grain::Month, 1)?; + Ok(helpers::cycle_nth_after(Grain::Day, -10, &month)? + .span_to(&month, false)? + .latent() + .form(Form::PartOfMonth)) + } + ); + b.rule_1_terminal("après-demain", + b.reg(r#"apr(?:e|è)s[- ]?demain"#)?, + |_| helpers::cycle_nth(Grain::Day, 2) + ); + b.rule_1_terminal("avant-hier", + b.reg(r#"avant[- ]?hier"#)?, + |_| helpers::cycle_nth(Grain::Day, -2) + ); + b.rule_2("le lendemain du ", + b.reg(r#"(?:le|au)? ?lendemain du"#)?, + datetime_check!(), + |_, datetime| helpers::cycle_nth_after_not_immediate(Grain::Day, 1, datetime.value()) + ); + b.rule_2("la veille du ", + b.reg(r#"(la )?veille du"#)?, + datetime_check!(), + |_, datetime| helpers::cycle_nth_after_not_immediate(Grain::Day, -1, datetime.value()) + ); + b.rule_2("ce ", + b.reg(r#"ce"#)?, + datetime_check!(form!(Form::DayOfWeek{..})), + |_, datetime| datetime.value().the_nth_not_immediate(0) + ); + b.rule_2("ce ", + b.reg(r#"ce"#)?, + datetime_check!(), + |_, datetime| Ok(datetime.value().the_nth(0)? + .datetime_kind(datetime.value().datetime_kind.clone())) + ); + b.rule_2(" prochain", + datetime_check!(form!(Form::DayOfWeek{..})), + b.reg(r#"prochain"#)?, + |datetime, _| datetime.value().the_nth_not_immediate(0) + ); + b.rule_2(" prochain", + datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), + b.reg(r#"prochaine?"#)?, + |datetime, _| datetime.value().the_nth_not_immediate(0) + ); + b.rule_2("au prochain ", + b.reg(r#"(au|[aà] la) prochaine?"#)?, + datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), + |_, datetime| datetime.value().the_nth_not_immediate(0) + ); + b.rule_2(" prochain", + // The direction check is to avoid application of datetime_check(month) on rule result + // "avant " + datetime_check!(|datetime: &DatetimeValue| form!(Form::Month(_))(datetime) && !datetime.direction.is_some()), + b.reg(r#"prochain"#)?, + |datetime, _| datetime.value().the_nth_not_immediate(0) + ); + b.rule_2(" suivant|d'après", + datetime_check!(), + b.reg(r#"suivante?s?|d'apr[eéè]s"#)?, + |datetime, _| datetime.value().the_nth(1) + ); + b.rule_2(" dernier|passé", + datetime_check!(), + b.reg(r#"derni[eéè]re?|pass[ée]e?"#)?, + |datetime, _| datetime.value().the_nth(-1) + ); + b.rule_2(" en huit", + datetime_check!(form!(Form::DayOfWeek{..})), + b.reg(r#"en (?:huit|8)"#)?, + |datetime, _| datetime.value().the_nth(1) + ); + b.rule_2(" en quinze", + datetime_check!(form!(Form::DayOfWeek{..})), + b.reg(r#"en (quinze|15)"#)?, + |datetime, _| datetime.value().the_nth(2) + ); + b.rule_4("dernier de (latent)", + b.reg(r#"derni[eéè]re?"#)?, + datetime_check!(form!(Form::DayOfWeek{..})), + b.reg(r#"d['e]"#)?, + datetime_check!(), + |_, dow, _, datetime| dow.value().last_of(datetime.value()) + ); + b.rule_4("dernier de (latent)", + b.reg(r#"derni[eéè]re?"#)?, + cycle_check!(), + b.reg(r#"d['e]"#)?, + datetime_check!(), + |_, cycle, _, datetime| cycle.value().last_of(datetime.value()) + ); + b.rule_4(" de ", + ordinal_check!(), // the first + datetime_check!(), // Thursday + b.reg(r#"d[e']"#)?, // of + datetime_check!(), // march + |ordinal, a, _, b| { + b.value().intersect(a.value())?.the_nth(ordinal.value().value - 1) + } + ); + b.rule_3(" week-end de ", + ordinal_check!(), + b.reg(r#"week(?:\s|-)?end (?:d['eu]|en|du mois de)"#)?, + datetime_check!(form!(Form::Month(_))), + |ordinal, _, datetime| { + let weekend = helpers::weekend()?; + let nth_week_end = datetime.value().intersect(&weekend)?; + nth_week_end.the_nth(ordinal.value().value - 1) + } + ); + b.rule_2("dernier week-end de ", + b.reg(r#"(?:le )?dernier week(?:\s|-)?end (?:du mois d[e']|d['eu]|en)"#)?, + datetime_check!(form!(Form::Month(_))), + |_, datetime| { + let weekend = helpers::weekend()?; + weekend.last_of(datetime.value()) + } + ); + // FIXME: change latency ranges for years? E.g. latent until 1900? + b.rule_1("year", + integer_check_by_range!(1000, 2100), + |integer| helpers::year(integer.value().value as i32) + ); + b.rule_1("year (latent)", + integer_check_by_range!(-1000, 999), + |integer| Ok(helpers::year(integer.value().value as i32)?.latent()) + ); + b.rule_2("l'année ", + b.reg(r#"l[' ]an(?:n[eé]+)?"#)?, + integer_check!(), + |_, integer| helpers::year(integer.value().value as i32) + ); + b.rule_2("en ", + b.reg(r#"en"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::Year(_))(datetime)), + |_, year| Ok(year.value().clone()) + ); + b.rule_1("year (latent)", + integer_check_by_range!(2101, 3000), + |integer| Ok(helpers::year(integer.value().value as i32)?.latent()) + ); + b.rule_1_terminal("day of month (premier)", + b.reg(r#"premier|prem\.?|1er|1 er"#)?, + |_| helpers::day_of_month(1) + ); + b.rule_2("le (non ordinal)", + b.reg(r#"le"#)?, + integer_check_by_range!(1, 31), + |_, integer| helpers::day_of_month(integer.value().value as u32) + ); + b.rule_4("le à ", + b.reg(r#"le"#)?, + integer_check_by_range!(1, 31), + b.reg(r#"[aà]"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(_))(datetime)), + |_, integer, _, datetime| { + let day_of_month = helpers::day_of_month(integer.value().value as u32)?; + day_of_month.intersect(&datetime.value()) + } + ); + b.rule_2(" ", + integer_check_by_range!(1, 31), + datetime_check!(form!(Form::Month(_))), + |integer, month| Ok(month.value() + .intersect(&helpers::day_of_month(integer.value().value as u32)?)? + .form(Form::DayOfMonth)) + ); + b.rule_2(" ", + datetime_check!(form!(Form::DayOfWeek{..})), // Weird it is not used in the production of the rule + integer_check_by_range!(1, 31), + |_, integer| helpers::day_of_month(integer.value().value as u32) + ); + b.rule_4(" à )", + datetime_check!(form!(Form::DayOfWeek{..})), // Weird it is not used in the production of the rule + integer_check_by_range!(1, 31), + b.reg(r#"[aà]"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(_))(datetime)), + |_, integer, _, tod| helpers::day_of_month(integer.value().value as u32) + ?.intersect(tod.value()) + ); + b.rule_1(" (latent)", + integer_check_by_range!(1, 23), + |integer| Ok(helpers::hour(integer.value().value as u32, integer.value().value < 12)?.latent()) + ); + b.rule_1(" (latent)", + integer_check_by_range!(0, 0), + |_| Ok(helpers::hour(0, false)?.latent()) + ); + b.rule_1_terminal("midi", + b.reg(r#"midi(?: pile| exactement| pr[eé]cises)?"#)?, + |_| helpers::hour(12, false) + ); + b.rule_1_terminal("minuit", + b.reg(r#"minuit(?: pile| exactement| pr[eé]cises)?"#)?, + |_| helpers::hour(0, false) + ); + b.rule_2(" heures", + datetime_check!(form!(Form::TimeOfDay(TimeOfDayForm::Hour { .. }))), + b.reg(r#"h\.?(?:eure)?s?(?: pile| exactement| pr[eé]cises?)?"#)?, + |a, _| Ok(a.value().clone().not_latent()) + ); + b.rule_1_terminal("quart (relative minutes)", + b.reg(r#"(?:un )?quart"#)?, + |_| Ok(RelativeMinuteValue(15)) + ); + b.rule_1_terminal("demi (relative minutes)", + b.reg(r#"demie?"#)?, + |_| Ok(RelativeMinuteValue(30)) + ); + b.rule_1_terminal("trois quarts (relative minutes)", + b.reg(r#"(?:3|trois) quarts?"#)?, + |_| Ok(RelativeMinuteValue(45)) + ); + b.rule_1("number (as relative minutes)", + integer_check_by_range!(1, 59), + |a| Ok(RelativeMinuteValue(a.value().value as i32)) + ); + b.rule_2("number minutes (as relative minutes)", + integer_check_by_range!(1, 59), + b.reg(r#"min\.?(?:ute)?s?"#)?, + |a, _| Ok(RelativeMinuteValue(a.value().value as i32)) + ); + b.rule_2(" (as relative minutes)", + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(TimeOfDayForm::Hour { .. }))(datetime)), + relative_minute_check!(), + |datetime, minutes| helpers::hour_relative_minute( + datetime.value().form_time_of_day()?.full_hour(), + minutes.value().0, + datetime.value().form_time_of_day()?.is_12_clock() + ) + ); + b.rule_3(" (as relative minutes) exactly", + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(TimeOfDayForm::Hour { .. }))(datetime)), + relative_minute_check!(), + b.reg(r#"pile|exactement|pr[ée]cises?"#)?, + |datetime, minutes, _| helpers::hour_relative_minute( + datetime.value().form_time_of_day()?.full_hour(), + minutes.value().0, + datetime.value().form_time_of_day()?.is_12_clock() + ) + ); + b.rule_3(" moins (as relative minutes)", + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(TimeOfDayForm::Hour { .. }))(datetime)), + b.reg(r#"moins(?: le)?"#)?, + relative_minute_check!(), + |datetime, _, minutes| helpers::hour_relative_minute( + datetime.value().form_time_of_day()?.full_hour(), + -1 * minutes.value().0, + datetime.value().form_time_of_day()?.is_12_clock() + ) + ); + b.rule_4(" moins (as relative minutes) exactly ", + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(TimeOfDayForm::Hour { .. }))(datetime)), + b.reg(r#"moins(?: le)?"#)?, + relative_minute_check!(), + b.reg(r#"pile|exactement|pr[ée]cises?"#)?, + |datetime, _, minutes, _| helpers::hour_relative_minute( + datetime.value().form_time_of_day()?.full_hour(), + -1 * minutes.value().0, + datetime.value().form_time_of_day()?.is_12_clock() + ) + ); + b.rule_3(" et|passé de ", + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(TimeOfDayForm::Hour { .. }))(datetime)), + b.reg(r#"et|pass[ée]e?s? de"#)?, + relative_minute_check!(), + |datetime, _, minutes| helpers::hour_relative_minute( + datetime.value().form_time_of_day()?.full_hour(), + minutes.value().0, + datetime.value().form_time_of_day()?.is_12_clock() + ) + ); + b.rule_4(" et|passé de exactly", + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(TimeOfDayForm::Hour { .. }))(datetime)), + b.reg(r#"et|pass[ée]e?s? de"#)?, + relative_minute_check!(), + b.reg(r#"pile|exactement|pr[ée]cises?"#)?, + |datetime, _, minutes, _| helpers::hour_relative_minute( + datetime.value().form_time_of_day()?.full_hour(), + minutes.value().0, + datetime.value().form_time_of_day()?.is_12_clock() + ) + ); + b.rule_4(" de exactly", + datetime_check!(form!(Form::TimeOfDay(_))), + b.reg(r#"(?:d[eu] |dans )(?:l[ 'a])?"#)?, + datetime_check!(form!(Form::PartOfDay(_))), + b.reg(r#"pile|exactement|pr[eé]cises?"#)?, + |a, _, b, _| Ok(a.value().intersect(b.value())?.form(a.value().form.clone())) + ); + // Adding "pour" here makes time-of-day ambiguous w/ Duration + // Duration has less priority than Datetime types, therefore duration will be output only + // if the output kind filter is set for Duration + b.rule_2("à ", + b.reg(r#"[aà]|pour"#)?, + datetime_check!(form!(Form::TimeOfDay(_))), + |_, a| Ok(a.value().clone().not_latent()) + ); + b.rule_2("vers ", + b.reg(r#"(?:plut[ôo]t )?(?:vers|autour de|[aà] environ|aux alentours de)"#)?, + datetime_check!(form!(Form::TimeOfDay(_))), + |_, a| Ok(a.value().clone().not_latent().precision(Precision::Approximate)) + ); + // Written time/date in numeric formats + b.rule_1_terminal("hh(:|h)mm (time-of-day)", + b.reg(r#"((?:[01]?\d)|(?:2[0-3]))[:h]([0-5]\d)"#)?, + |text_match| { + let hour: u32 = text_match.group(1).parse()?; + let minute: u32 = text_match.group(2).parse()?; + helpers::hour_minute(hour, minute, hour < 12) + } + ); + b.rule_3_terminal("hh(:|h)mm - hh(:|h)mm (time-of-day interval)", + b.reg(r#"((?:[01]?\d)|(?:2[0-3]))[:h]([0-5]\d)"#)?, + b.reg(r#" ?\- ?"#)?, + b.reg(r#"((?:[01]?\d)|(?:2[0-3]))[:h]([0-5]\d)"#)?, + |a, _, b| { + let hour_start: u32 = a.group(1).parse()?; + let minute_start: u32 = a.group(2).parse()?; + let hour_end: u32 = b.group(1).parse()?; + let minute_end: u32 = b.group(2).parse()?; + let start = helpers::hour_minute(hour_start, minute_start, hour_start < 12)?; + let end = helpers::hour_minute(hour_end, minute_end, hour_end < 12)?; + start.smart_span_to(&end, false) + } + ); + b.rule_1_terminal("hh:mm:ss", + b.reg(r#"((?:[01]?\d)|(?:2[0-3]))[:.]([0-5]\d)[:.]([0-5]\d)"#)?, + |text_match| helpers::hour_minute_second( + text_match.group(1).parse()?, + text_match.group(2).parse()?, + text_match.group(3).parse()?, + false + ) + + ); + b.rule_1_terminal("hhmm (military time-of-day)", + b.reg(r#"((?:[01]\d)|(?:2[0-3]))([0-5]\d)"#)?, + |text_match| Ok(helpers::hour_minute( + text_match.group(1).parse()?, + text_match.group(2).parse()?, + false + )?.latent()) + ); + b.rule_1_terminal("yyyy-mm-dd - ISO", + b.reg(r#"(\d{4})[-/](0?[1-9]|1[0-2])[-/](3[01]|[12]\d|0?[1-9])"#)?, + |text_match| helpers::year_month_day( + text_match.group(1).parse()?, + text_match.group(2).parse()?, + text_match.group(3).parse()?) + ); + // Supporting these date formats also with whitespace as a separator for legacy + // But this seems too permissive? + b.rule_1_terminal("dd/mm/yy or dd/mm/yyyy", + b.reg(r#"(0?[1-9]|[12]\d|3[01])[-\./ ](0?[1-9]|1[0-2])[-\./ ](\d{2,4})"#)?, + |text_match| helpers::year_month_day( + text_match.group(3).parse()?, + text_match.group(2).parse()?, + text_match.group(1).parse()?, + ) + ); + b.rule_1_terminal("dd/mm", + b.reg(r#"(0?[1-9]|[12]\d|3[01])[\./ ](1[0-2]|0?[1-9])"#)?, + |text_match| helpers::month_day( + text_match.group(2).parse()?, + text_match.group(1).parse()?) + ); + // End of Written time/date in numeric formats + b.rule_1_terminal("matin", + b.reg(r#"mat(?:in[ée]?e?)?"#)?, + |_| Ok(helpers::hour(4, false)? + .span_to(&helpers::hour(12, false)?, false)? + .latent() + .form(Form::PartOfDay(PartOfDayForm::Morning))) + ); + b.rule_1_terminal("début de matinée", + b.reg(r#"(?:le matin (?:tr[eè]s )?t[ôo]t|(?:tr[eè]s )?t[ôo]t le matin|d[ée]but de matin[ée]e)"#)?, + |_| Ok(helpers::hour(4, false)? + .span_to(&helpers::hour(9, false)?, false)? + .latent() + .form(Form::PartOfDay(PartOfDayForm::Morning))) + ); + b.rule_1_terminal("petit dejeuner", + b.reg(r#"petit[- ]d[ée]jeuner"#)?, + |_| Ok(helpers::hour(5, false)? + .span_to(&helpers::hour(10, false)?, false)? + .latent() + .form(Form::Meal)) + ); + b.rule_1_terminal("milieu de matinée", + b.reg(r#"milieu de matin[ée]e"#)?, + |_| Ok(helpers::hour(9, false)? + .span_to(&helpers::hour(11, false)?, false)? + .latent() + .form(Form::PartOfDay(PartOfDayForm::Morning))) + ); + b.rule_1_terminal("brunch", + b.reg(r#"brunch"#)?, + |_| Ok(helpers::hour(10, false)? + .span_to(&helpers::hour(15, false)?, false)? + .latent() + .form(Form::Meal)) + ); + b.rule_1_terminal("fin de matinée", + b.reg(r#"fin de matin[ée]e"#)?, + |_| Ok(helpers::hour(10, false)? + .span_to(&helpers::hour(12, false)?, false)? + .latent() + .form(Form::PartOfDay(PartOfDayForm::Morning))) + ); + b.rule_1_terminal("déjeuner", + b.reg(r#"d[eéè]jeuner"#)?, + |_| Ok(helpers::hour(12, false)? + .span_to(&helpers::hour(14, false)?, false)? + .latent() + .form(Form::Meal)) + ); + b.rule_1_terminal("après le déjeuner", + b.reg(r#"apr[eè]s (?:le )?d[eéè]jeuner"#)?, + |_| { + let period = helpers::hour(13, false)? + .span_to(&helpers::hour(17, false)?, false)?; + Ok(helpers::cycle_nth(Grain::Day, 0)?.intersect(&period)?.form(Form::PartOfDay(PartOfDayForm::Afternoon))) + } + ); + b.rule_1_terminal("avant le déjeuner", + b.reg(r#"avant (?:le )?d[eéè]jeuner"#)?, + |_| { + let period = helpers::hour(10, false)? + .span_to(&helpers::hour(12, false)?, false)?; + Ok(helpers::cycle_nth(Grain::Day, 0)?.intersect(&period)?.form(Form::PartOfDay(PartOfDayForm::Morning))) + } + ); + b.rule_1_terminal("avant le travail", + b.reg(r#"avant le travail"#)?, + |_| { + let period = helpers::hour(7, false)? + .span_to(&helpers::hour(10, false)?, false)?; + Ok(helpers::cycle_nth(Grain::Day, 0)?.intersect(&period)?.form(Form::PartOfDay(PartOfDayForm::Morning))) + } + ); + b.rule_1_terminal("pendant le travail", + b.reg(r#"pendant le travail"#)?, + |_| { + let period = helpers::hour(9, false)? + .span_to(&helpers::hour(19, false)?, false)?; + Ok(helpers::cycle_nth(Grain::Day, 0)?.intersect(&period)?.form(Form::PartOfDay(PartOfDayForm::None))) + } + ); + b.rule_1_terminal("après le travail", + b.reg(r#"apr[eè]s (?:le )?travail"#)?, + |_| { + let period = helpers::hour(17, false)? + .span_to(&helpers::hour(21, false)?, false)?; + Ok(helpers::cycle_nth(Grain::Day, 0)?.intersect(&period)?.form(Form::PartOfDay(PartOfDayForm::Evening))) + } + ); + b.rule_1_terminal("après-midi", + b.reg(r#"apr[eéè]s?[ \-]?midi|aprem"#)?, + |_| { + Ok(helpers::hour(12, false)? + .span_to(&helpers::hour(19, false)?, false)? + .latent() + .form(Form::PartOfDay(PartOfDayForm::Afternoon))) + } + ); + b.rule_1_terminal("début d'après-midi", + b.reg(r#"d[ée]but (?:d'|de l')(?:apr[eéè]s?[ \-]?midi|aprem)"#)?, + |_| { + Ok(helpers::hour(12, false)? + .span_to(&helpers::hour(15, false)?, false)? + .latent() + .form(Form::PartOfDay(PartOfDayForm::Afternoon))) + } + ); + b.rule_1_terminal("milieu d'après-midi", + b.reg(r#"milieu (?:d'|de l')(?:apr[eéè]s?[ \-]?midi|aprem)"#)?, + |_| { + Ok(helpers::hour(15, false)? + .span_to(&helpers::hour(17, false)?, false)? + .latent() + .form(Form::PartOfDay(PartOfDayForm::Afternoon))) + } + ); + b.rule_1_terminal("gouter", + b.reg(r#"(?:(?:[àa] )?l[' ]heure du|au moment du|pendant le|pour le) go[uû]ter"#)?, + |_| Ok(helpers::hour(16, false)? + .span_to(&helpers::hour(18, false)?, false)? + .form(Form::Meal)) + ); + b.rule_1_terminal("thé", + b.reg(r#"(?:(?:[àa] )?l[' ]heure du|au moment du|pendant le|pour le) th[eé]"#)?, + |_| Ok(helpers::hour(15, false)? + .span_to(&helpers::hour(17, false)?, false)? + .form(Form::Meal)) + ); + b.rule_1_terminal("cafe", + b.reg(r#"(?:(?:[àa] )?l[' ]heure du|au moment du|pendant le|pour le) caf[eé]"#)?, + |_| Ok(helpers::hour(14, false)? + .span_to(&helpers::hour(16, false)?, false)? + .form(Form::Meal)) + ); + b.rule_1_terminal("fin d'après-midi", + b.reg(r#"fin (?:d'|de l')(?:apr[eéè]s?[ \-]?midi|aprem)"#)?, + |_| { + Ok(helpers::hour(17, false)? + .span_to(&helpers::hour(19, false)?, false)? + .latent() + .form(Form::PartOfDay(PartOfDayForm::Afternoon))) + } + ); + // TODO: APERO + b.rule_1_terminal("début de journée", + b.reg(r#"d[ée]but de (?:la )?journ[ée]e"#)?, + |_| { + Ok(helpers::hour(6, false)? + .span_to(&helpers::hour(10, false)?, false)? + .latent() + .form(Form::PartOfDay(PartOfDayForm::Morning))) + } + ); + b.rule_1_terminal("milieu de journée", + b.reg(r#"milieu de (?:la )?journ[ée]e"#)?, + |_| { + Ok(helpers::hour(11, false)? + .span_to(&helpers::hour(16, false)?, false)? + .latent() + .form(Form::PartOfDay(PartOfDayForm::None))) + } + ); + b.rule_1_terminal("fin de journée", + b.reg(r#"fin de (?:la )?journ[ée]e"#)?, + |_| { + Ok(helpers::hour(17, false)? + .span_to(&helpers::hour(21, false)?, false)? + .latent() + .form(Form::PartOfDay(PartOfDayForm::Evening))) + } + ); + b.rule_1_terminal("soir", + b.reg(r#"soir[ée]?e?"#)?, + |_| { + Ok(helpers::hour(18, false)? + .span_to(&helpers::hour(0, false)?, false)? + .latent() + .form(Form::PartOfDay(PartOfDayForm::Evening))) + } + ); + b.rule_1_terminal("début de soirée", + b.reg(r#"d[ée]but de (?:la )?soir[ée]e?"#)?, + |_| { + Ok(helpers::hour(18, false)? + .span_to(&helpers::hour(21, false)?, false)? + .latent() + .form(Form::PartOfDay(PartOfDayForm::Evening))) + } + ); + b.rule_1_terminal("fin de soirée", + b.reg(r#"fin de (?:la )?soir[ée]e?"#)?, + |_| { + Ok(helpers::hour(21, false)? + .span_to(&helpers::hour(0, false)?, false)? + .latent() + .form(Form::PartOfDay(PartOfDayForm::Evening))) + } + ); + b.rule_1_terminal("diner", + b.reg(r#"d[iî]ner|souper"#)?, + |_| Ok(helpers::hour(18, false)? + .span_to(&helpers::hour(23, false)?, false)? + .form(Form::Meal)) + ); + b.rule_1_terminal("nuit", + b.reg(r#"nuit"#)?, + |_| { + Ok(helpers::hour(22, false)? + .span_to(&helpers::hour(6, false)?, false)? + .latent() + .form(Form::PartOfDay(PartOfDayForm::Night))) + } + ); + b.rule_2("a l'heure de ", + b.reg(r#"(?:[àa] )?l[' ]heure du|au moment du|pendant l[ea']|au|pour l[ea']|l[ea']"#)?, + datetime_check!(|datetime: &DatetimeValue| form!(Form::Meal)(datetime)), + |_, a| Ok(a.value().clone().not_latent()) + ); + b.rule_2(" ", + datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), + datetime_check!(form!(Form::Meal)), + |a, b| a.value().intersect(b.value()) + ); + b.rule_2("prep? & article ", // This is very catch-all/junky + b.reg(r#"(?:pendant |durant |dans |d[eè]s )?l[ae']?|en|au"#)?, + datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime)), + |_, a| Ok(a.value().clone().not_latent()) + ); + b.rule_2("ce ", + b.reg(r#"cet?t?e?"#)?, + datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), + |_, datetime| Ok(helpers::cycle_nth(Grain::Day, 0)? + .intersect(datetime.value())? + .form(datetime.value().form.clone()) + .datetime_kind(DatetimeKind::DatetimeComplement { date_and_time: true, today: true })) + ); + b.rule_2(" ", + datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), + datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), + |a, b| a.value().intersect(b.value()) + ); + b.rule_2(" du matin", + datetime_check!(form!(Form::TimeOfDay(_))), + b.reg(r#"(?:(?:du|dans|de) )?(?:(?:au|le|la) )?mat(?:in[ée]?e?)?(?: pile| exactement| pr[eé]cises?)?"#)?, + |a, _| { + let period = helpers::hour(0, false)? + .span_to(&helpers::hour(12, false)?, false)?; + Ok(a.value().intersect(&period)?.form(a.value().form.clone())) + } + ); + b.rule_2(" de l'apres-midi", + datetime_check!(form!(Form::TimeOfDay(_))), + b.reg(r#"(?:dans |de )?l[' ]apr[eè]s[\- ]midi(?: pile| exactement| pr[eé]cises?)?"#)?, + |a, _| { + let period = helpers::hour(12, false)? + .span_to(&helpers::hour(19, false)?, false)?; + Ok(a.value().intersect(&period)?.form(a.value().form.clone())) + } + ); + b.rule_2(" du soir", + datetime_check!(form!(Form::TimeOfDay(_))), + b.reg(r#"(?:(?:du|dans|de) )?(?:(?:au|le|la) )?soir[ée]?e?(?: pile| exactement| pr[eé]cises?)?"#)?, + |a, _| { + let period = helpers::hour(16, false)? + .span_to(&helpers::hour(0, false)?, false)?; + Ok(a.value().intersect(&period)?.form(a.value().form.clone())) + } + ); + b.rule_3(" du ", + datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), + b.reg(r#"du"#)?, + datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), + |a, _, b| b.value().intersect(a.value()) + ); + b.rule_1_terminal("(ce/le) week-end", + b.reg(r#"(?:[cl]e )?week(?:\s|-)?end"#)?, + |_| helpers::weekend() + ); + b.rule_1_terminal("le week-end dernier", + b.reg(r#"le week(?:\s|-)?end dernier"#)?, + |_| { + let weekend = helpers::weekend()?; + Ok(weekend.the_nth(-1)?.datetime_kind(DatetimeKind::DatePeriod)) + } + ); + b.rule_1_terminal("le week-end prochain", + b.reg(r#"le week(?:\s|-)?end prochain|le prochain week(?:\s|-)?end"#)?, + |_| { + let weekend = helpers::weekend()?; + Ok(weekend.the_nth(1)?.datetime_kind(DatetimeKind::DatePeriod)) + } + ); + b.rule_1_terminal("début de semaine", + b.reg(r#"(?:en |au )?d[ée]but de (?:cette |la )?semaine"#)?, + |_| helpers::day_of_week(Weekday::Mon) + ?.span_to(&helpers::day_of_week(Weekday::Tue)?, false) + ); + b.rule_1_terminal("milieu de semaine", + b.reg(r#"(?:en |au )?milieu de (?:cette |la )?semaine"#)?, + |_| helpers::day_of_week(Weekday::Wed) + ?.span_to(&helpers::day_of_week(Weekday::Thu)?, false) + ); + b.rule_1_terminal("fin de semaine (Warning: this is the weekend in Quebec)", + b.reg(r#"(?:en |à la )?fin de (?:cette |la )?semaine"#)?, + |_| helpers::day_of_week(Weekday::Thu) + ?.span_to(&helpers::day_of_week(Weekday::Sun)?, false) + ); + b.rule_1_terminal("en semaine", + b.reg(r#"(?:pendant la |en )semaine"#)?, + |_| helpers::day_of_week(Weekday::Mon) + ?.span_to(&helpers::day_of_week(Weekday::Fri)?, false) + ); + b.rule_1_terminal("season", + b.reg(r#"(?:cet )?(?:été|ete)"#)?, + |_| helpers::month_day(6, 21)?.span_to(&helpers::month_day(9, 23)?, false) + ); + b.rule_1_terminal("season", + b.reg(r#"(?:cet )?automne"#)?, + |_| helpers::month_day(9, 23)?.span_to(&helpers::month_day(12, 21)?, false) + ); + b.rule_1_terminal("season", + b.reg(r#"(?:cet )?hiver"#)?, + |_| helpers::month_day(12, 21)?.span_to(&helpers::month_day(3, 20)?, false) + ); + b.rule_1_terminal("season", + b.reg(r#"(?:ce )?printemps"#)?, + |_| helpers::month_day(3, 20)?.span_to(&helpers::month_day(6, 21)?, false) + ); + b.rule_2("le ", + b.reg(r#"l[ea]"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent), + |_, a| Ok(a.value().clone()) + ); + b.rule_4("dd-dd (interval)", + b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, + b.reg(r#"\-|(?:jusqu')?au"#)?, + b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, + datetime_check!(form!(Form::Month(_))), + |a, _, b, month| { + let start = month.value().intersect(&helpers::day_of_month(a.group(1).parse()?)?)?; + let end = month.value().intersect(&helpers::day_of_month(b.group(1).parse()?)?)?; + start.span_to(&end, true) + } + ); + b.rule_4("-dd (interval)", + datetime_check!(), + b.reg(r#"\-|(?:jusqu')?au"#)?, + b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, + datetime_check!(form!(Form::Month(_))), + |datetime, _, text_match, month| { + let start = month.value().intersect(datetime.value())?; + let end = month.value().intersect(&helpers::day_of_month(text_match.group(1).parse()?)?)?; + start.span_to(&end, true) + } + ); + b.rule_5("- dd (interval)", + datetime_check!(), + b.reg(r#"\-|(?:jusqu')?au"#)?, + datetime_check!(form!(Form::DayOfWeek{..})), + b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, + datetime_check!(form!(Form::Month(_))), + |datetime, _, _, text_match, month| { + let start = month.value().intersect(datetime.value())?; + let end = month.value().intersect(&helpers::day_of_month(text_match.group(1).parse()?)?)?; + start.span_to(&end, true) + } + ); + b.rule_6(" 1er- dd (interval)", + datetime_check!(form!(Form::DayOfWeek{..})), + b.reg(r#"premier|prem\.?|1er|1 er"#)?, + b.reg(r#"\-|(?:jusqu')?au"#)?, + datetime_check!(form!(Form::DayOfWeek{..})), + b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, + datetime_check!(form!(Form::Month(_))), + |_, _, _, _, text_match, month| { + let start = month.value().intersect(&helpers::day_of_month(1)?)?; + let end = month.value().intersect(&helpers::day_of_month(text_match.group(1).parse()?)?)?; + start.span_to(&end, true) + } + ); + b.rule_6("du dd- dd (interval)", + b.reg(r#"du"#)?, + b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, + b.reg(r#"\-|(?:jusqu')?au"#)?, + datetime_check!(form!(Form::DayOfWeek{..})), + b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, + datetime_check!(form!(Form::Month(_))), + |_, a, _, _, b, month| { + let start = month.value().intersect(&helpers::day_of_month(a.group(1).parse()?)?)?; + let end = month.value().intersect(&helpers::day_of_month(b.group(1).parse()?)?)?; + start.span_to(&end, true) + } + ); + b.rule_6("du dd- dd (interval)", + b.reg(r#"du"#)?, + datetime_check!(), + b.reg(r#"\-|(?:jusqu')?au"#)?, + datetime_check!(form!(Form::DayOfWeek{..})), + b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, + datetime_check!(form!(Form::Month(_))), + |_, datetime, _, _, text_match, month| { + let start = month.value().intersect(datetime.value())?; + let end = month.value().intersect(&helpers::day_of_month(text_match.group(1).parse()?)?)?; + start.span_to(&end, true) + } + ); + b.rule_4("la nuit ", + b.reg(r#"(dans|pendant|durant) la nuit (?:du|de)"#)?, + datetime_check!(form!(Form::DayOfWeek{..})), + b.reg(r#"\-|(?:jusqu')?au"#)?, + datetime_check!(form!(Form::DayOfWeek{..})), + |_, start, _, end| { + let start = start.value().intersect(&helpers::hour(22, false)?)?; + let end = end.value().intersect(&helpers::hour(6, false)?)?; + start.span_to(&end, false) + } + ); + b.rule_5("entre dd et dd (interval)", + b.reg(r#"entre(?: le)?"#)?, + b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, + b.reg(r#"et(?: le)?"#)?, + b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, + datetime_check!(form!(Form::Month(_))), + |_, a, _, b, month| { + let start = month.value().intersect(&helpers::day_of_month(a.group(1).parse()?)?)?; + let end = month.value().intersect(&helpers::day_of_month(b.group(1).parse()?)?)?; + start.span_to(&end, true) + } + ); + b.rule_4_terminal("du dd au dd(interval)", + b.reg(r#"du"#)?, + b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, + b.reg(r#"(?:jusqu')?au"#)?, + b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, + |_, a, _, b| { + let start = helpers::day_of_month(a.group(1).parse()?)?; + let end = helpers::day_of_month(b.group(1).parse()?)?; + start.span_to(&end, true) + } + ); + b.rule_2("fin (interval)", + b.reg(r#"fin(?: du mois d[e']? ?)?"#)?, + datetime_check!(form!(Form::Month(_))), + |_, month| { + let start = month.value().intersect(&helpers::day_of_month(25)?)?; + let end = helpers::cycle(Grain::Day)?.last_of(month.value())?; + start.span_to(&end, true) + } + ); + b.rule_2("début (interval)", + b.reg(r#"d[ée]but(?: du mois d[e'] ?)?"#)?, + datetime_check!(form!(Form::Month(_))), + |_, month| { + let start = month.value().intersect(&helpers::day_of_month(1)?)?; + let end = month.value().intersect(&helpers::day_of_month(5)?)?; + start.span_to(&end, true) + } + ); + b.rule_2("première quinzaine de (interval)", + b.reg(r#"(?:premi[èe]re|1 ?[èe]re) (?:quinzaine|15 ?aine) d[e']"#)?, + datetime_check!(form!(Form::Month(_))), + |_, month| { + let start = month.value().intersect(&helpers::day_of_month(1)?)?; + let end = month.value().intersect(&helpers::day_of_month(14)?)?; + start.span_to(&end, true) + } + ); + b.rule_2("deuxième quinzaine de (interval)", + b.reg(r#"(?:deuxi[èe]me|2 ?[èe]me) (?:quinzaine|15 ?aine) d[e']"#)?, + datetime_check!(form!(Form::Month(_))), + |_, month| { + let start = month.value().intersect(&helpers::day_of_month(15)?)?; + let end = helpers::cycle(Grain::Day)?.last_of(month.value())?; + start.span_to(&end, true) + } + ); + b.rule_2("", + b.reg(r#"mi[- ]"#)?, + datetime_check!(form!(Form::Month(_))), + |_, month| { + let start = month.value().intersect(&helpers::day_of_month(10)?)?; + let end = month.value().intersect(&helpers::day_of_month(19)?)?; + start.span_to(&end, true) + } + ); + + /* Intervals */ + b.rule_3(" - (interval)", + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), + b.reg(r#" \- |(?:jusqu')?(?:au|[aà])"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), + |a, _, b| a.value().span_to(b.value(), true) + ); + b.rule_3(" avant (interval)", + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), + b.reg(r#"jusqu'(?:au|[aà])|avant"#)?, + datetime_check!(form!(Form::TimeOfDay(_))), + |a, _, b| a.value().span_to(b.value(), false) + ); + b.rule_4("de - (interval)", + b.reg(r#"depuis|d[e'u]?"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), + b.reg(r#" \- |(?:jusqu')?(?:au|[aà])"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), + |_, a, _, b| a.value().span_to(b.value(), true) + ); + b.rule_4("entre et (interval)", + b.reg(r#"entre"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), + b.reg(r#"et"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), + |_, a, _, b| a.value().span_to(b.value(), true) + ); + // Specific case with years + b.rule_5("de - (interval)", + b.reg(r#"depuis|d[e'u]?"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), + b.reg(r#" \- |(?:jusqu')?(?:au|[aà])"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime) && datetime.is_coarse_grain_greater_than(Grain::Year)), + datetime_check!(form!(Form::Year(_))), + |_, a, _, b, year| a.value().span_to(b.value(), true)?.intersect(year.value()) + ); + b.rule_5("entre et (interval)", + b.reg(r#"entre"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), + b.reg(r#"et"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime) && datetime.is_coarse_grain_greater_than(Grain::Year)), + datetime_check!(form!(Form::Year(_))), + |_, a, _, b, year| a.value().span_to(b.value(), true)?.intersect(year.value()) + ); + b.rule_3(" - (interval)", + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(_))(datetime)), + b.reg(r#" \- |(?:jusqu')?(?:au|[aà])"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(_))(datetime)), + |a, _, b| a.value().smart_span_to(b.value(), false) + ); + b.rule_4("de - (interval)", + b.reg(r#"(?:[aà] partir )?d['e]"#)?, + datetime_check!(form!(Form::TimeOfDay(_))), + b.reg(r#"(?:jusqu')?(?:au|[aà])"#)?, + datetime_check!(form!(Form::TimeOfDay(_))), + |_, a, _, b| a.value().smart_span_to(b.value(), false) + ); + b.rule_2("de maintenant - (interval)", + b.reg(r#"(?:[aà] partir )?de maintenant (?:jusqu')?(?:au|[aà])"#)?, + datetime_check!(form!(Form::TimeOfDay(_))), + |_, a| helpers::cycle_nth(Grain::Second, 0)?.smart_span_to(a.value(), false) + ); + b.rule_3("de - maintenant (interval)", + b.reg(r#"(?:[aà] partir )?d['e]"#)?, + datetime_check!(form!(Form::TimeOfDay(_))), + b.reg(r#"(?:jusqu')?(?:au|[aà]) maintenant"#)?, + |_, a, _| { + let now = helpers::cycle_nth(Grain::Second, 0)?; + a.value().smart_span_to(&now, false) + } + ); + b.rule_4("entre et (interval)", + b.reg(r#"entre"#)?, + datetime_check!(form!(Form::TimeOfDay(_))), + b.reg(r#"et"#)?, + datetime_check!(form!(Form::TimeOfDay(_))), + |_, a, _, b| a.value().smart_span_to(b.value(), false) + ); + b.rule_2("jusqu'à ", + b.reg(r#"(?:n[ ']importe quand )?jusqu'(?:au|[aà]|en)"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent), + |_, datetime| Ok(datetime.value().clone().mark_before_end()) + ); + b.rule_2("avant ", + b.reg(r#"(?:n[ ']importe quand )?avant"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent), + |_, datetime| Ok(datetime.value().clone().mark_before_start()) + ); + b.rule_2("après ", + b.reg(r#"apr[eè]s"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent), + |_, datetime| Ok(datetime.value().clone().mark_after_end()) + ); + b.rule_2("à partir de ", + b.reg(r#"[aà] partir d['eu]"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent), + |_, datetime| Ok(datetime.value().clone().mark_after_start()) + ); + b.rule_2("à partir de ", + b.reg(r#"[aà] partir d['eu](?:l[ 'a])?"#)?, + datetime_check!(form!(Form::PartOfDay(_))), + |_, datetime| Ok(datetime.value().clone().mark_after_start()) + ); + b.rule_2("après le ", + b.reg(r#"apr(?:e|è)s le"#)?, + integer_check_by_range!(1, 31), + |_, integer| Ok(helpers::day_of_month(integer.value().value as u32)?.mark_after_end()) + ); + b.rule_2("après le ", + b.reg(r#"[aà] partir d['eu]"#)?, + integer_check_by_range!(1, 31), + |_, integer| Ok(helpers::day_of_month(integer.value().value as u32)?.mark_after_start()) + ); + b.rule_2("depuis ", + b.reg(r#"depuis"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent), + |_, datetime| Ok(datetime.value().the_nth(-1)?.mark_after_start()) + ); + b.rule_2("d'ici ", + b.reg(r#"d'ici"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(_))(datetime)), + |_, tod| { + // FIXME: This adds one second to the value of now+then + let now = helpers::cycle_nth(Grain::Second, 0)?; + let then = tod.value().clone().mark_before_start(); + now.span_to(&then, false) + } + ); + b.rule_2("d'ici ", + b.reg(r#"d'ici"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), + |_, date| { + // FIXME: This adds one second to the value of now+then + let today = helpers::cycle_nth(Grain::Day, 0)?; + let then = date.value().clone().mark_before_start(); + today.span_to(&then, false) + } + ); + Ok(()) +} + +pub fn rules_datetime_with_duration(b: &mut RuleSetBuilder) -> RustlingResult<()> { + b.rule_2("il y a ", + b.reg(r#"il y a"#)?, + duration_check!(), + |_, duration| duration.value().ago() + ); + // With "depuis/d'ici", interpretation of duration ambiguous with time-of-day we choose time-of-day + // I.e. "x heures (y)", but not "x heures et y minutes", "x minutes", etc. + // FIXME: some time-of-day patterns should be removed, e.g. "x heures y minutes" - they are not + // proper time-of-day, but they can be duration expressions + // TODO: check if Grain::Second here breaks DatePeriod - cf. implem. of equivalent in English? + b.rule_2("depuis ", + b.reg(r#"depuis|[cç]a fait"#)?, + duration_check!(), + |_, duration| { + if duration.value().get_coarser_grain() == Grain::Hour { + return Err(RuleError::Invalid.into()) + } + duration.value().ago()? + .span_to(&helpers::cycle_nth(Grain::Second, 0)?, false) + }); + b.rule_2("d'ici ", + b.reg(r#"d'ici|dans l(?:'|es?)"#)?, + duration_check!(), + |_, duration| { + let duration_grain = duration.value().get_coarser_grain(); + // Priority to d'ici + if duration_grain == Grain::Hour && + // FIXME: There must be a better way to do this check! + duration.value().period.0.get(Grain::Hour as usize).unwrap_or(&0) <= &23 { + return Err(RuleError::Invalid.into()) + } + let grain = if duration_grain.is_date_grain() { Grain::Day } else { Grain::Second }; + let start = helpers::cycle_nth(grain, 0)?; + let end = if grain == Grain::Day { duration.value().in_present_day()? } else { duration.value().in_present()? }; + start.span_to(&end, true) + } + ); + b.rule_2("dans le ", + b.reg(r#"dans l(?:'|es?)"#)?, + duration_check!(), + |_, duration| { + let duration_grain = duration.value().get_coarser_grain(); + let grain = if duration_grain.is_date_grain() { Grain::Day } else { Grain::Second }; + let start = helpers::cycle_nth(grain, 0)?; + let end = if grain == Grain::Day { duration.value().in_present_day()? } else { duration.value().in_present()? }; + start.span_to(&end, false) + } + ); + b.rule_3(" apres ", + duration_check!(), + b.reg(r#"apr[eè]s"#)?, + datetime_check!(), + |duration, _, datetime| duration.value().after(datetime.value()) + ); + b.rule_3(" avant ", + duration_check!(), + b.reg(r#"avant"#)?, + datetime_check!(), + |duration, _, datetime| duration.value().before(datetime.value()) + ); + b.rule_2("dans ", + b.reg(r#"dans"#)?, + duration_check!(), + |_, duration| duration.value().in_present() + ); + b.rule_2(" plus tard", + duration_check!(), + b.reg(r"plus tard")?, + |duration, _| duration.value().in_present() + ); + Ok(()) +} + + +pub fn rules_datetime_with_cycle(b: &mut RuleSetBuilder) -> RustlingResult<()> { + // Cycle patterns relative to now + b.rule_2("ce|dans le ", + b.reg(r#"(?:(?:dans )?l[ea' ]|cet?(?:te)?)"#)?, + cycle_check!(), + |_, cycle| helpers::cycle_nth(cycle.value().grain, 0) + ); + b.rule_3("ce (la ou ci)", + b.reg(r#"cet?t?e?s?"#)?, + cycle_check!(), + b.reg(r#"-?ci"#)?, + |_, cycle, _| helpers::cycle_nth(cycle.value().grain, 0) + ); + b.rule_2(" dernier", + cycle_check!(), + b.reg(r#"derni[èe]re?|pass[ée]e?|pr[eé]c[eé]dente?|(?:d')? ?avant"#)?, + |cycle, _| helpers::cycle_nth(cycle.value().grain, -1) + ); + b.rule_3("le dernier", + b.reg(r#"l[ae']? ?"#)?, + cycle_check!(), + b.reg(r#"derni[èe]re?|pass[ée]e?"#)?, + |_, cycle, _| helpers::cycle_nth(cycle.value().grain, -1) + ); + b.rule_3("n derniers ", + integer_check_by_range!(2, 9999), + b.reg(r#"derni.re?s?"#)?, + cycle_check!(), + |integer, _, cycle| { + let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, -1 * integer.value().value)?; + // All grains except Day will trigger the right datetime_kind + if cycle.value().grain == Grain::Day { + res = res.datetime_kind(DatetimeKind::DatePeriod); + } + Ok(res) + } + ); + b.rule_4("(pendant/durant/dans) les n derniers ", + b.reg(r#"(?:pendant |durant |dans )?[cld]es"#)?, + integer_check_by_range!(2, 9999), + b.reg(r#"derni.re?s?"#)?, + cycle_check!(), + |_, integer, _, cycle| { + let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, -1 * integer.value().value)?; + // All grains except Day will trigger the right datetime_kind + if cycle.value().grain == Grain::Day { + res = res.datetime_kind(DatetimeKind::DatePeriod); + } + Ok(res) + } + ); + b.rule_3("n passes|precedents", + integer_check_by_range!(2, 9999), + cycle_check!(), + b.reg(r#"pass[eèé][eèé]?s?|pr[eé]c[eé]dente?s?|(?:d')? ?avant|plus t[oô]t"#)?, + |integer, cycle, _| { + let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, -1 * integer.value().value)?; + // All grains except Day will trigger the right datetime_kind + if cycle.value().grain == Grain::Day { + res = res.datetime_kind(DatetimeKind::DatePeriod); + } + Ok(res) + } + ); + b.rule_4("(pendant/durant/dans) les n passes|precedents", + b.reg(r#"(?:pendant |durant |dans )?[cld]es"#)?, + integer_check_by_range!(2, 9999), + cycle_check!(), + b.reg(r#"pass[eèé][eèé]?s?|pr[eé]c[eé]dente?s?|(?:d')? ?avant|plus t[oô]t"#)?, + |_, integer, cycle, _| helpers::cycle_n_not_immediate(cycle.value().grain, -1 * integer.value().value) + ); + // Incorrect resolution if some follows the expression, + // e.g. "suivant le " (unsupported) + b.rule_2(" prochain|suivant|d'après", + cycle_check!(), + b.reg(r#"prochaine?|suivante?|qui suit|(?:d')? ?apr[eèé]s"#)?, + |cycle, _| helpers::cycle_nth(cycle.value().grain, 1) + ); + b.rule_3("le prochain|suivant|d'après", + b.reg(r#"l[ae']? ?|une? ?"#)?, + cycle_check!(), + b.reg(r#"prochaine?|suivante?|qui suit|(?:d'? ?)?apr[eèé]s"#)?, + |_, cycle, _| helpers::cycle_nth(cycle.value().grain, 1) + ); + b.rule_3("n prochains ", + integer_check_by_range!(2, 9999), + b.reg(r#"prochaine?s?|suivante?s?|apr[eèé]s"#)?, + cycle_check!(), + |integer, _, cycle| { + let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, integer.value().value)?; + // All grains except Day will trigger the right datetime_kind + if cycle.value().grain == Grain::Day { + res = res.datetime_kind(DatetimeKind::DatePeriod); + } + Ok(res) + } + ); + b.rule_4("(pendant/durant/dans) les n prochains ", + b.reg(r#"(?:pendant |durant |dans )?[cld]es"#)?, + integer_check_by_range!(2, 9999), + b.reg(r#"prochaine?s?|suivante?s?|apr[eèé]s"#)?, + cycle_check!(), + |_, integer, _, cycle| { + let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, integer.value().value)?; + // All grains except Day will trigger the right datetime_kind + if cycle.value().grain == Grain::Day { + res = res.datetime_kind(DatetimeKind::DatePeriod); + } + Ok(res) + } + ); + b.rule_3("n suivants", + integer_check_by_range!(2, 9999), + cycle_check!(), + b.reg(r#"prochaine?s?|suivante?s?|apr[eèé]s|qui sui(?:t|ves?)|plus tard"#)?, + |integer, cycle, _| { + let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, integer.value().value)?; + // All grains except Day will trigger the right datetime_kind + if cycle.value().grain == Grain::Day { + res = res.datetime_kind(DatetimeKind::DatePeriod); + } + Ok(res) + } + ); + b.rule_4("(pendant/durant/dans) les n suivants", + b.reg(r#"(?:pendant |durant |dans )?[cld]es"#)?, + integer_check_by_range!(2, 9999), + cycle_check!(), + b.reg(r#"prochaine?s?|suivante?s?|apr[eèé]s|qui sui(?:t|ves?)|plus tard"#)?, + |_, integer, cycle, _| { + let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, integer.value().value)?; + // All grains except Day will trigger the right datetime_kind + if cycle.value().grain == Grain::Day { + res = res.datetime_kind(DatetimeKind::DatePeriod); + } + Ok(res) + } + ); + b.rule_3("n avant", + integer_check_by_range!(2, 9999), + cycle_check!(), + b.reg(r#"(?:d')? ?avant|plus t[oô]t"#)?, + |integer, cycle, _| { + let mut res = helpers::cycle_nth(cycle.value().grain, -1 * integer.value().value)?; + // All grains except Day will trigger the right datetime_kind + if cycle.value().grain == Grain::Day { + res = res.datetime_kind(DatetimeKind::DatePeriod); + } + Ok(res) + } + ); + b.rule_3("n après", + integer_check_by_range!(2, 9999), + cycle_check!(), + b.reg(r#"(?:d')? ?apr[eèé]s|qui sui(?:t|ves?)|plus tard"#)?, + |integer, cycle, _| { + let mut res = helpers::cycle_nth(cycle.value().grain, integer.value().value)?; + // All grains except Day will trigger the right datetime_kind + if cycle.value().grain == Grain::Day { + res = res.datetime_kind(DatetimeKind::DatePeriod); + } + Ok(res) + } + ); + // Cycle patterns relative to another datetime + b.rule_4("le après|suivant ", + b.reg(r#"l[ea']? ?"#)?, + cycle_check!(), + b.reg(r#"suivante?|apr[eèé]s"#)?, + datetime_check!(), + |_, cycle, _, datetime| helpers::cycle_nth_after(cycle.value().grain, 1, datetime.value()) + ); + b.rule_4("le avant|précédent ", + b.reg(r#"l[ea']? ?"#)?, + cycle_check!(), + b.reg(r#"avant|pr[ée]c[ée]dent"#)?, + datetime_check!(), + |_, cycle, _, datetime| helpers::cycle_nth_after(cycle.value().grain, -1, datetime.value()) + ); + b.rule_4(" de ", + ordinal_check!(), + cycle_check!(), + b.reg(r#"d['eu]|en"#)?, + datetime_check!(), + |ordinal, cycle, _, datetime| helpers::cycle_nth_after_not_immediate(cycle.value().grain, ordinal.value().value - 1, datetime.value()) + ); + b.rule_5("le de ", + b.reg(r#"l[ea]"#)?, + ordinal_check!(), + cycle_check!(), + b.reg(r#"d['eu]|en"#)?, + datetime_check!(), + |_, ordinal, cycle, _, datetime| helpers::cycle_nth_after_not_immediate(cycle.value().grain, ordinal.value().value - 1, datetime.value()) + ); + b.rule_4("le de ", + b.reg(r#"l[ea]"#)?, + cycle_check!(), + b.reg(r#"d['eu]|en"#)?, + datetime_check!(), + |_, cycle, _, datetime| helpers::cycle_nth_after_not_immediate(cycle.value().grain, 0, datetime.value()) + ); + Ok(()) +} + + +/* DATETIME - CYCLE DEFINITIONS */ +pub fn rules_cycle(b: &mut RuleSetBuilder) -> RustlingResult<()> { + b.rule_1_terminal("seconde (cycle)", + b.reg(r#"secondes?"#)?, + |_| CycleValue::new(Grain::Second) + ); + b.rule_1_terminal("minute (cycle)", + b.reg(r#"minutes?"#)?, + |_| CycleValue::new(Grain::Minute) + ); + b.rule_1_terminal("heure (cycle)", + b.reg(r#"heures?"#)?, + |_| CycleValue::new(Grain::Hour) + ); + b.rule_1_terminal("jour (cycle)", + b.reg(r#"jour(?:n[ée]e?)?s?"#)?, + |_| CycleValue::new(Grain::Day) + ); + b.rule_1_terminal("semaine (cycle)", + b.reg(r#"semaines?"#)?, + |_| CycleValue::new(Grain::Week) + ); + b.rule_1("mois (cycle)", + b.reg(r#"mois"#)?, + |_| CycleValue::new(Grain::Month) + ); + b.rule_1_terminal("trimestre (cycle)", + b.reg(r#"trimestre"#)?, + |_| CycleValue::new(Grain::Quarter) + ); + b.rule_1("année (cycle)", + b.reg(r#"an(?:n[ée]e?)?s?"#)?, + |_| CycleValue::new(Grain::Year) + ); + Ok(()) +} diff --git a/grammar/fr/src/rules_duration.rs b/grammar/fr/src/rules_duration.rs new file mode 100644 index 00000000..33cb0e88 --- /dev/null +++ b/grammar/fr/src/rules_duration.rs @@ -0,0 +1,117 @@ +use rustling::*; +use rustling_ontology_values::dimension::*; +use rustling_ontology_values::helpers; +use rustling_ontology_moment::{Grain, PeriodComp, Period}; + +pub fn rules_duration(b: &mut RuleSetBuilder) -> RustlingResult<()> { + b.rule_1_terminal("seconde (unit-of-duration)", + b.reg(r#"sec(?:onde)?s?"#)?, + |_| Ok(UnitOfDurationValue::new(Grain::Second)) + ); + b.rule_1_terminal("minute (unit-of-duration)", + b.reg(r#"min(?:ute)?s?"#)?, + |_| Ok(UnitOfDurationValue::new(Grain::Minute)) + ); + b.rule_1_terminal("heure (unit-of-duration)", + b.reg(r#"h(?:eure)?s?"#)?, + |_| Ok(UnitOfDurationValue::new(Grain::Hour)) + ); + b.rule_1_terminal("jour (unit-of-duration)", + b.reg(r#"jour(?:n[ée]e?)?s?"#)?, + |_| Ok(UnitOfDurationValue::new(Grain::Day)) + ); + b.rule_1_terminal("semaine (unit-of-duration)", + b.reg(r#"semaines?"#)?, + |_| Ok(UnitOfDurationValue::new(Grain::Week)) + ); + b.rule_1_terminal("mois (unit-of-duration)", + b.reg(r#"mois?"#)?, + |_| Ok(UnitOfDurationValue::new(Grain::Month)) + ); + b.rule_1_terminal("année (unit-of-duration)", + b.reg(r#"an(?:n[ée]e?)?s?"#)?, + |_| Ok(UnitOfDurationValue::new(Grain::Year)) + ); + b.rule_1_terminal("un quart heure", + b.reg(r#"(1/4\s?h(?:eure)?|(?:un|1) quart d'heure)"#)?, + |_| Ok(DurationValue::new(PeriodComp::minutes(15).into())) + ); + b.rule_1_terminal("une demi heure", + b.reg(r#"(?:1/2\s?h(?:eure)?|(?:1|une) demi(?:e)?(?:\s|-)heure)"#)?, + |_| Ok(DurationValue::new(PeriodComp::minutes(30).into())) + ); + b.rule_1_terminal("trois quarts d'heure", + b.reg(r#"(?:3/4\s?h(?:eure)?|(?:3|trois) quart(?:s)? d'heure)"#)?, + |_| Ok(DurationValue::new(PeriodComp::minutes(45).into())) + ); + b.rule_2(" ", + integer_check_by_range!(0), + unit_of_duration_check!(), + |integer, unit| Ok(DurationValue::new(PeriodComp::new(unit.value().grain, integer.value().value).into())) + ); + b.rule_3(" de ", + integer_check!(|integer: &IntegerValue| integer.value >= 0 && integer.group), + b.reg(r#"d[e']"#)?, + unit_of_duration_check!(), + |integer, _, unit| Ok(DurationValue::new(PeriodComp::new(unit.value().grain, integer.value().value).into())) + ); + b.rule_4(" h ", + integer_check_by_range!(0), + b.reg(r#"h(?:eures?)?"#)?, + integer_check_by_range!(0,59), + b.reg(r#"m(?:inutes?)?"#)?, + |hour, _, minute, _| { + let hour_period = Period::from(PeriodComp::new(Grain::Hour, hour.value().clone().value)); + let minute_period = Period::from(PeriodComp::new(Grain::Minute, minute.value().clone().value)); + Ok(DurationValue::new(hour_period + minute_period)) + } + ); + b.rule_3(" et quart", + integer_check_by_range!(0), + unit_of_duration_check!(), + b.reg(r#"et quart"#)?, + |integer, uod, _| { + let quarter_period: Period = uod.value().grain.quarter_period().map(|a| a.into()).ok_or_else(|| RuleError::Invalid)?; + Ok(DurationValue::new(quarter_period + PeriodComp::new(uod.value().grain, integer.value().value))) + } + ); + b.rule_3(" et demie", + integer_check_by_range!(0), + unit_of_duration_check!(), + b.reg(r#"et demie?"#)?, + |integer, uod, _| { + let half_period: Period = uod.value().grain.half_period().map(|a| a.into()).ok_or_else(|| RuleError::Invalid)?; + Ok(DurationValue::new(half_period + PeriodComp::new(uod.value().grain, integer.value().value))) + } + ); + b.rule_3(" et ", + duration_check!(|duration: &DurationValue| !duration.suffixed), + b.reg(r#"et"#)?, + duration_check!(|duration: &DurationValue| !duration.prefixed), + |a, _, b| Ok(a.value() + b.value()) + ); + b.rule_2(" ", + duration_check!(|duration: &DurationValue| !duration.suffixed), + duration_check!(|duration: &DurationValue| !duration.prefixed), + |a, b| Ok(a.value() + b.value()) + ); + b.rule_2(" ", + duration_check!(|duration: &DurationValue| !duration.prefixed), + integer_check_by_range!(0), + |duration, integer| helpers::compose_duration_with_integer(duration.value(), integer.value()) + ); + b.rule_2("environ ", + b.reg(r#"environ"#)?, + duration_check!(), + |_, duration| Ok(duration.value().clone().precision(Precision::Approximate)) + ); + // Ambiguous w/ time-of-day w/ "pour" + // Duration has less priority than Datetime types, therefore duration will be output only + // if the output kind filter is set for Duration + b.rule_2("pendant ", + b.reg(r#"pendant|durant|pour(?: une dur[eé]e? d['e])?"#)?, + duration_check!(), + |_, duration| Ok(duration.value().clone().prefixed()) + ); + Ok(()) +} \ No newline at end of file diff --git a/grammar/fr/src/rules_number.rs b/grammar/fr/src/rules_number.rs new file mode 100644 index 00000000..da1ee39d --- /dev/null +++ b/grammar/fr/src/rules_number.rs @@ -0,0 +1,561 @@ +use std::f32; + +use rustling::*; +use rustling_ontology_values::dimension::*; +use rustling_ontology_values::helpers; + +pub fn rules_numbers(b: &mut RuleSetBuilder) -> RustlingResult<()> { + b.rule_2("intersect", + number_check!(|number: &NumberValue| number.grain().unwrap_or(0) > 1), + number_check!(), + |a, b| helpers::compose_numbers(&a.value(), &b.value())); + b.rule_1_terminal( + "number (0..16)", + b.reg(r#"(z[eé]ro|une?|deux|trois|quatre|cinq|six|sept|huit|neuf|dix|onze|douze|treize|quatorze|quinze|seize)"#)?, + |text_match| { + let value = match text_match.group(1).as_ref() { + "zéro" => 0, + "zero" => 0, + "un" => 1, + "une" => 1, + "deux" => 2, + "trois" => 3, + "quatre" => 4, + "cinq" => 5, + "six" => 6, + "sept" => 7, + "huit" => 8, + "neuf" => 9, + "dix" => 10, + "onze" => 11, + "douze" => 12, + "treize" => 13, + "quatorze" => 14, + "quinze" => 15, + "seize" => 16, + _ => return Err(RuleError::Invalid.into()), + }; + IntegerValue::new(value) + }); + b.rule_1_terminal("quelques", + b.reg(r#"quelques"#)?, + |_| IntegerValue::new_with_grain(3, 1) + ); + b.rule_1_terminal("number (20..60)", + b.reg(r#"(vingt|trente|quarante|cinquante|soixante)"#)?, + |text_match| { + let value = match text_match.group(1).as_ref() { + "vingt" => 20, + "trente" => 30, + "quarante" => 40, + "cinquante" => 50, + "soixante" => 60, + _ => return Err(RuleError::Invalid.into()), + }; + IntegerValue::new(value) + }); + b.rule_2("number (17..19)", + integer_check_by_range!(10, 10), + integer_check_by_range!(7, 9), + |_, b| IntegerValue::new(b.value().value + 10)); + b.rule_3("number (17..19)", + integer_check_by_range!(10, 10), + b.reg(r"-")?, + integer_check_by_range!(7, 9), + |_, _, b| IntegerValue::new(b.value().value + 10)); + b.rule_2_terminal("number 80", + b.reg(r#"quatre"#)?, + b.reg(r#"vingts?"#)?, + |_, _| IntegerValue::new(80)); + b.rule_3_terminal("number 80", + b.reg(r#"quatre"#)?, + b.reg(r"-")?, + b.reg(r#"vingts?"#)?, + |_, _, _| IntegerValue::new(80)); + b.rule_3("numbers 21 31 41 51", + integer_check_by_range!(20, 50, |integer: &IntegerValue| integer.value % 10 == 0), + b.reg(r#"-?et-?"#)?, + integer_check_by_range!(1, 1), + |a, _, b| IntegerValue::new(a.value().value + b.value().value)); + b.rule_2("numbers 22..29 32..39 .. 52..59", + integer_check_by_range!(20, 50, |integer: &IntegerValue| integer.value % 10 == 0), + integer_check_by_range!(2, 9), + |a, b| IntegerValue::new(a.value().value + b.value().value)); + b.rule_3("numbers 22..29 32..39 .. 52..59", + integer_check_by_range!(20, 50, |integer: &IntegerValue| integer.value % 10 == 0), + b.reg(r"-")?, + integer_check_by_range!(2, 9), + |a, _, b| IntegerValue::new(a.value().value + b.value().value)); + b.rule_3("numbers 61 71", + integer_check_by_range!(60, 60), + b.reg(r#"-?et-?"#)?, + integer_check_by_range!(1, + 11, + |integer: &IntegerValue| integer.value == 1 || integer.value == 11), + |a, _, b| IntegerValue::new(a.value().value + b.value().value)); + b.rule_2("numbers 81 91", + integer_check_by_range!(80, 80), + integer_check_by_range!(1, + 11, + |integer: &IntegerValue| integer.value == 1 || integer.value == 11), + |a, b| IntegerValue::new(a.value().value + b.value().value)); + b.rule_3("numbers 81 91", + integer_check_by_range!(80, 80), + b.reg(r#"-"#)?, + integer_check_by_range!(1, + 11, + |integer: &IntegerValue| integer.value == 1 || integer.value == 11), + |a, _, b| IntegerValue::new(a.value().value + b.value().value)); + b.rule_2("numbers 62..69 .. 92..99", + integer_check_by_range!(60, + 80, + |integer: &IntegerValue| integer.value == 60 || integer.value == 80), + integer_check_by_range!(2, 19), + |a, b| IntegerValue::new(a.value().value + b.value().value)); + b.rule_3("numbers 62..69 .. 92..99", + integer_check_by_range!(60, + 80, + |integer: &IntegerValue| integer.value == 60 || integer.value == 80), + b.reg(r"-")?, + integer_check_by_range!(2, 19), + |a, _, b| IntegerValue::new(a.value().value + b.value().value)); + b.rule_1_terminal("hundred", + b.reg(r#"cents?"#)?, + |_| IntegerValue::new_with_grain(100, 2) + ); + b.rule_1_terminal("thousand", + b.reg(r#"milles?"#)?, + |_| IntegerValue::new_with_grain(1000, 3) + ); + b.rule_1_terminal("million", + b.reg(r#"millions?"#)?, + |_| IntegerValue::new_with_grain(1000000, 6) + ); + b.rule_1_terminal("billion", + b.reg(r#"milliards?"#)?, + |_| IntegerValue::new_with_grain(1000000000, 9) + ); + b.rule_2("number hundreds", + integer_check_by_range!(1, 99), + b.reg(r#"cents?"#)?, + |a, _| { + Ok(IntegerValue { + value: a.value().value * 100, + grain: Some(2), + ..IntegerValue::default() + }) + }); + b.rule_2("number thousands", + integer_check_by_range!(1, 999), + b.reg(r#"milles?"#)?, + |a, _| { + Ok(IntegerValue { + value: a.value().value * 1000, + grain: Some(3), + ..IntegerValue::default() + }) + }); + b.rule_2("number millions", + integer_check_by_range!(1, 999), + b.reg(r#"millions?"#)?, + |a, _| { + Ok(IntegerValue { + value: a.value().value * 1000000, + grain: Some(6), + ..IntegerValue::default() + }) + }); + b.rule_2("number billions", + integer_check_by_range!(1, 999), + b.reg(r#"milliards?"#)?, + |a, _| { + Ok(IntegerValue { + value: a.value().value * 1000000000, + grain: Some(9), + ..IntegerValue::default() + }) + }); + b.rule_1_terminal("integer (numeric)", + b.reg(r#"(\d{1,18})"#)?, + |text_match| { + let value: i64 = text_match.group(1).parse()?; + IntegerValue::new(value) + }); + b.rule_1_terminal("integer with thousands separator .", + b.reg(r#"(\d{1,3}(\.\d\d\d){1,5})"#)?, + |text_match| { + let reformatted_string = text_match.group(1).replace(".", ""); + let value: i64 = reformatted_string.parse()?; + IntegerValue::new(value) + }); + b.rule_1_terminal("decimal number", + b.reg(r#"(\d*,\d+)"#)?, + |text_match| { + let reformatted_string = text_match.group(1).replace(",", "."); + let value: f32 = reformatted_string.parse()?; + FloatValue::new(value) + }); + b.rule_3("number dot number", + number_check!(|number: &NumberValue| !number.prefixed()), + b.reg(r#"virgule|point"#)?, + number_check!(|number: &NumberValue| !number.suffixed()), + |a, _, b| { + let power = b.value().value().to_string().chars().count(); + let coeff = 10.0_f32.powf(-1.0 * power as f32); + Ok(FloatValue { + value: b.value().value() * coeff + a.value().value(), + ..FloatValue::default() + }) + }); + b.rule_4("number dot zero ... number", + number_check!(|number: &NumberValue| !number.prefixed()), + b.reg(r#"virgule|point"#)?, + b.reg(r#"(?:(?:z[eé]ro )*(?:z[eé]ro))"#)?, + number_check!(|number: &NumberValue| !number.suffixed()), + |a, _, zeros, b| { + let power = zeros.group(0).split_whitespace().count() + b.value().value().to_string().chars().count(); + let coeff = 10.0_f32.powf(-1.0 * power as f32); + Ok(FloatValue { + value: b.value().value() * coeff + a.value().value(), + ..FloatValue::default() + }) + }); + b.rule_1_terminal("decimal with thousands separator", + b.reg(r#"(\d+(\.\d\d\d)+,\d+)"#)?, + |text_match| { + let reformatted_string = text_match.group(1).replace(".", "").replace(",", "."); + let value: f32 = reformatted_string.parse()?; + FloatValue::new(value) + }); + b.rule_2("numbers prefix with -, negative or minus", + b.reg(r#"-|moins"#)?, + number_check!(|number: &NumberValue| !number.prefixed()), + |_, a| -> RuleResult { + Ok(match a.value().clone() { + // checked + NumberValue::Integer(integer) => { + IntegerValue { + value: integer.value * -1, + prefixed: true, + ..integer + } + .into() + } + NumberValue::Float(float) => { + FloatValue { + value: float.value * -1.0, + prefixed: true, + ..float + } + .into() + } + }) + }); + b.rule_2("numbers suffixes (K, M, G)", + number_check!(|number: &NumberValue| !number.suffixed()), + b.reg_neg_lh(r#"([kmg])"#, r#"^[\W\$€]"#)?, + |a, text_match| -> RuleResult { + let multiplier = match text_match.group(0).as_ref() { + "k" => 1000, + "m" => 1000000, + "g" => 1000000000, + _ => return Err(RuleError::Invalid.into()), + }; + Ok(match a.value().clone() { // checked + NumberValue::Integer(integer) => { + IntegerValue { + value: integer.value * multiplier, + suffixed: true, + ..integer + } + .into() + } + NumberValue::Float(float) => { + let product = float.value * (multiplier as f32); + if product.floor() == product { + IntegerValue { + value: product as i64, + suffixed: true, + ..IntegerValue::default() + } + .into() + } else { + FloatValue { + value: product, + suffixed: true, + ..float + } + .into() + } + } + }) + }); + b.rule_1_terminal("(douzaine ... soixantaine)", + b.reg(r#"(demi[ -]douz|diz|douz|quinz|vingt|trent|quarant|cinquant|soixant|cent)aines?"#)?, + |text_match| { + let value = match text_match.group(1).as_ref() { + "demi douz" => 6, + "demi-douz" => 6, + "diz" => 10, + "douz" => 12, + "quinz" => 15, + "vingt" => 20, + "trent" => 30, + "quarant" => 40, + "cinquant" => 50, + "soixant" => 60, + "cent" => 100, + _ => return Err(RuleError::Invalid.into()), + }; + Ok(IntegerValue { + value, + group: true, + .. IntegerValue::default() + }) + } + ); + b.rule_2("number dozen", + integer_check_by_range!(1, 9), + integer_check!(|integer: &IntegerValue| integer.group), + |a, b| { + Ok(IntegerValue { + value: a.value().value * b.value().value, + grain: b.value().grain, + group: true, + ..IntegerValue::default() + }) + }); + b.rule_1_terminal("ordinal 0", + b.reg(r#"z[eé]rot?i[eè]me"#)?, + |_| { + Ok(OrdinalValue::new(0)) + } + ); + b.rule_1_terminal("ordinal 1", + b.reg(r#"premi[eè]re?"#)?, + |_| { + Ok(OrdinalValue::new(1)) + } + ); + b.rule_1_terminal("ordinal 2", + b.reg(r#"seconde?|deuxi[eè]me"#)?, + |_| { + Ok(OrdinalValue::new(2)) + } + ); + b.rule_1_terminal( + "ordinals (premier..seizieme)", + b.reg(r#"(trois|quatr|cinqu|six|sept|huit|neuv|dix|onz|douz|treiz|quatorz|quinz|seiz)i[eè]me"#)?, + |text_match| { + let value = match text_match.group(1).as_ref() { + "trois" => 3, + "quatr" => 4, + "cinqu" => 5, + "six" => 6, + "sept" => 7, + "huit" => 8, + "neuv" => 9, + "dix" => 10, + "onz" => 11, + "douz" => 12, + "treiz" => 13, + "quatorz" => 14, + "quinz" => 15, + "seiz" => 16, + _ => return Err(RuleError::Invalid.into()), + }; + Ok(OrdinalValue::new(value)) + }); + b.rule_2("17ieme, 18ieme, 19ieme", + b.reg(r#"dix-?"#)?, + ordinal_check_by_range!(7, 9), + |_, ordinal| { + Ok(OrdinalValue::new(10 + ordinal.value().value)) + } + ); + b.rule_1_terminal("20ieme, 30ieme, 40ieme, 50ieme, 60ieme", + b.reg(r#"(vingt|trent|quarant|cinquant|soixant)i[èe]me"#)?, + |text_match| { + let value = match text_match.group(1).as_ref() { + "vingt" => 20, + "trent" => 30, + "quarant" => 40, + "cinquant" => 50, + "soixant" => 60, + _ => return Err(RuleError::Invalid.into()), + }; + Ok(OrdinalValue::new(value)) + } + ); + b.rule_1_terminal("80ieme", + b.reg(r#"quatre[- ]vingts?i[èe]me"#)?, + |_| { + Ok(OrdinalValue::new(80)) + } + ); + b.rule_2("22ieme...29ieme, 32ieme...39ieme, 42ieme...49ieme, 52ieme...59ieme", + integer_check_by_range!(20, 50, |integer: &IntegerValue| integer.value % 10 == 0), + ordinal_check_by_range!(2, 9), + |integer, ordinal| { + Ok(OrdinalValue::new(integer.value().value + ordinal.value().value)) + } + ); + b.rule_3("22ieme...29ieme, 32ieme...39ieme, 42ieme...49ieme, 52ieme...59ieme", + integer_check_by_range!(20, 50, |integer: &IntegerValue| integer.value % 10 == 0), + b.reg(r"-")?, + ordinal_check_by_range!(2, 9), + |integer, _, ordinal| { + Ok(OrdinalValue::new(integer.value().value + ordinal.value().value)) + } + ); + b.rule_2("62ieme...70ieme, 72ieme...79ieme, 90ieme, 92ieme...99ieme", + integer_check_by_range!(60, 80, |integer: &IntegerValue| integer.value == 60 || integer.value == 80), + ordinal_check_by_range!(2, 19), + |integer, ordinal| { + Ok(OrdinalValue::new(integer.value().value + ordinal.value().value)) + } + ); + b.rule_3("62ieme...70ieme, 72ieme...79ieme, 90ieme, 92ieme...99ieme", + integer_check_by_range!(60, 80, |integer: &IntegerValue| integer.value == 60 || integer.value == 80), + b.reg(r"-")?, + ordinal_check_by_range!(2, 19), + |integer, _, ordinal| { + Ok(OrdinalValue::new(integer.value().value + ordinal.value().value)) + } + ); + b.rule_2("21, 31, 41, 51, 61", + integer_check_by_range!(20, 60, |integer: &IntegerValue| integer.value % 10 == 0), + b.reg(r#"(?:et |-)uni[èe]me"#)?, + |integer, _| { + Ok(OrdinalValue::new(integer.value().value + 1)) + } + ); + b.rule_2("81", + integer_check_by_range!(80, 80), + b.reg(r#"(?:et )?uni[èe]me"#)?, + |integer, _| { + Ok(OrdinalValue::new(integer.value().value + 1)) + } + ); + b.rule_2("71, 91", + integer_check_by_range!(60, 60), + b.reg(r#"et onzi[eè]me"#)?, + |integer, _| { + Ok(OrdinalValue::new(integer.value().value + 11)) + } + ); + b.rule_2(" et demi", + integer_check_by_range!(0, 99), + b.reg(r#"et demie?"#)?, + |integer, _| { + FloatValue::new(integer.value().value as f32 + 0.5) + } + ); + b.rule_1_terminal("70, 80, 90 (Belgium and Switzerland)", + b.reg(r#"(sept|huit|non)ante"#)?, + |text_match| { + let value = match text_match.group(1).as_ref() { + "sept" => 70, + "huit" => 80, + "non" => 90, + _ => return Err(RuleError::Invalid.into()), + }; + IntegerValue::new(value) + } + ); + b.rule_1_terminal("71, 81, 91 (Belgium and Switzerland)", + b.reg(r#"(sept|huit|non)ante et une?"#)?, + |text_match| { + let value = match text_match.group(1).as_ref() { + "sept" => 71, + "huit" => 81, + "non" => 91, + _ => return Err(RuleError::Invalid.into()), + }; + IntegerValue::new(value) + } + ); + + b.rule_2("72..79, 82..89, 92..99, (Belgium and Switzerland)", + b.reg(r#"(sept|huit|non)ante"#)?, + integer_check_by_range!(2, 9), + |text_match, integer| { + let value = match text_match.group(1).as_ref() { + "sept" => 70, + "huit" => 80, + "non" => 90, + _ => return Err(RuleError::Invalid.into()), + }; + IntegerValue::new(value + integer.value().value) + } + ); + b.rule_1_terminal("ordinal (100, 1_000, 1_000_000)", + b.reg(r#"(cent|mill|million|milliard)i[èe]me"#)?, + |text_match| { + let (value, grain) = match text_match.group(1).as_ref() { + "cent" => (100, 2), + "mill" => (1_000, 3), + "million" => (1_000_000, 6), + "milliard" => (1_000_000_000, 9), + _ => return Err(RuleError::Invalid.into()), + }; + Ok(OrdinalValue::new_with_grain(value, grain)) + } + ); + + b.rule_2("ordinal (200..900, 2_000..9_000, 2_000_000..9_000_000_000)", + integer_check_by_range!(2, 999), + b.reg(r#"(cent|mill|million|milliard)i[èe]me"#)?, + |integer, text_match| { + let (value, grain) = match text_match.group(1).as_ref() { + "cent" => (100, 2), + "mill" => (1_000, 3), + "million" => (1_000_000, 6), + "milliard" => (1_000_000_000, 9), + _ => return Err(RuleError::Invalid.into()), + }; + Ok(OrdinalValue::new_with_grain(integer.value().value * value, grain)) + } + ); + + b.rule_2("ordinal (1_1_000..9_999_999_000)", + integer_check_by_range!(1000, 99_999_999_000), + ordinal_check!(|ordinal: &OrdinalValue| { + let grain = ordinal.grain.unwrap_or(0); + grain == 2 || grain % 3 == 0 + }), + |integer, ordinal| { + let grain = ordinal.value().grain.unwrap_or(0); + let next_grain = (grain / 3) * 3 + 3; + if integer.value().value % 10i64.pow(next_grain as u32) != 0 { return Err(RuleError::Invalid.into()); } + Ok(OrdinalValue::new(integer.value().value + ordinal.value().value)) + } + ); + + b.rule_2("ordinal (102...9_999_999)", + integer_check!(|integer: &IntegerValue| integer.value >= 100 || integer.value % 100 == 0), + ordinal_check_by_range!(2, 99), + |integer, ordinal| { + Ok(OrdinalValue::new(integer.value().value + ordinal.value().value)) + } + ); + b.rule_2("ordinal (101, 201, 301, ...)", + integer_check!(|integer: &IntegerValue| integer.value >= 100 || integer.value % 100 == 0), + b.reg(r#"(?:et |-)?uni[èe]me"#)?, + |integer, _| { + Ok(OrdinalValue::new(integer.value().value + 1)) + } + ); + b.rule_1_terminal("ordinal (digits)", + b.reg(r#"0*(\d+) ?(ere?|ère|ème|eme|ieme|ième)"#)?, + |text_match| { + let value: i64 = text_match.group(1).parse()?; + Ok(OrdinalValue::new(value)) + }); + b.rule_2("le ", + b.reg(r#"l[ea]"#)?, + ordinal_check!(), + |_, a| Ok((*a.value()).prefixed()) + ); + Ok(()) +} \ No newline at end of file From a9e3a5ea394597f0f5feef12503573e3bb36de01 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Thu, 20 Jun 2019 18:28:04 +0200 Subject: [PATCH 030/107] =?UTF-8?q?Add=20support=20for=20mid/end/beg=20sea?= =?UTF-8?q?son,=20mi-journ=C3=A9e,=20prep=20+=20part-of-day.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- grammar/fr/src/rules_datetime.rs | 62 ++++++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 93fb4ac5..987049c0 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -428,6 +428,11 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { datetime_check!(form!(Form::TimeOfDay(_))), |_, a| Ok(a.value().clone().not_latent()) ); + b.rule_2("à ", + b.reg(r#"[aà]|pour"#)?, + datetime_check!(form!(Form::PartOfDay(_))), + |_, a| Ok(a.value().clone().not_latent()) + ); b.rule_2("vers ", b.reg(r#"(?:plut[ôo]t )?(?:vers|autour de|[aà] environ|aux alentours de)"#)?, datetime_check!(form!(Form::TimeOfDay(_))), @@ -652,7 +657,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { } ); b.rule_1_terminal("milieu de journée", - b.reg(r#"milieu de (?:la )?journ[ée]e"#)?, + b.reg(r#"(?:milieu de (?:la )?|(?:(?:[àa] )?la )?mi[ -])journ[ée]e"#)?, |_| { Ok(helpers::hour(11, false)? .span_to(&helpers::hour(16, false)?, false)? @@ -826,6 +831,54 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.reg(r#"(?:ce )?printemps"#)?, |_| helpers::month_day(3, 20)?.span_to(&helpers::month_day(6, 21)?, false) ); + b.rule_1_terminal("début de l'été", + b.reg(r#"début de (?:cet |l')?(?:été|ete)"#)?, + |_| helpers::month_day(6, 21)?.span_to(&helpers::month_day(7, 15)?, false) + ); + b.rule_1_terminal("milieu de l'été", + b.reg(r#"milieu de (?:cet |l')?(?:été|ete)"#)?, + |_| helpers::month_day(7, 15)?.span_to(&helpers::month_day(8, 15)?, false) + ); + b.rule_1_terminal("fin de l'été", + b.reg(r#"fin de (?:cet |l')?(?:été|ete)"#)?, + |_| helpers::month_day(8, 15)?.span_to(&helpers::month_day(9, 21)?, false) + ); + b.rule_1_terminal("début de l'automne", + b.reg(r#"début de (?:cet |l')?(?:été|ete)"#)?, + |_| helpers::month_day(9, 21)?.span_to(&helpers::month_day(10, 15)?, false) + ); + b.rule_1_terminal("milieu de l'automne", + b.reg(r#"milieu de (?:cet |l')?(?:été|ete)"#)?, + |_| helpers::month_day(10, 15)?.span_to(&helpers::month_day(11, 15)?, false) + ); + b.rule_1_terminal("fin de l'automne", + b.reg(r#"fin de (?:cet |l')?(?:été|ete)"#)?, + |_| helpers::month_day(11, 15)?.span_to(&helpers::month_day(12, 21)?, false) + ); + b.rule_1_terminal("début de l'hiver", + b.reg(r#"début de (?:cet |l')?(?:été|ete)"#)?, + |_| helpers::month_day(12, 21)?.span_to(&helpers::month_day(1, 15)?, false) + ); + b.rule_1_terminal("milieu de l'hiver", + b.reg(r#"milieu de (?:cet |l')?(?:été|ete)"#)?, + |_| helpers::month_day(1, 15)?.span_to(&helpers::month_day(2, 15)?, false) + ); + b.rule_1_terminal("fin de l'hiver", + b.reg(r#"fin de (?:cet |l')?(?:été|ete)"#)?, + |_| helpers::month_day(2, 15)?.span_to(&helpers::month_day(3, 21)?, false) + ); + b.rule_1_terminal("début du printemps", + b.reg(r#"début de (?:cet |l')?(?:été|ete)"#)?, + |_| helpers::month_day(3, 21)?.span_to(&helpers::month_day(4, 15)?, false) + ); + b.rule_1_terminal("milieu du printemps", + b.reg(r#"milieu de (?:cet |l')?(?:été|ete)"#)?, + |_| helpers::month_day(4, 15)?.span_to(&helpers::month_day(5, 15)?, false) + ); + b.rule_1_terminal("fin du printemps", + b.reg(r#"fin de (?:cet |l')?(?:été|ete)"#)?, + |_| helpers::month_day(5, 15)?.span_to(&helpers::month_day(6, 21)?, false) + ); b.rule_2("le ", b.reg(r#"l[ea]"#)?, datetime_check!(|datetime: &DatetimeValue| !datetime.latent), @@ -1072,6 +1125,11 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { datetime_check!(|datetime: &DatetimeValue| !datetime.latent), |_, datetime| Ok(datetime.value().clone().mark_before_start()) ); + b.rule_2("avant ", + b.reg(r#"(?:n[ ']importe quand )?(avant|jusqu'(?:au|[aà]|en))"#)?, + datetime_check!(form!(Form::PartOfDay(_))), + |_, datetime| Ok(datetime.value().clone().mark_before_start()) + ); b.rule_2("après ", b.reg(r#"apr[eè]s"#)?, datetime_check!(|datetime: &DatetimeValue| !datetime.latent), @@ -1083,7 +1141,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { |_, datetime| Ok(datetime.value().clone().mark_after_start()) ); b.rule_2("à partir de ", - b.reg(r#"[aà] partir d['eu](?:l[ 'a])?"#)?, + b.reg(r#"[aà] partir d['eu](?:l[ 'a])?|après"#)?, datetime_check!(form!(Form::PartOfDay(_))), |_, datetime| Ok(datetime.value().clone().mark_after_start()) ); From b1bf9ad2d62d1ca8b4f4a6ae5ebc8179c03c5d70 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Thu, 18 Jul 2019 11:59:22 +0200 Subject: [PATCH 031/107] Add support for POD 'sunrise', 'middle of the night', and changed hour range for mid-day. --- grammar/fr/src/rules_datetime.rs | 20 ++++++++++++++++++-- grammar/fr/src/training.rs | 2 +- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 987049c0..13650182 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -517,6 +517,13 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { .latent() .form(Form::PartOfDay(PartOfDayForm::Morning))) ); + b.rule_1_terminal("lever du soleil", + b.reg(r#"lever du soleil|aurore|aube"#)?, + |_| Ok(helpers::hour(5, false)? + .span_to(&helpers::hour(9, false)?, false)? + .latent() + .form(Form::PartOfDay(PartOfDayForm::Morning))) + ); b.rule_1_terminal("petit dejeuner", b.reg(r#"petit[- ]d[ée]jeuner"#)?, |_| Ok(helpers::hour(5, false)? @@ -525,7 +532,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { .form(Form::Meal)) ); b.rule_1_terminal("milieu de matinée", - b.reg(r#"milieu de matin[ée]e"#)?, + b.reg(r#"(?:le )?milieu de matin[ée]e"#)?, |_| Ok(helpers::hour(9, false)? .span_to(&helpers::hour(11, false)?, false)? .latent() @@ -659,7 +666,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.rule_1_terminal("milieu de journée", b.reg(r#"(?:milieu de (?:la )?|(?:(?:[àa] )?la )?mi[ -])journ[ée]e"#)?, |_| { - Ok(helpers::hour(11, false)? + Ok(helpers::hour(12, false)? .span_to(&helpers::hour(16, false)?, false)? .latent() .form(Form::PartOfDay(PartOfDayForm::None))) @@ -716,6 +723,15 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { .form(Form::PartOfDay(PartOfDayForm::Night))) } ); + b.rule_1_terminal("milieu de la nuit", + b.reg(r#"(?:le )?milieu de la nuit"#)?, + |_| { + Ok(helpers::hour(2, false)? + .span_to(&helpers::hour(4, false)?, false)? + .latent() + .form(Form::PartOfDay(PartOfDayForm::Night))) + } + ); b.rule_2("a l'heure de ", b.reg(r#"(?:[àa] )?l[' ]heure du|au moment du|pendant l[ea']|au|pour l[ea']|l[ea']"#)?, datetime_check!(|datetime: &DatetimeValue| form!(Form::Meal)(datetime)), diff --git a/grammar/fr/src/training.rs b/grammar/fr/src/training.rs index 5b2e435b..036a392f 100644 --- a/grammar/fr/src/training.rs +++ b/grammar/fr/src/training.rs @@ -174,7 +174,7 @@ pub fn examples_datetime(v: &mut Vec<::rustling::train::Example>) { example!(v, check_moment_span!(c, [2013, 2, 12, 12], [2013, 2, 12, 15]), "en début d'après-midi", "en début d'aprem"); example!(v, check_moment_span!(c, [2013, 2, 12, 17], [2013, 2, 12, 19]), "en fin d'après-midi", "en fin d'aprem"); example!(v, check_moment_span!(c, [2013, 2, 12, 6], [2013, 2, 12, 10]), "en début de journée"); - example!(v, check_moment_span!(c, [2013, 2, 12, 11], [2013, 2, 12, 16]), "milieu de journée"); + example!(v, check_moment_span!(c, [2013, 2, 12, 12], [2013, 2, 12, 16]), "milieu de journée"); example!(v, check_moment_span!(c, [2013, 2, 12, 17], [2013, 2, 12, 21]), "en fin de journée"); example!(v, check_moment_span!(c, [2013, 2, 12, 18], [2013, 2, 13, 00]), "ce soir"); example!(v, check_moment_span!(c, [2013, 2, 12, 18], [2013, 2, 12, 21]), "en début de soirée"); From ca08f892fc951567ac96d17cc81148bc832e72dc Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Thu, 18 Jul 2019 14:31:02 +0200 Subject: [PATCH 032/107] Add support for POD 'sunset'. --- grammar/fr/src/rules_datetime.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 13650182..fe994eda 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -690,6 +690,15 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { .form(Form::PartOfDay(PartOfDayForm::Evening))) } ); + b.rule_1_terminal("coucher du soleil", + b.reg(r#"coucher du soleil|cr[eé]puscule|tomb[ée]e de la nuit"#)?, + |_| { + Ok(helpers::hour(18, false)? + .span_to(&helpers::hour(21, false)?, false)? + .latent() + .form(Form::PartOfDay(PartOfDayForm::Evening))) + } + ); b.rule_1_terminal("début de soirée", b.reg(r#"d[ée]but de (?:la )?soir[ée]e?"#)?, |_| { @@ -724,7 +733,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { } ); b.rule_1_terminal("milieu de la nuit", - b.reg(r#"(?:le )?milieu de la nuit"#)?, + b.reg(r#"milieu de la nuit"#)?, |_| { Ok(helpers::hour(2, false)? .span_to(&helpers::hour(4, false)?, false)? From 4eb8f375e0d5fb8774578c8910f58af975da3883 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Thu, 18 Jul 2019 15:17:04 +0200 Subject: [PATCH 033/107] Corrected rule 'lever du soleil', Changed hour range 'aube'. --- grammar/fr/src/rules_datetime.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index fe994eda..6d735903 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -518,9 +518,9 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { .form(Form::PartOfDay(PartOfDayForm::Morning))) ); b.rule_1_terminal("lever du soleil", - b.reg(r#"lever du soleil|aurore|aube"#)?, - |_| Ok(helpers::hour(5, false)? - .span_to(&helpers::hour(9, false)?, false)? + b.reg(r#"lever d[ue] soleil|aurore|aube"#)?, + |_| Ok(helpers::hour(4, false)? + .span_to(&helpers::hour(8, false)?, false)? .latent() .form(Form::PartOfDay(PartOfDayForm::Morning))) ); @@ -693,8 +693,8 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.rule_1_terminal("coucher du soleil", b.reg(r#"coucher du soleil|cr[eé]puscule|tomb[ée]e de la nuit"#)?, |_| { - Ok(helpers::hour(18, false)? - .span_to(&helpers::hour(21, false)?, false)? + Ok(helpers::hour(19, false)? + .span_to(&helpers::hour(22, false)?, false)? .latent() .form(Form::PartOfDay(PartOfDayForm::Evening))) } @@ -1166,10 +1166,15 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { |_, datetime| Ok(datetime.value().clone().mark_after_start()) ); b.rule_2("à partir de ", - b.reg(r#"[aà] partir d['eu](?:l[ 'a])?|après"#)?, + b.reg(r#"[aà] partir d['eu](?:l[ 'a])?"#)?, datetime_check!(form!(Form::PartOfDay(_))), |_, datetime| Ok(datetime.value().clone().mark_after_start()) ); + b.rule_2("après ", + b.reg(r#"après"#)?, + datetime_check!(form!(Form::PartOfDay(_))), + |_, datetime| Ok(datetime.value().clone().mark_after_end()) + ); b.rule_2("après le ", b.reg(r#"apr(?:e|è)s le"#)?, integer_check_by_range!(1, 31), From 096a1250371ca7e56f818c6279e2475034818b8e Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Thu, 18 Jul 2019 15:52:15 +0200 Subject: [PATCH 034/107] =?UTF-8?q?Added=20rule=20from=20=20t?= =?UTF-8?q?o=20.=20Added=20article=20in=20'fin=20de=20la=20ma?= =?UTF-8?q?tin=C3=A9e'.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- grammar/fr/src/rules_datetime.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 6d735903..09de62fd 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -546,7 +546,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { .form(Form::Meal)) ); b.rule_1_terminal("fin de matinée", - b.reg(r#"fin de matin[ée]e"#)?, + b.reg(r#"fin de (?:la )?matin[ée]e"#)?, |_| Ok(helpers::hour(10, false)? .span_to(&helpers::hour(12, false)?, false)? .latent() @@ -1119,6 +1119,22 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { datetime_check!(form!(Form::TimeOfDay(_))), |_, a, _, b| a.value().smart_span_to(b.value(), false) ); + b.rule_4("de - (interval)", + b.reg(r#"(?:[aà] partir )?d['eu]"#)?, + datetime_check!(form!(Form::PartOfDay(_))), + b.reg(r#"(?:jusqu')?(?:au|[aà])"#)?, + datetime_check!(form!(Form::PartOfDay(_))), + //datetime.value().clone().mark_before_start() + |_, a, _, b| a.value().smart_span_to(b.value(), false) + ); + b.rule_4("de - (interval)", + b.reg(r#"(?:[aà] partir )?d['e]"#)?, + datetime_check!(form!(Form::Meal)), + b.reg(r#"(?:jusqu')?(?:au|[aà])"#)?, + datetime_check!(form!(Form::Meal)), + //datetime.value().clone().mark_before_start() + |_, a, _, b| a.value().smart_span_to(b.value(), false) + ); b.rule_2("de maintenant - (interval)", b.reg(r#"(?:[aà] partir )?de maintenant (?:jusqu')?(?:au|[aà])"#)?, datetime_check!(form!(Form::TimeOfDay(_))), From 56b96c84dd885bd0756f7bb3bf28d8b3749ff168 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Thu, 18 Jul 2019 17:03:26 +0200 Subject: [PATCH 035/107] Add support for 'last '. --- grammar/fr/src/rules_datetime.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 09de62fd..3a02bd60 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -176,10 +176,15 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { |datetime, _| datetime.value().the_nth_not_immediate(0) ); b.rule_2("au prochain ", - b.reg(r#"(au|[aà] la) prochaine?"#)?, + b.reg(r#"(au |(?:[aà] )?la )prochaine?"#)?, datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), |_, datetime| datetime.value().the_nth_not_immediate(0) ); + b.rule_2("au dernier ", + b.reg(r#"(au |(?:[aà] )?la )dernier?"#)?, + datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), + |_, datetime| datetime.value().the_nth_not_immediate(-1) + ); b.rule_2(" prochain", // The direction check is to avoid application of datetime_check(month) on rule result // "avant " From cd74f45fae10796f083ee130c7c9d3d0298ea8db Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Thu, 18 Jul 2019 17:16:37 +0200 Subject: [PATCH 036/107] Correction rule 'au (prochain|dernier) '. --- grammar/fr/src/rules_datetime.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 3a02bd60..899130e3 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -176,12 +176,12 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { |datetime, _| datetime.value().the_nth_not_immediate(0) ); b.rule_2("au prochain ", - b.reg(r#"(au |(?:[aà] )?la )prochaine?"#)?, + b.reg(r#"(au |(?:[aà] )?l[ae] )prochaine?"#)?, datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), |_, datetime| datetime.value().the_nth_not_immediate(0) ); b.rule_2("au dernier ", - b.reg(r#"(au |(?:[aà] )?la )dernier?"#)?, + b.reg(r#"(au |(?:[aà] )?l[ea] )derni[eè]re?"#)?, datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), |_, datetime| datetime.value().the_nth_not_immediate(-1) ); From 505981834c2ed9e5b13b5ff9c987916c020d4a3c Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Thu, 18 Jul 2019 17:58:19 +0200 Subject: [PATCH 037/107] Remove useless comments. --- grammar/fr/src/rules_datetime.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 899130e3..ae504c10 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -1129,7 +1129,6 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { datetime_check!(form!(Form::PartOfDay(_))), b.reg(r#"(?:jusqu')?(?:au|[aà])"#)?, datetime_check!(form!(Form::PartOfDay(_))), - //datetime.value().clone().mark_before_start() |_, a, _, b| a.value().smart_span_to(b.value(), false) ); b.rule_4("de - (interval)", @@ -1137,7 +1136,6 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { datetime_check!(form!(Form::Meal)), b.reg(r#"(?:jusqu')?(?:au|[aà])"#)?, datetime_check!(form!(Form::Meal)), - //datetime.value().clone().mark_before_start() |_, a, _, b| a.value().smart_span_to(b.value(), false) ); b.rule_2("de maintenant - (interval)", From 0638c2a73b753eafa540ec5f584f5bc097dc0320 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Mon, 22 Jul 2019 11:11:41 +0200 Subject: [PATCH 038/107] Correction rules 'au dernier ', 'au prochain >datetime>'. --- grammar/fr/src/rules_datetime.rs | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index ae504c10..98123547 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -175,15 +175,25 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.reg(r#"prochaine?"#)?, |datetime, _| datetime.value().the_nth_not_immediate(0) ); - b.rule_2("au prochain ", + // TODO: add restrictions on datetime form? + b.rule_2("au prochain ", b.reg(r#"(au |(?:[aà] )?l[ae] )prochaine?"#)?, - datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), - |_, datetime| datetime.value().the_nth_not_immediate(0) + datetime_check!(|datetime: &DatetimeValue| !form!(Form::PartOfDay(_))(datetime) && !form!(Form::Meal)(datetime)), + |_, a| { + Ok(a.value().the_nth(0)? + .form(a.value().form.clone()) + .datetime_kind(a.value().datetime_kind.clone())) + } ); - b.rule_2("au dernier ", + // TODO: add restrictions on datetime form? + b.rule_2("au dernier ", b.reg(r#"(au |(?:[aà] )?l[ea] )derni[eè]re?"#)?, - datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), - |_, datetime| datetime.value().the_nth_not_immediate(-1) + datetime_check!(|datetime: &DatetimeValue| !form!(Form::PartOfDay(_))(datetime) && !form!(Form::Meal)(datetime)), + |_, a| { + Ok(a.value().the_nth(-1)? + .form(a.value().form.clone()) + .datetime_kind(a.value().datetime_kind.clone())) + } ); b.rule_2(" prochain", // The direction check is to avoid application of datetime_check(month) on rule result From 424e453476c937f80f6dc5f841154c5f5dc543b2 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Mon, 22 Jul 2019 16:02:09 +0200 Subject: [PATCH 039/107] =?UTF-8?q?Add=20support=20for=20'fin=20de=20cette?= =?UTF-8?q?=20ann=C3=A9e'.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- grammar/fr/src/rules_datetime.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 98123547..a9f6202f 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -919,6 +919,15 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.reg(r#"fin de (?:cet |l')?(?:été|ete)"#)?, |_| helpers::month_day(5, 15)?.span_to(&helpers::month_day(6, 21)?, false) ); + b.rule_1_terminal("fin de cette année", + b.reg(r#"fin (?:de (?:l'|cette )|d')?ann[ée]e"#)?, + |_| { + let current_year = helpers::cycle_nth(Grain::Year, 0)?; + let start = current_year.intersect(&helpers::month(10)?)?; + let end = current_year.intersect(&helpers::month(12)?)?; + start.span_to(&end, true) + } + ); b.rule_2("le ", b.reg(r#"l[ea]"#)?, datetime_check!(|datetime: &DatetimeValue| !datetime.latent), From 7ae21db97887048400cc8c0966994041c0404140 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Mon, 22 Jul 2019 16:22:52 +0200 Subject: [PATCH 040/107] Corrections rules 'end/mid/beg of season'. --- grammar/fr/src/rules_datetime.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index a9f6202f..de31f1d2 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -884,39 +884,39 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { |_| helpers::month_day(8, 15)?.span_to(&helpers::month_day(9, 21)?, false) ); b.rule_1_terminal("début de l'automne", - b.reg(r#"début de (?:cet |l')?(?:été|ete)"#)?, + b.reg(r#"début de (?:cet |l')?automne"#)?, |_| helpers::month_day(9, 21)?.span_to(&helpers::month_day(10, 15)?, false) ); b.rule_1_terminal("milieu de l'automne", - b.reg(r#"milieu de (?:cet |l')?(?:été|ete)"#)?, + b.reg(r#"milieu de (?:cet |l')?automne"#)?, |_| helpers::month_day(10, 15)?.span_to(&helpers::month_day(11, 15)?, false) ); b.rule_1_terminal("fin de l'automne", - b.reg(r#"fin de (?:cet |l')?(?:été|ete)"#)?, + b.reg(r#"fin de (?:cet |l')?automne"#)?, |_| helpers::month_day(11, 15)?.span_to(&helpers::month_day(12, 21)?, false) ); b.rule_1_terminal("début de l'hiver", - b.reg(r#"début de (?:cet |l')?(?:été|ete)"#)?, + b.reg(r#"début de (?:cet |l')?hiver"#)?, |_| helpers::month_day(12, 21)?.span_to(&helpers::month_day(1, 15)?, false) ); b.rule_1_terminal("milieu de l'hiver", - b.reg(r#"milieu de (?:cet |l')?(?:été|ete)"#)?, + b.reg(r#"milieu de (?:cet |l')?hiver"#)?, |_| helpers::month_day(1, 15)?.span_to(&helpers::month_day(2, 15)?, false) ); b.rule_1_terminal("fin de l'hiver", - b.reg(r#"fin de (?:cet |l')?(?:été|ete)"#)?, + b.reg(r#"fin de (?:cet |l')?hiver"#)?, |_| helpers::month_day(2, 15)?.span_to(&helpers::month_day(3, 21)?, false) ); b.rule_1_terminal("début du printemps", - b.reg(r#"début de (?:cet |l')?(?:été|ete)"#)?, + b.reg(r#"début (?:du|de ce)? printemps"#)?, |_| helpers::month_day(3, 21)?.span_to(&helpers::month_day(4, 15)?, false) ); b.rule_1_terminal("milieu du printemps", - b.reg(r#"milieu de (?:cet |l')?(?:été|ete)"#)?, + b.reg(r#"milieu (?:du|de ce)? printemps"#)?, |_| helpers::month_day(4, 15)?.span_to(&helpers::month_day(5, 15)?, false) ); b.rule_1_terminal("fin du printemps", - b.reg(r#"fin de (?:cet |l')?(?:été|ete)"#)?, + b.reg(r#"fin (?:du|de ce)? printemps"#)?, |_| helpers::month_day(5, 15)?.span_to(&helpers::month_day(6, 21)?, false) ); b.rule_1_terminal("fin de cette année", From 074886fd668077dca76f35cdbe38d4418a7c8127 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Mon, 22 Jul 2019 16:39:48 +0200 Subject: [PATCH 041/107] Enlarge coverage rule 'fin du mois'. --- grammar/fr/src/rules_datetime.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index de31f1d2..bb0e323f 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -127,7 +127,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { |_| helpers::cycle_nth(Grain::Day, -1) ); b.rule_1_terminal("fin du mois", - b.reg(r#"(?:(?:[aà] )?la )?fin (?:du|de) mois"#)?, + b.reg(r#"(?:(?:(?:[aà] )?la|en)? )?fin (?:du|de) mois"#)?, |_| { let month = helpers::cycle_nth(Grain::Month, 1)?; Ok(helpers::cycle_nth_after(Grain::Day, -10, &month)? From 730412558d89bb8d9e22af9ed6d3ba69b26258e2 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Mon, 22 Jul 2019 17:38:53 +0200 Subject: [PATCH 042/107] =?UTF-8?q?Add=20support=20for=20'd=C3=A9but=20de?= =?UTF-8?q?=20l'ann=C3=A9e'.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- grammar/fr/src/rules_datetime.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index bb0e323f..2446330c 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -928,8 +928,18 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { start.span_to(&end, true) } ); + b.rule_1_terminal("début de cette année", + b.reg(r#"d[ée]but (?:de (?:l'|cette )|d')?ann[ée]e"#)?, + |_| { + let current_year = helpers::cycle_nth(Grain::Year, 0)?; + let start = current_year.intersect(&helpers::month(1)?)?; + let end = current_year.intersect(&helpers::month(2)?)?; + start.span_to(&end, true) + } + ); b.rule_2("le ", b.reg(r#"l[ea]"#)?, + //b.reg(r#"l[ea]|en|au|à|pour"#)?, datetime_check!(|datetime: &DatetimeValue| !datetime.latent), |_, a| Ok(a.value().clone()) ); From 81a604d2f41404895e8826cf82e73088bfc4beab Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Mon, 22 Jul 2019 17:51:23 +0200 Subject: [PATCH 043/107] =?UTF-8?q?Add=20prep=20+=20datetime=20'a|en|au|?= =?UTF-8?q?=C3=A0|pour'.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- grammar/fr/src/rules_datetime.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 2446330c..05fca4ab 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -938,8 +938,8 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { } ); b.rule_2("le ", - b.reg(r#"l[ea]"#)?, - //b.reg(r#"l[ea]|en|au|à|pour"#)?, + //b.reg(r#"l[ea]"#)?, + b.reg(r#"l[ea]|en|au|à|pour"#)?, datetime_check!(|datetime: &DatetimeValue| !datetime.latent), |_, a| Ok(a.value().clone()) ); From 2faa90e88a0461bed15c545f412a994d641b3388 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Tue, 23 Jul 2019 11:40:17 +0200 Subject: [PATCH 044/107] =?UTF-8?q?Add=20preposition=20'au'=20in=20rule=20?= =?UTF-8?q?'go=C3=BBter'.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- grammar/fr/src/rules_datetime.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 05fca4ab..7c8746f3 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -10,6 +10,11 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { datetime_check!(|datetime: &DatetimeValue| !datetime.latent), |a, b| a.value().intersect(b.value()) ); + b.rule_2("intersect + ", + datetime_check!(form!(Form::DayOfWeek{..})), + datetime_check!(|datetime: &DatetimeValue| !form!(Form::PartOfDay(_))(datetime) && !form!(Form::Meal)(datetime)), + |a, b| a.value().intersect(b.value()) + ); b.rule_3("intersect by 'de' or ','", datetime_check!(|datetime: &DatetimeValue| !datetime.latent), b.reg(r#"de|,"#)?, @@ -642,7 +647,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { } ); b.rule_1_terminal("gouter", - b.reg(r#"(?:(?:[àa] )?l[' ]heure du|au moment du|pendant le|pour le) go[uû]ter"#)?, + b.reg(r#"(?:(?:[àa] )?l[' ]heure du|au(?: moment du)?|pendant le|pour le) go[uû]ter"#)?, |_| Ok(helpers::hour(16, false)? .span_to(&helpers::hour(18, false)?, false)? .form(Form::Meal)) From dd45d7e49b0e9b36b8f11a58c673b72f05786ef5 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Tue, 23 Jul 2019 11:43:58 +0200 Subject: [PATCH 045/107] Delete redundant rules. One rule for intersection . --- grammar/fr/src/rules_datetime.rs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 7c8746f3..e1b47ae1 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -10,11 +10,6 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { datetime_check!(|datetime: &DatetimeValue| !datetime.latent), |a, b| a.value().intersect(b.value()) ); - b.rule_2("intersect + ", - datetime_check!(form!(Form::DayOfWeek{..})), - datetime_check!(|datetime: &DatetimeValue| !form!(Form::PartOfDay(_))(datetime) && !form!(Form::Meal)(datetime)), - |a, b| a.value().intersect(b.value()) - ); b.rule_3("intersect by 'de' or ','", datetime_check!(|datetime: &DatetimeValue| !datetime.latent), b.reg(r#"de|,"#)?, @@ -766,11 +761,6 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { datetime_check!(|datetime: &DatetimeValue| form!(Form::Meal)(datetime)), |_, a| Ok(a.value().clone().not_latent()) ); - b.rule_2(" ", - datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), - datetime_check!(form!(Form::Meal)), - |a, b| a.value().intersect(b.value()) - ); b.rule_2("prep? & article ", // This is very catch-all/junky b.reg(r#"(?:pendant |durant |dans |d[eè]s )?l[ae']?|en|au"#)?, datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime)), @@ -784,7 +774,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { .form(datetime.value().form.clone()) .datetime_kind(DatetimeKind::DatetimeComplement { date_and_time: true, today: true })) ); - b.rule_2(" ", + b.rule_2("intersect ", datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), |a, b| a.value().intersect(b.value()) From c8da4530c09ef1858826f3283ce721faf5839926 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Tue, 23 Jul 2019 15:01:40 +0200 Subject: [PATCH 046/107] =?UTF-8?q?Add=20rule=20interval=20=20-=20=20to=20support=20'en?= =?UTF-8?q?tre=204H=20et=20le=20d=C3=AEner'=20for=20example.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- grammar/fr/src/rules_datetime.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index e1b47ae1..db8e5cb6 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -1118,6 +1118,34 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), |_, a, _, b| a.value().span_to(b.value(), true) ); + b.rule_4("entre et (interval)", + b.reg(r#"entre"#)?, + datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), + b.reg(r#"et"#)?, + datetime_check!(|datetime: &DatetimeValue| form!(Form::TimeOfDay(_))(datetime)), + |_, a, _, b| a.value().span_to(b.value(), true) + ); + b.rule_4("entre et (interval)", + b.reg(r#"d[eu]"#)?, + datetime_check!(|datetime: &DatetimeValue| form!(Form::TimeOfDay(_))(datetime)), + b.reg(r#"(?:jusqu')?(?:à|au)"#)?, + datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), + |_, a, _, b| a.value().span_to(b.value(), true) + ); + b.rule_4("entre et (interval)", + b.reg(r#"d[eu]"#)?, + datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), + b.reg(r#"(?:jusqu')?(?:à|au)"#)?, + datetime_check!(|datetime: &DatetimeValue| form!(Form::TimeOfDay(_))(datetime)), + |_, a, _, b| a.value().span_to(b.value(), true) + ); + b.rule_4("entre et (interval)", + b.reg(r#"entre"#)?, + datetime_check!(|datetime: &DatetimeValue| form!(Form::TimeOfDay(_))(datetime)), + b.reg(r#"et"#)?, + datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), + |_, a, _, b| a.value().span_to(b.value(), true) + ); // Specific case with years b.rule_5("de - (interval)", b.reg(r#"depuis|d[e'u]?"#)?, From 8fb2ff8241a624f48eaa91759ec5b2875a090795 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Tue, 23 Jul 2019 15:14:53 +0200 Subject: [PATCH 047/107] =?UTF-8?q?Correction=20rule=20'entre=20?= =?UTF-8?q?=20et=20'=20to=20support=20'=C3=A0=20partir=20de'.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- grammar/fr/src/rules_datetime.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index db8e5cb6..31f33370 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -706,7 +706,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { } ); b.rule_1_terminal("coucher du soleil", - b.reg(r#"coucher du soleil|cr[eé]puscule|tomb[ée]e de la nuit"#)?, + b.reg(r#"coucher d[eu] soleil|cr[eé]puscule|tomb[ée]e de la nuit"#)?, |_| { Ok(helpers::hour(19, false)? .span_to(&helpers::hour(22, false)?, false)? @@ -1126,14 +1126,14 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { |_, a, _, b| a.value().span_to(b.value(), true) ); b.rule_4("entre et (interval)", - b.reg(r#"d[eu]"#)?, + b.reg(r#"(?:[aà] partir )?d[eu]"#)?, datetime_check!(|datetime: &DatetimeValue| form!(Form::TimeOfDay(_))(datetime)), b.reg(r#"(?:jusqu')?(?:à|au)"#)?, datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), |_, a, _, b| a.value().span_to(b.value(), true) ); b.rule_4("entre et (interval)", - b.reg(r#"d[eu]"#)?, + b.reg(r#"(?:[aà] partir )?d[eu]"#)?, datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), b.reg(r#"(?:jusqu')?(?:à|au)"#)?, datetime_check!(|datetime: &DatetimeValue| form!(Form::TimeOfDay(_))(datetime)), From 64975b666a13fcc0d2d597c90cd1e4d497ab333d Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Tue, 23 Jul 2019 16:13:48 +0200 Subject: [PATCH 048/107] Add reverse order for rule . --- grammar/fr/src/rules_datetime.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 31f33370..17a87c51 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -779,6 +779,11 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), |a, b| a.value().intersect(b.value()) ); + b.rule_2("intersect ", + datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), + datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), + |a, b| a.value().intersect(b.value()) + ); b.rule_2(" du matin", datetime_check!(form!(Form::TimeOfDay(_))), b.reg(r#"(?:(?:du|dans|de) )?(?:(?:au|le|la) )?mat(?:in[ée]?e?)?(?: pile| exactement| pr[eé]cises?)?"#)?, @@ -1212,7 +1217,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { |_, a, _, b| a.value().smart_span_to(b.value(), false) ); b.rule_2("jusqu'à ", - b.reg(r#"(?:n[ ']importe quand )?jusqu'(?:au|[aà]|en)"#)?, + b.reg(r#"(?:n[ ']importe quand )?jusqu'(?:au|[aà]|en)?"#)?, datetime_check!(|datetime: &DatetimeValue| !datetime.latent), |_, datetime| Ok(datetime.value().clone().mark_before_end()) ); From 17b1c59a2d3f01ec83be6ae03791250ef5de29b0 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Wed, 24 Jul 2019 10:43:56 +0200 Subject: [PATCH 049/107] Include + prefix in rules_numbers.rs. --- grammar/fr/src/rules_number.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/grammar/fr/src/rules_number.rs b/grammar/fr/src/rules_number.rs index da1ee39d..486eef4c 100644 --- a/grammar/fr/src/rules_number.rs +++ b/grammar/fr/src/rules_number.rs @@ -251,6 +251,29 @@ pub fn rules_numbers(b: &mut RuleSetBuilder) -> RustlingResult<()> { } }) }); + b.rule_2("numbers prefix with +, positive", + b.reg(r#"\+"#)?, + number_check!(|number: &NumberValue| !number.prefixed()), + |_, a| -> RuleResult { + Ok(match a.value().clone() { + // checked + NumberValue::Integer(integer) => { + IntegerValue { + prefixed: true, + ..integer + } + .into() + } + NumberValue::Float(float) => { + FloatValue { + prefixed: true, + ..float + } + .into() + } + }) + } + ); b.rule_2("numbers suffixes (K, M, G)", number_check!(|number: &NumberValue| !number.suffixed()), b.reg_neg_lh(r#"([kmg])"#, r#"^[\W\$€]"#)?, From bf703807d8480cc05215a915c5a0b146f16923d9 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Wed, 24 Jul 2019 16:49:13 +0200 Subject: [PATCH 050/107] Corrected rules interval - . --- grammar/fr/src/rules_datetime.rs | 38 ++++++++++++++++---------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 17a87c51..fc9157d6 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -1123,34 +1123,34 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), |_, a, _, b| a.value().span_to(b.value(), true) ); - b.rule_4("entre et (interval)", + b.rule_4("entre et (interval)", b.reg(r#"entre"#)?, - datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), + datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime)), b.reg(r#"et"#)?, datetime_check!(|datetime: &DatetimeValue| form!(Form::TimeOfDay(_))(datetime)), |_, a, _, b| a.value().span_to(b.value(), true) ); - b.rule_4("entre et (interval)", + b.rule_4("entre et (interval)", + b.reg(r#"entre"#)?, + datetime_check!(|datetime: &DatetimeValue| form!(Form::TimeOfDay(_))(datetime)), + b.reg(r#"et"#)?, + datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime)), + |_, a, _, b| a.value().span_to(b.value(), true) + ); + b.rule_4("de à (interval)", b.reg(r#"(?:[aà] partir )?d[eu]"#)?, datetime_check!(|datetime: &DatetimeValue| form!(Form::TimeOfDay(_))(datetime)), b.reg(r#"(?:jusqu')?(?:à|au)"#)?, datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), |_, a, _, b| a.value().span_to(b.value(), true) ); - b.rule_4("entre et (interval)", + b.rule_4("de à (interval)", b.reg(r#"(?:[aà] partir )?d[eu]"#)?, datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), b.reg(r#"(?:jusqu')?(?:à|au)"#)?, datetime_check!(|datetime: &DatetimeValue| form!(Form::TimeOfDay(_))(datetime)), |_, a, _, b| a.value().span_to(b.value(), true) ); - b.rule_4("entre et (interval)", - b.reg(r#"entre"#)?, - datetime_check!(|datetime: &DatetimeValue| form!(Form::TimeOfDay(_))(datetime)), - b.reg(r#"et"#)?, - datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), - |_, a, _, b| a.value().span_to(b.value(), true) - ); // Specific case with years b.rule_5("de - (interval)", b.reg(r#"depuis|d[e'u]?"#)?, @@ -1181,18 +1181,18 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { datetime_check!(form!(Form::TimeOfDay(_))), |_, a, _, b| a.value().smart_span_to(b.value(), false) ); - b.rule_4("de - (interval)", + b.rule_4("de à (interval)", b.reg(r#"(?:[aà] partir )?d['eu]"#)?, - datetime_check!(form!(Form::PartOfDay(_))), + datetime_check!(|datetime: &DatetimeValue| !form!(Form::PartOfDay(_))(datetime)), b.reg(r#"(?:jusqu')?(?:au|[aà])"#)?, - datetime_check!(form!(Form::PartOfDay(_))), + datetime_check!(|datetime: &DatetimeValue| !form!(Form::PartOfDay(_))(datetime)), |_, a, _, b| a.value().smart_span_to(b.value(), false) ); - b.rule_4("de - (interval)", - b.reg(r#"(?:[aà] partir )?d['e]"#)?, - datetime_check!(form!(Form::Meal)), - b.reg(r#"(?:jusqu')?(?:au|[aà])"#)?, - datetime_check!(form!(Form::Meal)), + b.rule_4("entre et (interval)", + b.reg(r#"entre"#)?, + datetime_check!(|datetime: &DatetimeValue| !form!(Form::PartOfDay(_))(datetime)), + b.reg(r#"et"#)?, + datetime_check!(|datetime: &DatetimeValue| !form!(Form::PartOfDay(_))(datetime)), |_, a, _, b| a.value().smart_span_to(b.value(), false) ); b.rule_2("de maintenant - (interval)", From befe6580ee198539bdd2bfe8ea0d1d394ad46bd4 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Thu, 25 Jul 2019 11:23:38 +0200 Subject: [PATCH 051/107] Small correction rule 'gouter' added article 'le'. --- grammar/fr/src/rules_datetime.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index fc9157d6..06af3c3b 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -642,7 +642,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { } ); b.rule_1_terminal("gouter", - b.reg(r#"(?:(?:[àa] )?l[' ]heure du|au(?: moment du)?|pendant le|pour le) go[uû]ter"#)?, + b.reg(r#"(?:(?:[àa] )?l[' ]heure du|au(?: moment du)?|pendant le|(?:pour )?le) go[uû]ter"#)?, |_| Ok(helpers::hour(16, false)? .span_to(&helpers::hour(18, false)?, false)? .form(Form::Meal)) From a1aa19d80eb5e89d509c1bdeb563e747fb042e47 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Thu, 25 Jul 2019 15:39:16 +0200 Subject: [PATCH 052/107] Add trimestre to duration, add new training ex to account for newly supported cases. --- grammar/fr/src/rules_datetime.rs | 2 +- grammar/fr/src/rules_duration.rs | 4 ++++ grammar/fr/src/training.rs | 7 +++++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 06af3c3b..38d9159e 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -1596,7 +1596,7 @@ pub fn rules_cycle(b: &mut RuleSetBuilder) -> RustlingResult<()> { |_| CycleValue::new(Grain::Month) ); b.rule_1_terminal("trimestre (cycle)", - b.reg(r#"trimestre"#)?, + b.reg(r#"trimestres?"#)?, |_| CycleValue::new(Grain::Quarter) ); b.rule_1("année (cycle)", diff --git a/grammar/fr/src/rules_duration.rs b/grammar/fr/src/rules_duration.rs index 33cb0e88..1bd4695e 100644 --- a/grammar/fr/src/rules_duration.rs +++ b/grammar/fr/src/rules_duration.rs @@ -32,6 +32,10 @@ pub fn rules_duration(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.reg(r#"an(?:n[ée]e?)?s?"#)?, |_| Ok(UnitOfDurationValue::new(Grain::Year)) ); + b.rule_1_terminal("trimestre (unit-of-duration)", + b.reg(r#"trimestres?"#)?, + |_| Ok(UnitOfDurationValue::new(Grain::Quarter)) + ); b.rule_1_terminal("un quart heure", b.reg(r#"(1/4\s?h(?:eure)?|(?:un|1) quart d'heure)"#)?, |_| Ok(DurationValue::new(PeriodComp::minutes(15).into())) diff --git a/grammar/fr/src/training.rs b/grammar/fr/src/training.rs index 036a392f..00b4c27a 100644 --- a/grammar/fr/src/training.rs +++ b/grammar/fr/src/training.rs @@ -118,7 +118,7 @@ pub fn examples_datetime(v: &mut Vec<::rustling::train::Example>) { example!(v, check_moment!(c, [2015, 10, 31]), "dernier jour d'octobre 2015", "le dernier jour d'octobre 2015"); example!(v, check_moment!(c, [2014, 9, 22], Grain::Week), "dernière semaine de septembre 2014", "la dernière semaine de septembre 2014"); //Hours - example!(v, check_moment!(c, [2013, 2, 12, 15]), "à quinze heures", "à 15 heures", "à 3 heures cet après-midi", "15h", "15H"); + example!(v, check_moment!(c, [2013, 2, 12, 15]), "à quinze heures", "à 15 heures", "15h précises", "15 heures pile", "à 3 heures cet après-midi", "15h", "15H"); example!(v, check_moment_with_precision!(c, [2013, 2, 12, 15], Precision::Approximate), "vers 15 heures", "à environ 15 heures"); example!(v, check_moment!(c, [2013, 2, 12, 15, 0]), "15:00", "15h00", "15H00"); example!(v, check_moment!(c, [2013, 2, 13, 00]), "minuit"); @@ -154,12 +154,14 @@ pub fn examples_datetime(v: &mut Vec<::rustling::train::Example>) { example!(v, check_moment!(c, [2011, 2]), "il y a deux ans"); //Seasons example!(v, check_moment_span!(c, [2013, 6, 21], [2013, 9, 24]), "cet été"); + example!(v, check_moment_span!(c, [2013, 7, 15], [2013, 8, 16]), "au milieu de cet été"); example!(v, check_moment_span!(c, [2012, 12, 21], [2013, 3, 21]), "cet hiver"); example!(v, check_moment!(c, [2013, 12, 25]), "Noel", "noël", "jour de noel"); example!(v, check_moment_span!(c, [2013, 12, 24, 18], [2013, 12, 25, 00]), "le soir de noël"); example!(v, check_moment!(c, [2014, 1, 1]), "jour de l'an", "nouvel an", "premier janvier"); + example!(v, check_moment!(c, [2013, 12, 31]), "le réveillon de la saint sylvestre", "pour la saint-sylvestre"); example!(v, check_moment!(c, [2013, 11, 1]), "la toussaint", "le jour de la toussaint", "la journée de la toussaint", "toussaint", "le jour des morts"); - example!(v, check_moment!(c, [2013, 05, 1]), "fête du travail"); + example!(v, check_moment!(c, [2013, 05, 1]), "fête du travail", "à la prochaine fête du travail"); //Part of day (morning, afternoon...) example!(v, check_moment_span!(c, [2013, 2, 12, 12], [2013, 2, 12, 19]), "cet après-midi", "l'après-midi"); example!(v, check_moment_span!(c, [2013, 2, 12, 15], [2013, 2, 12, 17]), "en milieu d'après-midi"); @@ -254,6 +256,7 @@ pub fn examples_datetime(v: &mut Vec<::rustling::train::Example>) { pub fn examples_durations(v: &mut Vec<::rustling::train::Example>) { example!(v, check_duration!([0, 0, 0, 0, 2]), "pendant deux heures", "durant deux heures", "pour une durée de deux heures", "une durée de deux heures"); example!(v, check_duration!([0, 0, 0, 1]), "pendant un jour", "une journée"); + example!(v, check_duration!([0, 0, 0, 3]), "pour une durée de quelques jours", "quelques jours"); example!(v, check_duration!([0, 1, 0]), "durant un mois"); example!(v, check_duration!([1]), "durant une année"); example!(v, check_duration!([0, 0, 0, 0, 0, 1, 3]), "pendant une minute et trois secondes"); From 272d50ad50a2d518af61d3274fe94bd404c17462 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Thu, 25 Jul 2019 17:16:31 +0200 Subject: [PATCH 053/107] Add training examples, Correct rule sunrise. --- grammar/fr/src/rules_datetime.rs | 2 +- grammar/fr/src/training.rs | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 38d9159e..0811d20e 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -533,7 +533,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { .form(Form::PartOfDay(PartOfDayForm::Morning))) ); b.rule_1_terminal("lever du soleil", - b.reg(r#"lever d[ue] soleil|aurore|aube"#)?, + b.reg(r#"lever d[ue] soleil|(?:aux )?aurores?|aube"#)?, |_| Ok(helpers::hour(4, false)? .span_to(&helpers::hour(8, false)?, false)? .latent() diff --git a/grammar/fr/src/training.rs b/grammar/fr/src/training.rs index 00b4c27a..251e2c00 100644 --- a/grammar/fr/src/training.rs +++ b/grammar/fr/src/training.rs @@ -154,8 +154,12 @@ pub fn examples_datetime(v: &mut Vec<::rustling::train::Example>) { example!(v, check_moment!(c, [2011, 2]), "il y a deux ans"); //Seasons example!(v, check_moment_span!(c, [2013, 6, 21], [2013, 9, 24]), "cet été"); + example!(v, check_moment_span!(c, [2013, 6, 21], [2013, 7, 16]), "au début de cet été"); example!(v, check_moment_span!(c, [2013, 7, 15], [2013, 8, 16]), "au milieu de cet été"); + example!(v, check_moment_span!(c, [2013, 8, 15], [2013, 9, 22]), "à la fin de cet été"); example!(v, check_moment_span!(c, [2012, 12, 21], [2013, 3, 21]), "cet hiver"); + example!(v, check_moment_span!(c, [2013, 10, 01], [2014, 01, 01]), "en fin d'année"); + example!(v, check_moment_span!(c, [2013, 01, 01], [2013, 03, 01]), "en début d'année"); example!(v, check_moment!(c, [2013, 12, 25]), "Noel", "noël", "jour de noel"); example!(v, check_moment_span!(c, [2013, 12, 24, 18], [2013, 12, 25, 00]), "le soir de noël"); example!(v, check_moment!(c, [2014, 1, 1]), "jour de l'an", "nouvel an", "premier janvier"); @@ -181,6 +185,9 @@ pub fn examples_datetime(v: &mut Vec<::rustling::train::Example>) { example!(v, check_moment_span!(c, [2013, 2, 12, 18], [2013, 2, 13, 00]), "ce soir"); example!(v, check_moment_span!(c, [2013, 2, 12, 18], [2013, 2, 12, 21]), "en début de soirée"); example!(v, check_moment_span!(c, [2013, 2, 12, 21], [2013, 2, 13, 00]), "en fin de soirée"); + example!(v, check_moment_span!(c, [2013, 2, 13, 02], [2013, 2, 13, 04]), "au milieu de la nuit"); + example!(v, check_moment_span!(c, [2013, 2, 12, 19], [2013, 2, 12, 22]), "au coucher du soleil", "à la tombée de la nuit", "pour le crépuscule"); + example!(v, check_moment_span!(c, [2013, 2, 12, 04], [2013, 2, 12, 08]), "au lever du soleil", "à l'aube", "aux aurores", "à l'aurore"); example!(v, check_moment_span!(c, [2013, 2, 13, 18], [2013, 2, 14, 00]), "demain soir", "mercredi soir", "mercredi en soirée"); example!(v, check_moment_span!(c, [2013, 2, 11, 18], [2013, 2, 12, 00]), "hier soir", "la veille au soir"); example!(v, check_moment_span!(c, [2013, 2, 15, 18], [2013, 2, 18, 00]), "ce week-end"); From 91159172724710a14dd961eef6a2759aa376ec2c Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Thu, 25 Jul 2019 18:07:39 +0200 Subject: [PATCH 054/107] Fix/fr add duration vocab 2 [#182](https://github.com/snipsco/rustling-ontology/pull/182) --- grammar/fr/src/rules_duration.rs | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/grammar/fr/src/rules_duration.rs b/grammar/fr/src/rules_duration.rs index 1bd4695e..e5c53ba3 100644 --- a/grammar/fr/src/rules_duration.rs +++ b/grammar/fr/src/rules_duration.rs @@ -105,17 +105,37 @@ pub fn rules_duration(b: &mut RuleSetBuilder) -> RustlingResult<()> { |duration, integer| helpers::compose_duration_with_integer(duration.value(), integer.value()) ); b.rule_2("environ ", - b.reg(r#"environ"#)?, + b.reg(r#"environ|approximativement|à peu près|presque"#)?, duration_check!(), |_, duration| Ok(duration.value().clone().precision(Precision::Approximate)) ); + b.rule_2(" environ", + duration_check!(), + b.reg(r#"environ|approximativement|à peu près"#)?, + |duration, _| Ok(duration.value().clone().precision(Precision::Approximate)) + ); + b.rule_2("exactement ", + b.reg(r#"exactement|précisément"#)?, + duration_check!(), + |_, duration| Ok(duration.value().clone().precision(Precision::Exact)) + ); + b.rule_2(" exactement", + duration_check!(), + b.reg(r#"exactement|précisément|pile"#)?, + |duration, _| Ok(duration.value().clone().precision(Precision::Exact)) + ); // Ambiguous w/ time-of-day w/ "pour" // Duration has less priority than Datetime types, therefore duration will be output only // if the output kind filter is set for Duration b.rule_2("pendant ", - b.reg(r#"pendant|durant|pour(?: une dur[eé]e? d['e])?"#)?, + b.reg(r#"pendant|durant|pour"#)?, duration_check!(), |_, duration| Ok(duration.value().clone().prefixed()) ); + b.rule_2("une durée de ", + b.reg(r#"une dur[ée]e d['e]"#)?, + duration_check!(), + |_, duration| Ok(duration.value().clone().prefixed()) + ); Ok(()) } \ No newline at end of file From b75bf65ee424f8cf384e05817e5bf3ff5e55360c Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Mon, 29 Jul 2019 11:43:24 +0200 Subject: [PATCH 055/107] Split grammar: one rule file per entity. --- grammar/fr/src/lib.rs | 23 +- grammar/fr/src/rules_amount.rs | 243 ++++ grammar/fr/src/rules_celebrations.rs | 106 ++ .../fr/src/{rules.rs => rules_datetime.rs} | 1173 ++--------------- grammar/fr/src/rules_duration.rs | 172 +++ grammar/fr/src/rules_number.rs | 580 ++++++++ 6 files changed, 1204 insertions(+), 1093 deletions(-) create mode 100644 grammar/fr/src/rules_amount.rs create mode 100644 grammar/fr/src/rules_celebrations.rs rename grammar/fr/src/{rules.rs => rules_datetime.rs} (56%) create mode 100644 grammar/fr/src/rules_duration.rs create mode 100644 grammar/fr/src/rules_number.rs diff --git a/grammar/fr/src/lib.rs b/grammar/fr/src/lib.rs index 00c89d87..1672f388 100644 --- a/grammar/fr/src/lib.rs +++ b/grammar/fr/src/lib.rs @@ -3,7 +3,11 @@ extern crate rustling; extern crate rustling_ontology_values; extern crate rustling_ontology_moment; -pub mod rules; +mod rules_datetime; +mod rules_celebrations; +mod rules_duration; +mod rules_number; +mod rules_amount; pub mod training; use rustling_ontology_values::DimensionKind::*; @@ -12,13 +16,16 @@ pub fn rule_set() -> ::rustling::RustlingResult<::rustling::RuleSet) -> RustlingResult<()> { + b.rule_2(" per cent", + number_check!(), + b.reg(r"(?:%|p\.c\.|p. cents?|pour[ -]?cents?)")?, + |number, _| Ok(PercentageValue(number.value().value())) + ); + Ok(()) +} + +pub fn rules_finance(b: &mut RuleSetBuilder) -> RustlingResult<()> { + b.rule_2("intersect (X cents)", + amount_of_money_check!(), + amount_of_money_check!(|money: &AmountOfMoneyValue| money.unit == Some("cent")), + |a, b| helpers::compose_money(a.value(), b.value())); + b.rule_3("intersect (and X cents)", + amount_of_money_check!(), + b.reg(r#"et"#)?, + amount_of_money_check!(|money: &AmountOfMoneyValue| money.unit == Some("cent")), + |a, _, b| helpers::compose_money(&a.value(), &b.value())); + b.rule_2("intersect", + amount_of_money_check!(|money: &AmountOfMoneyValue| money.unit != Some("cent")), + number_check!(), + |a, b| helpers::compose_money_number(&a.value(), &b.value())); + b.rule_1_terminal("$", + b.reg(r#"\$|dollars?"#)?, + |_| Ok(MoneyUnitValue { unit: Some("$") }) + ); + b.rule_1_terminal("EUR", + b.reg(r#"€|(?:[e€]uro?s?)"#)?, + |_| Ok(MoneyUnitValue { unit: Some("EUR") }) + ); + b.rule_1_terminal("£", + b.reg(r#"£|livres?"#)?, + |_| Ok(MoneyUnitValue { unit: Some("£") }) + ); + b.rule_1_terminal("USD", + b.reg(r#"us[d\$]|dollars? am[eé]ricains?"#)?, + |_| Ok(MoneyUnitValue { unit: Some("USD") }) + ); + b.rule_1_terminal("AUD", + b.reg(r#"au[d\$]|dollars? australiens?"#)?, + |_| Ok(MoneyUnitValue { unit: Some("AUD") }) + ); + b.rule_1_terminal("CAD", + b.reg(r#"cad|dollars? canadiens?"#)?, + |_| Ok(MoneyUnitValue { unit: Some("CAD") }) + ); + b.rule_1_terminal("HKD", + b.reg(r#"hkd|dollars? de hong[- ]kong"#)?, + |_| Ok(MoneyUnitValue { unit: Some("HKD") }) + ); + b.rule_1_terminal("KR", + b.reg(r#"kr|couronnes?"#)?, + |_| Ok(MoneyUnitValue { unit: Some("KR") }) + ); + b.rule_1_terminal("DKK", + b.reg(r#"dkk|couronnes? danoises?"#)?, + |_| Ok(MoneyUnitValue { unit: Some("DKK") }) + ); + b.rule_1_terminal("NOK", + b.reg(r#"nok|couronnes? norv[ée]giennes?"#)?, + |_| Ok(MoneyUnitValue { unit: Some("NOK") }) + ); + b.rule_1_terminal("SEK", + b.reg(r#"sek|couronnes? su[ée]doises?"#)?, + |_| Ok(MoneyUnitValue { unit: Some("SEK") }) + ); + b.rule_1_terminal("CHF", + b.reg(r#"chf|francs? suisses?"#)?, + |_| Ok(MoneyUnitValue { unit: Some("CHF") }) + ); + b.rule_1_terminal("RUB", + b.reg(r#"rub|roubles?"#)?, + |_| Ok(MoneyUnitValue { unit: Some("RUB") }) + ); + b.rule_1_terminal("INR", + b.reg(r#"inr|roupies?"#)?, + |_| Ok(MoneyUnitValue { unit: Some("INR") }) + ); + b.rule_1_terminal("JPY", + b.reg(r#"jpy|yens?"#)?, + |_| Ok(MoneyUnitValue { unit: Some("JPY") }) + ); + b.rule_1_terminal("RMB|CNH|CNY", + b.reg(r#"cny|cnh|rmb|yuans?|renmimbis?"#)?, + |_| Ok(MoneyUnitValue { unit: Some("CNY") }) + ); + b.rule_1_terminal("¥", + b.reg(r#"¥"#)?, + |_| Ok(MoneyUnitValue { unit: Some("¥") }) + ); + b.rule_1_terminal("KRW", + b.reg(r#"₩|krw|wons? (?:sud[- ])?cor[ée]ns?|wons?"#)?, + |_| Ok(MoneyUnitValue { unit: Some("KRW") }) + ); + b.rule_1_terminal("Bitcoin", + b.reg(r#"฿|bitcoins?"#)?, + |_| Ok(MoneyUnitValue { unit: Some("฿") }) + ); + b.rule_1_terminal("GBP", + b.reg(r#"gbp|livres? sterlings?"#)?, + |_| Ok(MoneyUnitValue { unit: Some("GBP") }) + ); + b.rule_1_terminal("cent", + b.reg(r#"centimes?|cents?|penn(?:y|ies)|fens?"#)?, + |_| Ok(MoneyUnitValue { unit: Some("cent") }) + ); + b.rule_1_terminal("unnamed currency", + b.reg(r#"(?:balle)s?"#)?, + |_| Ok(MoneyUnitValue { unit: None }) + ); + b.rule_2(" ", + number_check!(), + money_unit!(), + |a, b| { + Ok(AmountOfMoneyValue { + value: a.value().value(), + unit: b.value().unit, + ..AmountOfMoneyValue::default() + }) + }); + b.rule_3(" de ", // "un million de dollars" + integer_check!(|integer: &IntegerValue| !integer.group), + b.reg(r#"d[e']"#)?, + money_unit!(), + |a, _, b| { + Ok(AmountOfMoneyValue { + value: a.value().value as f32, + precision: Exact, + unit: b.value().unit, + ..AmountOfMoneyValue::default() + }) + }); + b.rule_3(" de ", // "une douzaine de dollars" + integer_check!(|integer: &IntegerValue| integer.group), + b.reg(r#"d[e']"#)?, + money_unit!(), + |a, _, b| { + Ok(AmountOfMoneyValue { + value: a.value().value as f32, + precision: Approximate, + unit: b.value().unit, + ..AmountOfMoneyValue::default() + }) + }); + b.rule_2("about ", + b.reg(r#"(?:autour|pas loin|pr[eè]s|aux alentours) d[e']|environ|presque|(?:approximative|quasi)ment"#)?, + amount_of_money_check!(), + |_, a| { + Ok(AmountOfMoneyValue { + precision: Approximate, + ..a.value().clone() + }) + }); + b.rule_2("exactly ", + b.reg(r#"(?:tr[eè]s )?exactement|pr[eé]cis[eé]ment|pile(?: poil)?"#)?, + amount_of_money_check!(), + |_, a| { + Ok(AmountOfMoneyValue { + precision: Exact, + ..a.value().clone() + }) + }); + b.rule_2("exactly ", + amount_of_money_check!(), + b.reg(r#"pile(?: poil)?|tout rond"#)?, + |a, _| { + Ok(AmountOfMoneyValue { + precision: Exact, + ..a.value().clone() + }) + } + ); + Ok(()) +} + +pub fn rules_temperature(b: &mut RuleSetBuilder) -> RustlingResult<()> { + b.rule_1("number as temp", + number_check!(), + |a| { + Ok(TemperatureValue { + value: a.value().value(), + unit: None, + latent: true, + }) + }); + b.rule_2(" degrees", + temperature_check!(), + b.reg(r#"(?:deg(?:r[éeè])?s?\.?)|°"#)?, + |a, _| { + Ok(TemperatureValue { + value: a.value().value, + unit: Some("degree"), + latent: false, + }) + }); + b.rule_2(" Celcius", + temperature_check!(), + b.reg(r#"centigrades?|c(?:el[cs]?(?:ius)?)?\.?"#)?, + |a, _| { + Ok(TemperatureValue { + value: a.value().value, + unit: Some("celsius"), + latent: false, + }) + }); + b.rule_2(" Fahrenheit", + temperature_check!(), + b.reg(r#"f(?:ah?reh?n(?:h?eit)?)?\.?"#)?, + |a, _| { + Ok(TemperatureValue { + value: a.value().value, + unit: Some("fahrenheit"), + latent: false, + }) + }); + b.rule_2(" Kelvin", + temperature_check!(), + b.reg(r#"k(?:elvin)?\.?"#)?, + |a, _| { + Ok(TemperatureValue { + value: a.value().value, + unit: Some("kelvin"), + latent: false, + }) + }); + b.rule_2(" en dessous de zero", + temperature_check!(|temp: &TemperatureValue| !temp.latent), + b.reg(r#"en dessous de (?:0|z[ée]ro)"#)?, + |a, _| { + Ok(TemperatureValue { + value: -1.0 * a.value().value, + latent: false, + ..*a.value() + }) + }); + Ok(()) +} diff --git a/grammar/fr/src/rules_celebrations.rs b/grammar/fr/src/rules_celebrations.rs new file mode 100644 index 00000000..446de1c8 --- /dev/null +++ b/grammar/fr/src/rules_celebrations.rs @@ -0,0 +1,106 @@ +use rustling::*; +use rustling_ontology_values::dimension::*; +use rustling_ontology_values::helpers; +use rustling_ontology_moment::{Weekday, Grain}; + +pub fn rules_celebration(b: &mut RuleSetBuilder) -> RustlingResult<()> { + +b.rule_1_terminal("noel", + b.reg(r#"(?:jour de )?no[eë]l"#)?, + |_| Ok(helpers::month_day(12, 25)?.form(Form::Celebration)) + ); + b.rule_1_terminal("soir de noël", + b.reg(r#"(soir(?:ée)?|veille) de no[eë]l"#)?, + |_| { + let start = helpers::month_day(12, 24)?.intersect(&helpers::hour(18, false)?)?; + let end = helpers::month_day(12, 25)?.intersect(&helpers::hour(0, false)?)?; + Ok(start.span_to(&end, false)? + .form(Form::Celebration)) + } + ); + b.rule_1_terminal("jour de l'an", + b.reg(r#"(?:le )?(?:jour de l'|nouvel )an"#)?, + |_| Ok(helpers::month_day(1, 1)?.form(Form::Celebration)) + ); + b.rule_1_terminal("toussaint", + b.reg(r#"(?:(?:la |la journée de la |jour de la )?toussaint|jour des morts)"#)?, + |_| Ok(helpers::month_day(11, 1)?.form(Form::Celebration)) + ); + b.rule_1_terminal("Armistice", + b.reg(r#"(?:pour )?l'armistice"#)?, + |_| Ok(helpers::month_day(11, 11)?.form(Form::Celebration)) + ); + b.rule_1_terminal("Saint Etienne (Alsace)", + b.reg(r#"(?:(?:le jour|la f[eê]te) de )?la (?:saint|st) [eé]tienne"#)?, + |_| Ok(helpers::month_day(12, 26)?.form(Form::Celebration)) + ); + b.rule_1_terminal("jeudi saint", + b.reg(r#"(?:le )?jeudi saint"#)?, + |_| Ok(helpers::cycle_nth_after(Grain::Day, -3, &helpers::easter()?)? + .form(Form::Celebration)) + ); + b.rule_1_terminal("vendredi saint", + b.reg(r#"(?:le )?vendredi saint"#)?, + |_| Ok(helpers::cycle_nth_after(Grain::Day, -2, &helpers::easter()?)? + .form(Form::Celebration)) + ); + b.rule_1_terminal("samedi saint", + b.reg(r#"(?:le )?samedi saint"#)?, + |_| Ok(helpers::cycle_nth_after(Grain::Day, -1, &helpers::easter()?)? + .form(Form::Celebration)) + ); + b.rule_1_terminal("pâques", + b.reg(r#"(?:la f[eê]te de |le jour de |le dimanche de )?p[âa]ques"#)?, + |_| Ok(helpers::easter()?.form(Form::Celebration)) + ); + b.rule_1_terminal("le lundi de pâques", + b.reg(r#"le lundi de p[âa]ques"#)?, + |_| Ok(helpers::cycle_nth_after(Grain::Day, 1, &helpers::easter()?)? + .form(Form::Celebration)) + ); + b.rule_1_terminal("ascension", + b.reg(r#"(?:la f[eê]te de l'|le jeudi de l'|l'|le jour de l')ascension"#)?, + |_| Ok(helpers::cycle_nth_after(Grain::Day, 39, &helpers::easter()?)? + .form(Form::Celebration)) + + ); + b.rule_1_terminal("pencôte", + b.reg(r#"(?:la f[eê]te de la |la |le lundi de la )?penc[oô]te"#)?, + |_| Ok(helpers::cycle_nth_after(Grain::Day, 49, &helpers::easter()?)? + .form(Form::Celebration)) + ); + b.rule_1_terminal("1er mai", + b.reg(r#"(?:la )?f(e|ê)te du travail"#)?, + |_| Ok(helpers::month_day(5, 1)?.form(Form::Celebration)) + ); + b.rule_1_terminal("fêtes des pères", + b.reg(r#"(?:la )?f[eê]te des p[eè]res"#)?, + |_| { + let sundays_of_june = helpers::month(6)?.intersect(&helpers::day_of_week(Weekday::Sun)?)?; + let second_week_of_june = helpers::cycle_nth_after(Grain::Week, 2, &helpers::month_day(6, 1)?)?; + Ok(sundays_of_june.intersect(&second_week_of_june)? // third sunday of June + .form(Form::Celebration)) + } + ); + b.rule_1_terminal("fêtes des mères", + b.reg(r#"(?:la )?f[eê]te des m[eè]res"#)?, + |_| { + // It is the last last sunday of may + // If it is the same day as the Pentecost, it is the first sunday of june + // This case is not supported for now + Ok(helpers::day_of_week(Weekday::Sun)?.last_of(&helpers::month(5)?)? + .form(Form::Celebration)) + } + ); + b.rule_1_terminal("fête nationale", + b.reg(r#"(?:la )?f[eê]te (?:nationale|du (?:14|quatorze) juillet)"#)?, + |_| Ok(helpers::month_day(7, 14)? + .form(Form::Celebration)) + ); + b.rule_1_terminal("assomption", + b.reg(r#"(?:la f[eê]te de |le jour de )?l'assomption"#)?, + |_| Ok(helpers::month_day(8, 15)? + .form(Form::Celebration)) + ); + +} \ No newline at end of file diff --git a/grammar/fr/src/rules.rs b/grammar/fr/src/rules_datetime.rs similarity index 56% rename from grammar/fr/src/rules.rs rename to grammar/fr/src/rules_datetime.rs index deb3e149..f5306bd6 100644 --- a/grammar/fr/src/rules.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -1,353 +1,11 @@ + use rustling::*; use rustling_ontology_values::dimension::*; -use rustling_ontology_values::dimension::Precision::*; use rustling_ontology_values::helpers; -use rustling_ontology_moment::{Weekday, Grain, PeriodComp, Period}; - -pub fn rules_percentage(b: &mut RuleSetBuilder) -> RustlingResult<()> { - b.rule_2(" per cent", - number_check!(), - b.reg(r"(?:%|p\.c\.|p. cents?|pour[ -]?cents?)")?, - |number, _| Ok(PercentageValue(number.value().value())) - ); - Ok(()) -} - -pub fn rules_finance(b: &mut RuleSetBuilder) -> RustlingResult<()> { - b.rule_2("intersect (X cents)", - amount_of_money_check!(), - amount_of_money_check!(|money: &AmountOfMoneyValue| money.unit == Some("cent")), - |a, b| helpers::compose_money(a.value(), b.value())); - b.rule_3("intersect (and X cents)", - amount_of_money_check!(), - b.reg(r#"et"#)?, - amount_of_money_check!(|money: &AmountOfMoneyValue| money.unit == Some("cent")), - |a, _, b| helpers::compose_money(&a.value(), &b.value())); - b.rule_2("intersect", - amount_of_money_check!(|money: &AmountOfMoneyValue| money.unit != Some("cent")), - number_check!(), - |a, b| helpers::compose_money_number(&a.value(), &b.value())); - b.rule_1_terminal("$", - b.reg(r#"\$|dollars?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("$") }) - ); - b.rule_1_terminal("EUR", - b.reg(r#"€|(?:[e€]uro?s?)"#)?, - |_| Ok(MoneyUnitValue { unit: Some("EUR") }) - ); - b.rule_1_terminal("£", - b.reg(r#"£|livres?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("£") }) - ); - b.rule_1_terminal("USD", - b.reg(r#"us[d\$]|dollars? am[eé]ricains?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("USD") }) - ); - b.rule_1_terminal("AUD", - b.reg(r#"au[d\$]|dollars? australiens?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("AUD") }) - ); - b.rule_1_terminal("CAD", - b.reg(r#"cad|dollars? canadiens?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("CAD") }) - ); - b.rule_1_terminal("HKD", - b.reg(r#"hkd|dollars? de hong[- ]kong"#)?, - |_| Ok(MoneyUnitValue { unit: Some("HKD") }) - ); - b.rule_1_terminal("KR", - b.reg(r#"kr|couronnes?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("KR") }) - ); - b.rule_1_terminal("DKK", - b.reg(r#"dkk|couronnes? danoises?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("DKK") }) - ); - b.rule_1_terminal("NOK", - b.reg(r#"nok|couronnes? norv[ée]giennes?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("NOK") }) - ); - b.rule_1_terminal("SEK", - b.reg(r#"sek|couronnes? su[ée]doises?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("SEK") }) - ); - b.rule_1_terminal("CHF", - b.reg(r#"chf|francs? suisses?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("CHF") }) - ); - b.rule_1_terminal("RUB", - b.reg(r#"rub|roubles?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("RUB") }) - ); - b.rule_1_terminal("INR", - b.reg(r#"inr|roupies?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("INR") }) - ); - b.rule_1_terminal("JPY", - b.reg(r#"jpy|yens?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("JPY") }) - ); - b.rule_1_terminal("RMB|CNH|CNY", - b.reg(r#"cny|cnh|rmb|yuans?|renmimbis?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("CNY") }) - ); - b.rule_1_terminal("¥", - b.reg(r#"¥"#)?, - |_| Ok(MoneyUnitValue { unit: Some("¥") }) - ); - b.rule_1_terminal("KRW", - b.reg(r#"₩|krw|wons? (?:sud[- ])?cor[ée]ns?|wons?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("KRW") }) - ); - b.rule_1_terminal("Bitcoin", - b.reg(r#"฿|bitcoins?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("฿") }) - ); - b.rule_1_terminal("GBP", - b.reg(r#"gbp|livres? sterlings?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("GBP") }) - ); - b.rule_1_terminal("cent", - b.reg(r#"centimes?|cents?|penn(?:y|ies)|fens?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("cent") }) - ); - b.rule_1_terminal("unnamed currency", - b.reg(r#"(?:balle)s?"#)?, - |_| Ok(MoneyUnitValue { unit: None }) - ); - b.rule_2(" ", - number_check!(), - money_unit!(), - |a, b| { - Ok(AmountOfMoneyValue { - value: a.value().value(), - unit: b.value().unit, - ..AmountOfMoneyValue::default() - }) - }); - b.rule_3(" de ", // "un million de dollars" - integer_check!(|integer: &IntegerValue| !integer.group), - b.reg(r#"d[e']"#)?, - money_unit!(), - |a, _, b| { - Ok(AmountOfMoneyValue { - value: a.value().value as f32, - precision: Exact, - unit: b.value().unit, - ..AmountOfMoneyValue::default() - }) - }); - b.rule_3(" de ", // "une douzaine de dollars" - integer_check!(|integer: &IntegerValue| integer.group), - b.reg(r#"d[e']"#)?, - money_unit!(), - |a, _, b| { - Ok(AmountOfMoneyValue { - value: a.value().value as f32, - precision: Approximate, - unit: b.value().unit, - ..AmountOfMoneyValue::default() - }) - }); - b.rule_2("about ", - b.reg(r#"(?:autour|pas loin|pr[eè]s|aux alentours) d[e']|environ|presque|(?:approximative|quasi)ment"#)?, - amount_of_money_check!(), - |_, a| { - Ok(AmountOfMoneyValue { - precision: Approximate, - ..a.value().clone() - }) - }); - b.rule_2("exactly ", - b.reg(r#"(?:tr[eè]s )?exactement|pr[eé]cis[eé]ment|pile(?: poil)?"#)?, - amount_of_money_check!(), - |_, a| { - Ok(AmountOfMoneyValue { - precision: Exact, - ..a.value().clone() - }) - }); - b.rule_2("exactly ", - amount_of_money_check!(), - b.reg(r#"pile(?: poil)?|tout rond"#)?, - |a, _| { - Ok(AmountOfMoneyValue { - precision: Exact, - ..a.value().clone() - }) - } - ); - Ok(()) -} +use rustling_ontology_moment::{Weekday, Grain}; -pub fn rules_duration(b: &mut RuleSetBuilder) -> RustlingResult<()> { - b.rule_1_terminal("seconde (unit-of-duration)", - b.reg(r#"sec(?:onde)?s?"#)?, - |_| Ok(UnitOfDurationValue::new(Grain::Second)) - ); - b.rule_1_terminal("minute (unit-of-duration)", - b.reg(r#"min(?:ute)?s?"#)?, - |_| Ok(UnitOfDurationValue::new(Grain::Minute)) - ); - b.rule_1_terminal("heure (unit-of-duration)", - b.reg(r#"h(?:eure)?s?"#)?, - |_| Ok(UnitOfDurationValue::new(Grain::Hour)) - ); - b.rule_1_terminal("jour (unit-of-duration)", - b.reg(r#"jour(?:n[ée]e?)?s?"#)?, - |_| Ok(UnitOfDurationValue::new(Grain::Day)) - ); - b.rule_1_terminal("semaine (unit-of-duration)", - b.reg(r#"semaines?"#)?, - |_| Ok(UnitOfDurationValue::new(Grain::Week)) - ); - b.rule_1_terminal("mois (unit-of-duration)", - b.reg(r#"mois?"#)?, - |_| Ok(UnitOfDurationValue::new(Grain::Month)) - ); - b.rule_1_terminal("année (unit-of-duration)", - b.reg(r#"an(?:n[ée]e?)?s?"#)?, - |_| Ok(UnitOfDurationValue::new(Grain::Year)) - ); - b.rule_1_terminal("trimestre (unit-of-duration)", - b.reg(r#"trimestres?"#)?, - |_| Ok(UnitOfDurationValue::new(Grain::Quarter)) - ); - b.rule_1_terminal("un quart heure", - b.reg(r#"(1/4\s?h(?:eure)?|(?:un|1) quart d'heure)"#)?, - |_| Ok(DurationValue::new(PeriodComp::minutes(15).into())) - ); - b.rule_1_terminal("une demi heure", - b.reg(r#"(?:1/2\s?h(?:eure)?|(?:1|une) demi(?:e)?(?:\s|-)heure)"#)?, - |_| Ok(DurationValue::new(PeriodComp::minutes(30).into())) - ); - b.rule_1_terminal("trois quarts d'heure", - b.reg(r#"(?:3/4\s?h(?:eure)?|(?:3|trois) quart(?:s)? d'heure)"#)?, - |_| Ok(DurationValue::new(PeriodComp::minutes(45).into())) - ); - b.rule_2(" ", - integer_check_by_range!(0), - unit_of_duration_check!(), - |integer, unit| Ok(DurationValue::new(PeriodComp::new(unit.value().grain, integer.value().value).into())) - ); - b.rule_3(" de ", - integer_check!(|integer: &IntegerValue| integer.value >= 0 && integer.group), - b.reg(r#"d[e']"#)?, - unit_of_duration_check!(), - |integer, _, unit| Ok(DurationValue::new(PeriodComp::new(unit.value().grain, integer.value().value).into())) - ); - b.rule_4(" h ", - integer_check_by_range!(0), - b.reg(r#"h(?:eures?)?"#)?, - integer_check_by_range!(0,59), - b.reg(r#"m(?:inutes?)?"#)?, - |hour, _, minute, _| { - let hour_period = Period::from(PeriodComp::new(Grain::Hour, hour.value().clone().value)); - let minute_period = Period::from(PeriodComp::new(Grain::Minute, minute.value().clone().value)); - Ok(DurationValue::new(hour_period + minute_period)) - } - ); - b.rule_3(" et quart", - integer_check_by_range!(0), - unit_of_duration_check!(), - b.reg(r#"et quart"#)?, - |integer, uod, _| { - let quarter_period: Period = uod.value().grain.quarter_period().map(|a| a.into()).ok_or_else(|| RuleError::Invalid)?; - Ok(DurationValue::new(quarter_period + PeriodComp::new(uod.value().grain, integer.value().value))) - } - ); - b.rule_3(" et demie", - integer_check_by_range!(0), - unit_of_duration_check!(), - b.reg(r#"et demie?"#)?, - |integer, uod, _| { - let half_period: Period = uod.value().grain.half_period().map(|a| a.into()).ok_or_else(|| RuleError::Invalid)?; - Ok(DurationValue::new(half_period + PeriodComp::new(uod.value().grain, integer.value().value))) - } - ); - b.rule_3(" et ", - duration_check!(|duration: &DurationValue| !duration.suffixed), - b.reg(r#"et"#)?, - duration_check!(|duration: &DurationValue| !duration.prefixed), - |a, _, b| Ok(a.value() + b.value()) - ); - b.rule_2(" ", - duration_check!(|duration: &DurationValue| !duration.suffixed), - duration_check!(|duration: &DurationValue| !duration.prefixed), - |a, b| Ok(a.value() + b.value()) - ); - b.rule_2(" ", - duration_check!(|duration: &DurationValue| !duration.prefixed), - integer_check_by_range!(0), - |duration, integer| helpers::compose_duration_with_integer(duration.value(), integer.value()) - ); - b.rule_2("dans ", - b.reg(r#"dans"#)?, - duration_check!(), - |_, duration| duration.value().in_present() - ); - b.rule_2(" plus tard", - duration_check!(), - b.reg(r"plus tard")?, - |duration, _| duration.value().in_present() - ); - b.rule_2("environ ", - b.reg(r#"environ|approximativement|à peu près|presque"#)?, - duration_check!(), - |_, duration| Ok(duration.value().clone().precision(Precision::Approximate)) - ); - b.rule_2(" environ", - duration_check!(), - b.reg(r#"environ|approximativement|à peu près"#)?, - |duration, _| Ok(duration.value().clone().precision(Precision::Approximate)) - ); - b.rule_2("exactement ", - b.reg(r#"exactement|précisément"#)?, - duration_check!(), - |_, duration| Ok(duration.value().clone().precision(Precision::Exact)) - ); - b.rule_2(" exactement", - duration_check!(), - b.reg(r#"exactement|précisément|pile"#)?, - |duration, _| Ok(duration.value().clone().precision(Precision::Exact)) - ); - b.rule_2("pendant ", - b.reg(r#"pendant|durant|pour"#)?, - duration_check!(), - |_, duration| Ok(duration.value().clone().prefixed()) - ); - b.rule_2("une durée de ", - b.reg(r#"une dur[ée]e d['e]"#)?, - duration_check!(), - |_, duration| Ok(duration.value().clone().prefixed()) - ); - b.rule_2("il y a ", - b.reg(r#"il y a"#)?, - duration_check!(), - |_, duration| duration.value().ago() - ); - b.rule_2("depuis ", - b.reg(r#"depuis|[cç]a fait"#)?, - duration_check!(), - |_, duration| { - duration.value().ago()? - .span_to(&helpers::cycle_nth(Grain::Second, 0)?, false) - }); - b.rule_3(" apres ", - duration_check!(), - b.reg(r#"apr[eè]s"#)?, - datetime_check!(), - |duration, _, datetime| duration.value().after(datetime.value()) - ); - b.rule_3(" avant ", - duration_check!(), - b.reg(r#"avant"#)?, - datetime_check!(), - |duration, _, datetime| duration.value().before(datetime.value()) - ); - Ok(()) -} +/* DATETIME - CYCLE DEFINITIONS */ pub fn rules_cycle(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.rule_1_terminal("seconde (cycle)", b.reg(r#"secondes?"#)?, @@ -381,6 +39,12 @@ pub fn rules_cycle(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.reg(r#"trimestres?"#)?, |_| CycleValue::new(Grain::Quarter) ); + Ok(()) +} + + +pub fn rules_datetime_with_cycle(b: &mut RuleSetBuilder) -> RustlingResult<()> { + b.rule_2("ce|dans le ", b.reg(r#"(?:cet?t?e?s?)|(?:dans l[ae']? ?)"#)?, cycle_check!(), @@ -392,7 +56,7 @@ pub fn rules_cycle(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.reg(r#"-?ci"#)?, |_, cycle, _| helpers::cycle_nth(cycle.value().grain, 0) ); - + b.rule_3("le dernier", b.reg(r#"l[ae']? ?"#)?, cycle_check!(), @@ -529,6 +193,80 @@ pub fn rules_cycle(b: &mut RuleSetBuilder) -> RustlingResult<()> { Ok(()) } +pub fn rules_datetime_with_duration(b: &mut RuleSetBuilder) -> RustlingResult<()> { + b.rule_2("il y a ", + b.reg(r#"il y a"#)?, + duration_check!(), + |_, duration| duration.value().ago() + ); + // With "depuis/d'ici", interpretation of duration ambiguous with time-of-day we choose time-of-day + // I.e. "x heures (y)", but not "x heures et y minutes", "x minutes", etc. + // FIXME: some time-of-day patterns should be removed, e.g. "x heures y minutes" - they are not + // proper time-of-day, but they can be duration expressions + // TODO: check if Grain::Second here breaks DatePeriod - cf. implem. of equivalent in English? + b.rule_2("depuis ", + b.reg(r#"depuis|[cç]a fait"#)?, + duration_check!(), + |_, duration| { + if duration.value().get_coarser_grain() == Grain::Hour { + return Err(RuleError::Invalid.into()) + } + duration.value().ago()? + .span_to(&helpers::cycle_nth(Grain::Second, 0)?, false) + }); + b.rule_2("d'ici ", + b.reg(r#"d'ici|dans l(?:'|es?)"#)?, + duration_check!(), + |_, duration| { + let duration_grain = duration.value().get_coarser_grain(); + // Priority to d'ici + if duration_grain == Grain::Hour && + // FIXME: There must be a better way to do this check! + duration.value().period.0.get(Grain::Hour as usize).unwrap_or(&0) <= &23 { + return Err(RuleError::Invalid.into()) + } + let grain = if duration_grain.is_date_grain() { Grain::Day } else { Grain::Second }; + let start = helpers::cycle_nth(grain, 0)?; + let end = if grain == Grain::Day { duration.value().in_present_day()? } else { duration.value().in_present()? }; + start.span_to(&end, true) + } + ); + b.rule_2("dans le ", + b.reg(r#"dans l(?:'|es?)"#)?, + duration_check!(), + |_, duration| { + let duration_grain = duration.value().get_coarser_grain(); + let grain = if duration_grain.is_date_grain() { Grain::Day } else { Grain::Second }; + let start = helpers::cycle_nth(grain, 0)?; + let end = if grain == Grain::Day { duration.value().in_present_day()? } else { duration.value().in_present()? }; + start.span_to(&end, false) + } + ); + b.rule_3(" apres ", + duration_check!(), + b.reg(r#"apr[eè]s"#)?, + datetime_check!(), + |duration, _, datetime| duration.value().after(datetime.value()) + ); + b.rule_3(" avant ", + duration_check!(), + b.reg(r#"avant"#)?, + datetime_check!(), + |duration, _, datetime| duration.value().before(datetime.value()) + ); + b.rule_2("dans ", + b.reg(r#"dans"#)?, + duration_check!(), + |_, duration| duration.value().in_present() + ); + b.rule_2(" plus tard", + duration_check!(), + b.reg(r"plus tard")?, + |duration, _| duration.value().in_present() + ); + Ok(()) +} + pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.rule_2("intersect", datetime_check!(|datetime: &DatetimeValue| !datetime.latent), @@ -634,103 +372,6 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.reg(r#"décembre|decembre|déc\.?|dec\.?"#)?, |_| helpers::month(12) ); - b.rule_1_terminal("noel", - b.reg(r#"(?:jour de )?no[eë]l"#)?, - |_| Ok(helpers::month_day(12, 25)?.form(Form::Celebration)) - ); - b.rule_1_terminal("soir de noël", - b.reg(r#"(soir(?:ée)?|veille) de no[eë]l"#)?, - |_| { - let start = helpers::month_day(12, 24)?.intersect(&helpers::hour(18, false)?)?; - let end = helpers::month_day(12, 25)?.intersect(&helpers::hour(0, false)?)?; - Ok(start.span_to(&end, false)? - .form(Form::Celebration)) - } - ); - b.rule_1_terminal("jour de l'an", - b.reg(r#"(?:le )?(?:jour de l'|nouvel )an"#)?, - |_| Ok(helpers::month_day(1, 1)?.form(Form::Celebration)) - ); - b.rule_1_terminal("toussaint", - b.reg(r#"(?:(?:la |la journée de la |jour de la )?toussaint|jour des morts)"#)?, - |_| Ok(helpers::month_day(11, 1)?.form(Form::Celebration)) - ); - b.rule_1_terminal("Armistice", - b.reg(r#"(?:pour )?l'armistice"#)?, - |_| Ok(helpers::month_day(11, 11)?.form(Form::Celebration)) - ); - b.rule_1_terminal("Saint Etienne (Alsace)", - b.reg(r#"(?:(?:le jour|la f[eê]te) de )?la (?:saint|st) [eé]tienne"#)?, - |_| Ok(helpers::month_day(12, 26)?.form(Form::Celebration)) - ); - b.rule_1_terminal("jeudi saint", - b.reg(r#"(?:le )?jeudi saint"#)?, - |_| Ok(helpers::cycle_nth_after(Grain::Day, -3, &helpers::easter()?)? - .form(Form::Celebration)) - ); - b.rule_1_terminal("vendredi saint", - b.reg(r#"(?:le )?vendredi saint"#)?, - |_| Ok(helpers::cycle_nth_after(Grain::Day, -2, &helpers::easter()?)? - .form(Form::Celebration)) - ); - b.rule_1_terminal("samedi saint", - b.reg(r#"(?:le )?samedi saint"#)?, - |_| Ok(helpers::cycle_nth_after(Grain::Day, -1, &helpers::easter()?)? - .form(Form::Celebration)) - ); - b.rule_1_terminal("pâques", - b.reg(r#"(?:la f[eê]te de |le jour de |le dimanche de )?p[âa]ques"#)?, - |_| Ok(helpers::easter()?.form(Form::Celebration)) - ); - b.rule_1_terminal("le lundi de pâques", - b.reg(r#"le lundi de p[âa]ques"#)?, - |_| Ok(helpers::cycle_nth_after(Grain::Day, 1, &helpers::easter()?)? - .form(Form::Celebration)) - ); - b.rule_1_terminal("ascension", - b.reg(r#"(?:la f[eê]te de l'|le jeudi de l'|l'|le jour de l')ascension"#)?, - |_| Ok(helpers::cycle_nth_after(Grain::Day, 39, &helpers::easter()?)? - .form(Form::Celebration)) - - ); - b.rule_1_terminal("pencôte", - b.reg(r#"(?:la f[eê]te de la |la |le lundi de la )?penc[oô]te"#)?, - |_| Ok(helpers::cycle_nth_after(Grain::Day, 49, &helpers::easter()?)? - .form(Form::Celebration)) - ); - b.rule_1_terminal("1er mai", - b.reg(r#"(?:la )?f(e|ê)te du travail"#)?, - |_| Ok(helpers::month_day(5, 1)?.form(Form::Celebration)) - ); - b.rule_1_terminal("fêtes des pères", - b.reg(r#"(?:la )?f[eê]te des p[eè]res"#)?, - |_| { - let sundays_of_june = helpers::month(6)?.intersect(&helpers::day_of_week(Weekday::Sun)?)?; - let second_week_of_june = helpers::cycle_nth_after(Grain::Week, 2, &helpers::month_day(6, 1)?)?; - Ok(sundays_of_june.intersect(&second_week_of_june)? // third sunday of June - .form(Form::Celebration)) - } - ); - b.rule_1_terminal("fêtes des mères", - b.reg(r#"(?:la )?f[eê]te des m[eè]res"#)?, - |_| { - // It is the last last sunday of may - // If it is the same day as the Pentecost, it is the first sunday of june - // This case is not supported for now - Ok(helpers::day_of_week(Weekday::Sun)?.last_of(&helpers::month(5)?)? - .form(Form::Celebration)) - } - ); - b.rule_1_terminal("fête nationale", - b.reg(r#"(?:la )?f[eê]te (?:nationale|du (?:14|quatorze) juillet)"#)?, - |_| Ok(helpers::month_day(7, 14)? - .form(Form::Celebration)) - ); - b.rule_1_terminal("assomption", - b.reg(r#"(?:la f[eê]te de |le jour de )?l'assomption"#)?, - |_| Ok(helpers::month_day(8, 15)? - .form(Form::Celebration)) - ); b.rule_1_terminal("maintenant", b.reg(r#"maintenant|(?:tout de suite)"#)?, |_| helpers::cycle_nth(Grain::Second, 0) @@ -755,7 +396,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { .span_to(&month, false)? .latent() .form(Form::PartOfMonth)) - } + } ); b.rule_1_terminal("après-demain", b.reg(r#"apr(?:e|è)s[- ]?demain"#)?, @@ -965,10 +606,10 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.rule_1_terminal("hh:mm:ss", b.reg(r#"((?:[01]?\d)|(?:2[0-3]))[:.]([0-5]\d)[:.]([0-5]\d)"#)?, |text_match| helpers::hour_minute_second( - text_match.group(1).parse()?, + text_match.group(1).parse()?, text_match.group(2).parse()?, text_match.group(3).parse()?, - false + false ) ); @@ -1259,7 +900,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { .span_to(&helpers::hour(23, false)?, false)? .form(Form::Meal)) ); - b.rule_1_terminal("nuit", + b.rule_1_terminal("nuit", b.reg(r#"nuit"#)?, |_| { Ok(helpers::hour(22, false)? @@ -1624,641 +1265,3 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { ); Ok(()) } - -pub fn rules_temperature(b: &mut RuleSetBuilder) -> RustlingResult<()> { - b.rule_1("number as temp", - number_check!(), - |a| { - Ok(TemperatureValue { - value: a.value().value(), - unit: None, - latent: true, - }) - }); - b.rule_2(" degrees", - temperature_check!(), - b.reg(r#"(?:deg(?:r[éeè])?s?\.?)|°"#)?, - |a, _| { - Ok(TemperatureValue { - value: a.value().value, - unit: Some("degree"), - latent: false, - }) - }); - b.rule_2(" Celcius", - temperature_check!(), - b.reg(r#"centigrades?|c(?:el[cs]?(?:ius)?)?\.?"#)?, - |a, _| { - Ok(TemperatureValue { - value: a.value().value, - unit: Some("celsius"), - latent: false, - }) - }); - b.rule_2(" Fahrenheit", - temperature_check!(), - b.reg(r#"f(?:ah?reh?n(?:h?eit)?)?\.?"#)?, - |a, _| { - Ok(TemperatureValue { - value: a.value().value, - unit: Some("fahrenheit"), - latent: false, - }) - }); - b.rule_2(" Kelvin", - temperature_check!(), - b.reg(r#"k(?:elvin)?\.?"#)?, - |a, _| { - Ok(TemperatureValue { - value: a.value().value, - unit: Some("kelvin"), - latent: false, - }) - }); - b.rule_2(" en dessous de zero", - temperature_check!(|temp: &TemperatureValue| !temp.latent), - b.reg(r#"en dessous de (?:0|z[ée]ro)"#)?, - |a, _| { - Ok(TemperatureValue { - value: -1.0 * a.value().value, - latent: false, - ..*a.value() - }) - }); - Ok(()) -} - -pub fn rules_numbers(b: &mut RuleSetBuilder) -> RustlingResult<()> { - b.rule_2("intersect", - number_check!(|number: &NumberValue| number.grain().unwrap_or(0) > 1), - number_check!(), - |a, b| helpers::compose_numbers(&a.value(), &b.value())); - b.rule_1_terminal( - "number (0..16)", - b.reg(r#"(z[eé]ro|une?|deux|trois|quatre|cinq|six|sept|huit|neuf|dix|onze|douze|treize|quatorze|quinze|seize)"#)?, - |text_match| { - let value = match text_match.group(1).as_ref() { - "zéro" => 0, - "zero" => 0, - "un" => 1, - "une" => 1, - "deux" => 2, - "trois" => 3, - "quatre" => 4, - "cinq" => 5, - "six" => 6, - "sept" => 7, - "huit" => 8, - "neuf" => 9, - "dix" => 10, - "onze" => 11, - "douze" => 12, - "treize" => 13, - "quatorze" => 14, - "quinze" => 15, - "seize" => 16, - _ => return Err(RuleError::Invalid.into()), - }; - IntegerValue::new(value) - }); - b.rule_1_terminal("number (20..60)", - b.reg(r#"(vingt|trente|quarante|cinquante|soixante)"#)?, - |text_match| { - let value = match text_match.group(1).as_ref() { - "vingt" => 20, - "trente" => 30, - "quarante" => 40, - "cinquante" => 50, - "soixante" => 60, - _ => return Err(RuleError::Invalid.into()), - }; - IntegerValue::new(value) - }); - b.rule_2("number (17..19)", - integer_check_by_range!(10, 10), - integer_check_by_range!(7, 9), - |_, b| IntegerValue::new(b.value().value + 10)); - b.rule_3("number (17..19)", - integer_check_by_range!(10, 10), - b.reg(r"-")?, - integer_check_by_range!(7, 9), - |_, _, b| IntegerValue::new(b.value().value + 10)); - b.rule_2_terminal("number 80", - b.reg(r#"quatre"#)?, - b.reg(r#"vingts?"#)?, - |_, _| IntegerValue::new(80)); - b.rule_3_terminal("number 80", - b.reg(r#"quatre"#)?, - b.reg(r"-")?, - b.reg(r#"vingts?"#)?, - |_, _, _| IntegerValue::new(80)); - b.rule_3("numbers 21 31 41 51", - integer_check_by_range!(20, 50, |integer: &IntegerValue| integer.value % 10 == 0), - b.reg(r#"-?et-?"#)?, - integer_check_by_range!(1, 1), - |a, _, b| IntegerValue::new(a.value().value + b.value().value)); - b.rule_2("numbers 22..29 32..39 .. 52..59", - integer_check_by_range!(20, 50, |integer: &IntegerValue| integer.value % 10 == 0), - integer_check_by_range!(2, 9), - |a, b| IntegerValue::new(a.value().value + b.value().value)); - b.rule_3("numbers 22..29 32..39 .. 52..59", - integer_check_by_range!(20, 50, |integer: &IntegerValue| integer.value % 10 == 0), - b.reg(r"-")?, - integer_check_by_range!(2, 9), - |a, _, b| IntegerValue::new(a.value().value + b.value().value)); - b.rule_3("numbers 61 71", - integer_check_by_range!(60, 60), - b.reg(r#"-?et-?"#)?, - integer_check_by_range!(1, - 11, - |integer: &IntegerValue| integer.value == 1 || integer.value == 11), - |a, _, b| IntegerValue::new(a.value().value + b.value().value)); - b.rule_2("numbers 81 91", - integer_check_by_range!(80, 80), - integer_check_by_range!(1, - 11, - |integer: &IntegerValue| integer.value == 1 || integer.value == 11), - |a, b| IntegerValue::new(a.value().value + b.value().value)); - b.rule_3("numbers 81 91", - integer_check_by_range!(80, 80), - b.reg(r#"-"#)?, - integer_check_by_range!(1, - 11, - |integer: &IntegerValue| integer.value == 1 || integer.value == 11), - |a, _, b| IntegerValue::new(a.value().value + b.value().value)); - b.rule_2("numbers 62..69 .. 92..99", - integer_check_by_range!(60, - 80, - |integer: &IntegerValue| integer.value == 60 || integer.value == 80), - integer_check_by_range!(2, 19), - |a, b| IntegerValue::new(a.value().value + b.value().value)); - b.rule_3("numbers 62..69 .. 92..99", - integer_check_by_range!(60, - 80, - |integer: &IntegerValue| integer.value == 60 || integer.value == 80), - b.reg(r"-")?, - integer_check_by_range!(2, 19), - |a, _, b| IntegerValue::new(a.value().value + b.value().value)); - b.rule_1_terminal("hundred", - b.reg(r#"cents?"#)?, - |_| IntegerValue::new_with_grain(100, 2) - ); - b.rule_1_terminal("thousand", - b.reg(r#"milles?"#)?, - |_| IntegerValue::new_with_grain(1000, 3) - ); - b.rule_1_terminal("million", - b.reg(r#"millions?"#)?, - |_| IntegerValue::new_with_grain(1000000, 6) - ); - b.rule_1_terminal("billion", - b.reg(r#"milliards?"#)?, - |_| IntegerValue::new_with_grain(1000000000, 9) - ); - b.rule_2("number hundreds", - integer_check_by_range!(1, 99), - b.reg(r#"cents?"#)?, - |a, _| { - Ok(IntegerValue { - value: a.value().value * 100, - grain: Some(2), - ..IntegerValue::default() - }) - }); - b.rule_2("number thousands", - integer_check_by_range!(1, 999), - b.reg(r#"milles?"#)?, - |a, _| { - Ok(IntegerValue { - value: a.value().value * 1000, - grain: Some(3), - ..IntegerValue::default() - }) - }); - b.rule_2("number millions", - integer_check_by_range!(1, 999), - b.reg(r#"millions?"#)?, - |a, _| { - Ok(IntegerValue { - value: a.value().value * 1000000, - grain: Some(6), - ..IntegerValue::default() - }) - }); - b.rule_2("number billions", - integer_check_by_range!(1, 999), - b.reg(r#"milliards?"#)?, - |a, _| { - Ok(IntegerValue { - value: a.value().value * 1000000000, - grain: Some(9), - ..IntegerValue::default() - }) - }); - b.rule_1_terminal("integer (numeric)", - b.reg(r#"(\d{1,18})"#)?, - |text_match| { - let value: i64 = text_match.group(1).parse()?; - IntegerValue::new(value) - }); - b.rule_1_terminal("integer with thousands separator .", - b.reg(r#"(\d{1,3}(\.\d\d\d){1,5})"#)?, - |text_match| { - let reformatted_string = text_match.group(1).replace(".", ""); - let value: i64 = reformatted_string.parse()?; - IntegerValue::new(value) - }); - b.rule_1_terminal("decimal number", - b.reg(r#"(\d*,\d+)"#)?, - |text_match| { - let reformatted_string = text_match.group(1).replace(",", "."); - let value: f32 = reformatted_string.parse()?; - FloatValue::new(value) - }); - b.rule_3("number dot number", - number_check!(|number: &NumberValue| !number.prefixed()), - b.reg(r#"virgule|point"#)?, - number_check!(|number: &NumberValue| !number.suffixed()), - |a, _, b| { - let power = b.value().value().to_string().chars().count(); - let coeff = 10.0_f32.powf(-1.0 * power as f32); - Ok(FloatValue { - value: b.value().value() * coeff + a.value().value(), - ..FloatValue::default() - }) - }); - b.rule_4("number dot zero ... number", - number_check!(|number: &NumberValue| !number.prefixed()), - b.reg(r#"virgule|point"#)?, - b.reg(r#"(?:(?:z[eé]ro )*(?:z[eé]ro))"#)?, - number_check!(|number: &NumberValue| !number.suffixed()), - |a, _, zeros, b| { - let power = zeros.group(0).split_whitespace().count() + b.value().value().to_string().chars().count(); - let coeff = 10.0_f32.powf(-1.0 * power as f32); - Ok(FloatValue { - value: b.value().value() * coeff + a.value().value(), - ..FloatValue::default() - }) - }); - b.rule_1_terminal("decimal with thousands separator", - b.reg(r#"(\d+(\.\d\d\d)+,\d+)"#)?, - |text_match| { - let reformatted_string = text_match.group(1).replace(".", "").replace(",", "."); - let value: f32 = reformatted_string.parse()?; - FloatValue::new(value) - }); - b.rule_2("numbers prefix with -, negative or minus", - b.reg(r#"-|moins"#)?, - number_check!(|number: &NumberValue| !number.prefixed()), - |_, a| -> RuleResult { - Ok(match a.value().clone() { - // checked - NumberValue::Integer(integer) => { - IntegerValue { - value: integer.value * -1, - prefixed: true, - ..integer - } - .into() - } - NumberValue::Float(float) => { - FloatValue { - value: float.value * -1.0, - prefixed: true, - ..float - } - .into() - } - }) - }); - b.rule_2("numbers prefix with +, positive", - b.reg(r#"\+"#)?, - number_check!(|number: &NumberValue| !number.prefixed()), - |_, a| -> RuleResult { - Ok(match a.value().clone() { - // checked - NumberValue::Integer(integer) => { - IntegerValue { - prefixed: true, - ..integer - } - .into() - } - NumberValue::Float(float) => { - FloatValue { - prefixed: true, - ..float - } - .into() - } - }) - } - ); - b.rule_2("numbers suffixes (K, M, G)", - number_check!(|number: &NumberValue| !number.suffixed()), - b.reg_neg_lh(r#"([kmg])"#, r#"^[\W\$€]"#)?, - |a, text_match| -> RuleResult { - let multiplier = match text_match.group(0).as_ref() { - "k" => 1000, - "m" => 1000000, - "g" => 1000000000, - _ => return Err(RuleError::Invalid.into()), - }; - Ok(match a.value().clone() { // checked - NumberValue::Integer(integer) => { - IntegerValue { - value: integer.value * multiplier, - suffixed: true, - ..integer - } - .into() - } - NumberValue::Float(float) => { - let product = float.value * (multiplier as f32); - if product.floor() == product { - IntegerValue { - value: product as i64, - suffixed: true, - ..IntegerValue::default() - } - .into() - } else { - FloatValue { - value: product, - suffixed: true, - ..float - } - .into() - } - } - }) - }); - b.rule_1_terminal("(douzaine ... soixantaine)", - b.reg(r#"(demi[ -]douz|diz|douz|quinz|vingt|trent|quarant|cinquant|soixant|cent)aines?"#)?, - |text_match| { - let value = match text_match.group(1).as_ref() { - "demi douz" => 6, - "demi-douz" => 6, - "diz" => 10, - "douz" => 12, - "quinz" => 15, - "vingt" => 20, - "trent" => 30, - "quarant" => 40, - "cinquant" => 50, - "soixant" => 60, - "cent" => 100, - _ => return Err(RuleError::Invalid.into()), - }; - Ok(IntegerValue { - value, - group: true, - .. IntegerValue::default() - }) - } - ); - b.rule_2("number dozen", - integer_check_by_range!(1, 9), - integer_check!(|integer: &IntegerValue| integer.group), - |a, b| { - Ok(IntegerValue { - value: a.value().value * b.value().value, - grain: b.value().grain, - group: true, - ..IntegerValue::default() - }) - }); - b.rule_1_terminal("ordinal 0", - b.reg(r#"z[eé]rot?i[eè]me"#)?, - |_| { - Ok(OrdinalValue::new(0)) - } - ); - b.rule_1_terminal("ordinal 1", - b.reg(r#"premi[eè]re?"#)?, - |_| { - Ok(OrdinalValue::new(1)) - } - ); - b.rule_1_terminal("ordinal 2", - b.reg(r#"seconde?|deuxi[eè]me"#)?, - |_| { - Ok(OrdinalValue::new(2)) - } - ); - b.rule_1_terminal( - "ordinals (premier..seizieme)", - b.reg(r#"(trois|quatr|cinqu|six|sept|huit|neuv|dix|onz|douz|treiz|quatorz|quinz|seiz)i[eè]me"#)?, - |text_match| { - let value = match text_match.group(1).as_ref() { - "trois" => 3, - "quatr" => 4, - "cinqu" => 5, - "six" => 6, - "sept" => 7, - "huit" => 8, - "neuv" => 9, - "dix" => 10, - "onz" => 11, - "douz" => 12, - "treiz" => 13, - "quatorz" => 14, - "quinz" => 15, - "seiz" => 16, - _ => return Err(RuleError::Invalid.into()), - }; - Ok(OrdinalValue::new(value)) - }); - b.rule_2("17ieme, 18ieme, 19ieme", - b.reg(r#"dix-?"#)?, - ordinal_check_by_range!(7, 9), - |_, ordinal| { - Ok(OrdinalValue::new(10 + ordinal.value().value)) - } - ); - b.rule_1_terminal("20ieme, 30ieme, 40ieme, 50ieme, 60ieme", - b.reg(r#"(vingt|trent|quarant|cinquant|soixant)i[èe]me"#)?, - |text_match| { - let value = match text_match.group(1).as_ref() { - "vingt" => 20, - "trent" => 30, - "quarant" => 40, - "cinquant" => 50, - "soixant" => 60, - _ => return Err(RuleError::Invalid.into()), - }; - Ok(OrdinalValue::new(value)) - } - ); - b.rule_1_terminal("80ieme", - b.reg(r#"quatre[- ]vingts?i[èe]me"#)?, - |_| { - Ok(OrdinalValue::new(80)) - } - ); - b.rule_2("22ieme...29ieme, 32ieme...39ieme, 42ieme...49ieme, 52ieme...59ieme", - integer_check_by_range!(20, 50, |integer: &IntegerValue| integer.value % 10 == 0), - ordinal_check_by_range!(2, 9), - |integer, ordinal| { - Ok(OrdinalValue::new(integer.value().value + ordinal.value().value)) - } - ); - b.rule_3("22ieme...29ieme, 32ieme...39ieme, 42ieme...49ieme, 52ieme...59ieme", - integer_check_by_range!(20, 50, |integer: &IntegerValue| integer.value % 10 == 0), - b.reg(r"-")?, - ordinal_check_by_range!(2, 9), - |integer, _, ordinal| { - Ok(OrdinalValue::new(integer.value().value + ordinal.value().value)) - } - ); - b.rule_2("62ieme...70ieme, 72ieme...79ieme, 90ieme, 92ieme...99ieme", - integer_check_by_range!(60, 80, |integer: &IntegerValue| integer.value == 60 || integer.value == 80), - ordinal_check_by_range!(2, 19), - |integer, ordinal| { - Ok(OrdinalValue::new(integer.value().value + ordinal.value().value)) - } - ); - b.rule_3("62ieme...70ieme, 72ieme...79ieme, 90ieme, 92ieme...99ieme", - integer_check_by_range!(60, 80, |integer: &IntegerValue| integer.value == 60 || integer.value == 80), - b.reg(r"-")?, - ordinal_check_by_range!(2, 19), - |integer, _, ordinal| { - Ok(OrdinalValue::new(integer.value().value + ordinal.value().value)) - } - ); - b.rule_2("21, 31, 41, 51, 61", - integer_check_by_range!(20, 60, |integer: &IntegerValue| integer.value % 10 == 0), - b.reg(r#"(?:et |-)uni[èe]me"#)?, - |integer, _| { - Ok(OrdinalValue::new(integer.value().value + 1)) - } - ); - b.rule_2("81", - integer_check_by_range!(80, 80), - b.reg(r#"(?:et )?uni[èe]me"#)?, - |integer, _| { - Ok(OrdinalValue::new(integer.value().value + 1)) - } - ); - b.rule_2("71, 91", - integer_check_by_range!(60, 60), - b.reg(r#"et onzi[eè]me"#)?, - |integer, _| { - Ok(OrdinalValue::new(integer.value().value + 11)) - } - ); - b.rule_2(" et demi", - integer_check_by_range!(0, 99), - b.reg(r#"et demie?"#)?, - |integer, _| { - FloatValue::new(integer.value().value as f32 + 0.5) - } - ); - b.rule_1_terminal("70, 80, 90 (Belgium and Switzerland)", - b.reg(r#"(sept|huit|non)ante"#)?, - |text_match| { - let value = match text_match.group(1).as_ref() { - "sept" => 70, - "huit" => 80, - "non" => 90, - _ => return Err(RuleError::Invalid.into()), - }; - IntegerValue::new(value) - } - ); - b.rule_1_terminal("71, 81, 91 (Belgium and Switzerland)", - b.reg(r#"(sept|huit|non)ante et une?"#)?, - |text_match| { - let value = match text_match.group(1).as_ref() { - "sept" => 71, - "huit" => 81, - "non" => 91, - _ => return Err(RuleError::Invalid.into()), - }; - IntegerValue::new(value) - } - ); - - b.rule_2("72..79, 82..89, 92..99, (Belgium and Switzerland)", - b.reg(r#"(sept|huit|non)ante"#)?, - integer_check_by_range!(2, 9), - |text_match, integer| { - let value = match text_match.group(1).as_ref() { - "sept" => 70, - "huit" => 80, - "non" => 90, - _ => return Err(RuleError::Invalid.into()), - }; - IntegerValue::new(value + integer.value().value) - } - ); - b.rule_1_terminal("ordinal (100, 1_000, 1_000_000)", - b.reg(r#"(cent|mill|million|milliard)i[èe]me"#)?, - |text_match| { - let (value, grain) = match text_match.group(1).as_ref() { - "cent" => (100, 2), - "mill" => (1_000, 3), - "million" => (1_000_000, 6), - "milliard" => (1_000_000_000, 9), - _ => return Err(RuleError::Invalid.into()), - }; - Ok(OrdinalValue::new_with_grain(value, grain)) - } - ); - - b.rule_2("ordinal (200..900, 2_000..9_000, 2_000_000..9_000_000_000)", - integer_check_by_range!(2, 999), - b.reg(r#"(cent|mill|million|milliard)i[èe]me"#)?, - |integer, text_match| { - let (value, grain) = match text_match.group(1).as_ref() { - "cent" => (100, 2), - "mill" => (1_000, 3), - "million" => (1_000_000, 6), - "milliard" => (1_000_000_000, 9), - _ => return Err(RuleError::Invalid.into()), - }; - Ok(OrdinalValue::new_with_grain(integer.value().value * value, grain)) - } - ); - - b.rule_2("ordinal (1_1_000..9_999_999_000)", - integer_check_by_range!(1000, 99_999_999_000), - ordinal_check!(|ordinal: &OrdinalValue| { - let grain = ordinal.grain.unwrap_or(0); - grain == 2 || grain % 3 == 0 - }), - |integer, ordinal| { - let grain = ordinal.value().grain.unwrap_or(0); - let next_grain = (grain / 3) * 3 + 3; - if integer.value().value % 10i64.pow(next_grain as u32) != 0 { return Err(RuleError::Invalid.into()); } - Ok(OrdinalValue::new(integer.value().value + ordinal.value().value)) - } - ); - - b.rule_2("ordinal (102...9_999_999)", - integer_check!(|integer: &IntegerValue| integer.value >= 100 || integer.value % 100 == 0), - ordinal_check_by_range!(2, 99), - |integer, ordinal| { - Ok(OrdinalValue::new(integer.value().value + ordinal.value().value)) - } - ); - b.rule_2("ordinal (101, 201, 301, ...)", - integer_check!(|integer: &IntegerValue| integer.value >= 100 || integer.value % 100 == 0), - b.reg(r#"(?:et |-)?uni[èe]me"#)?, - |integer, _| { - Ok(OrdinalValue::new(integer.value().value + 1)) - } - ); - b.rule_1_terminal("ordinal (digits)", - b.reg(r#"0*(\d+) ?(ere?|ère|ème|eme|ieme|ième)"#)?, - |text_match| { - let value: i64 = text_match.group(1).parse()?; - Ok(OrdinalValue::new(value)) - }); - b.rule_2("le ", - b.reg(r#"l[ea]"#)?, - ordinal_check!(), - |_, a| Ok((*a.value()).prefixed()) - ); - Ok(()) -} diff --git a/grammar/fr/src/rules_duration.rs b/grammar/fr/src/rules_duration.rs new file mode 100644 index 00000000..7347e9fb --- /dev/null +++ b/grammar/fr/src/rules_duration.rs @@ -0,0 +1,172 @@ +use rustling::*; +use rustling_ontology_values::dimension::*; +use rustling_ontology_values::helpers; +use rustling_ontology_moment::{Grain, PeriodComp, Period}; + +pub fn rules_duration(b: &mut RuleSetBuilder) -> RustlingResult<()> { + b.rule_1_terminal("seconde (unit-of-duration)", + b.reg(r#"sec(?:onde)?s?"#)?, + |_| Ok(UnitOfDurationValue::new(Grain::Second)) + ); + b.rule_1_terminal("minute (unit-of-duration)", + b.reg(r#"min(?:ute)?s?"#)?, + |_| Ok(UnitOfDurationValue::new(Grain::Minute)) + ); + b.rule_1_terminal("heure (unit-of-duration)", + b.reg(r#"h(?:eure)?s?"#)?, + |_| Ok(UnitOfDurationValue::new(Grain::Hour)) + ); + b.rule_1_terminal("jour (unit-of-duration)", + b.reg(r#"jour(?:n[ée]e?)?s?"#)?, + |_| Ok(UnitOfDurationValue::new(Grain::Day)) + ); + b.rule_1_terminal("semaine (unit-of-duration)", + b.reg(r#"semaines?"#)?, + |_| Ok(UnitOfDurationValue::new(Grain::Week)) + ); + b.rule_1_terminal("mois (unit-of-duration)", + b.reg(r#"mois?"#)?, + |_| Ok(UnitOfDurationValue::new(Grain::Month)) + ); + b.rule_1_terminal("année (unit-of-duration)", + b.reg(r#"an(?:n[ée]e?)?s?"#)?, + |_| Ok(UnitOfDurationValue::new(Grain::Year)) + ); + b.rule_1_terminal("trimestre (unit-of-duration)", + b.reg(r#"trimestres?"#)?, + |_| Ok(UnitOfDurationValue::new(Grain::Quarter)) + ); + b.rule_1_terminal("un quart heure", + b.reg(r#"(1/4\s?h(?:eure)?|(?:un|1) quart d'heure)"#)?, + |_| Ok(DurationValue::new(PeriodComp::minutes(15).into())) + ); + b.rule_1_terminal("une demi heure", + b.reg(r#"(?:1/2\s?h(?:eure)?|(?:1|une) demi(?:e)?(?:\s|-)heure)"#)?, + |_| Ok(DurationValue::new(PeriodComp::minutes(30).into())) + ); + b.rule_1_terminal("trois quarts d'heure", + b.reg(r#"(?:3/4\s?h(?:eure)?|(?:3|trois) quart(?:s)? d'heure)"#)?, + |_| Ok(DurationValue::new(PeriodComp::minutes(45).into())) + ); + b.rule_2(" ", + integer_check_by_range!(0), + unit_of_duration_check!(), + |integer, unit| Ok(DurationValue::new(PeriodComp::new(unit.value().grain, integer.value().value).into())) + ); + b.rule_3(" de ", + integer_check!(|integer: &IntegerValue| integer.value >= 0 && integer.group), + b.reg(r#"d[e']"#)?, + unit_of_duration_check!(), + |integer, _, unit| Ok(DurationValue::new(PeriodComp::new(unit.value().grain, integer.value().value).into())) + ); + b.rule_4(" h ", + integer_check_by_range!(0), + b.reg(r#"h(?:eures?)?"#)?, + integer_check_by_range!(0,59), + b.reg(r#"m(?:inutes?)?"#)?, + |hour, _, minute, _| { + let hour_period = Period::from(PeriodComp::new(Grain::Hour, hour.value().clone().value)); + let minute_period = Period::from(PeriodComp::new(Grain::Minute, minute.value().clone().value)); + Ok(DurationValue::new(hour_period + minute_period)) + } + ); + b.rule_3(" et quart", + integer_check_by_range!(0), + unit_of_duration_check!(), + b.reg(r#"et quart"#)?, + |integer, uod, _| { + let quarter_period: Period = uod.value().grain.quarter_period().map(|a| a.into()).ok_or_else(|| RuleError::Invalid)?; + Ok(DurationValue::new(quarter_period + PeriodComp::new(uod.value().grain, integer.value().value))) + } + ); + b.rule_3(" et demie", + integer_check_by_range!(0), + unit_of_duration_check!(), + b.reg(r#"et demie?"#)?, + |integer, uod, _| { + let half_period: Period = uod.value().grain.half_period().map(|a| a.into()).ok_or_else(|| RuleError::Invalid)?; + Ok(DurationValue::new(half_period + PeriodComp::new(uod.value().grain, integer.value().value))) + } + ); + b.rule_3(" et ", + duration_check!(|duration: &DurationValue| !duration.suffixed), + b.reg(r#"et"#)?, + duration_check!(|duration: &DurationValue| !duration.prefixed), + |a, _, b| Ok(a.value() + b.value()) + ); + b.rule_2(" ", + duration_check!(|duration: &DurationValue| !duration.suffixed), + duration_check!(|duration: &DurationValue| !duration.prefixed), + |a, b| Ok(a.value() + b.value()) + ); + b.rule_2(" ", + duration_check!(|duration: &DurationValue| !duration.prefixed), + integer_check_by_range!(0), + |duration, integer| helpers::compose_duration_with_integer(duration.value(), integer.value()) + ); + b.rule_2("dans ", + b.reg(r#"dans"#)?, + duration_check!(), + |_, duration| duration.value().in_present() + ); + b.rule_2(" plus tard", + duration_check!(), + b.reg(r"plus tard")?, + |duration, _| duration.value().in_present() + ); + b.rule_2("environ ", + b.reg(r#"environ|approximativement|à peu près|presque"#)?, + duration_check!(), + |_, duration| Ok(duration.value().clone().precision(Precision::Approximate)) + ); + b.rule_2(" environ", + duration_check!(), + b.reg(r#"environ|approximativement|à peu près"#)?, + |duration, _| Ok(duration.value().clone().precision(Precision::Approximate)) + ); + b.rule_2("exactement ", + b.reg(r#"exactement|précisément"#)?, + duration_check!(), + |_, duration| Ok(duration.value().clone().precision(Precision::Exact)) + ); + b.rule_2(" exactement", + duration_check!(), + b.reg(r#"exactement|précisément|pile"#)?, + |duration, _| Ok(duration.value().clone().precision(Precision::Exact)) + ); + b.rule_2("pendant ", + b.reg(r#"pendant|durant|pour"#)?, + duration_check!(), + |_, duration| Ok(duration.value().clone().prefixed()) + ); + b.rule_2("une durée de ", + b.reg(r#"une dur[ée]e d['e]"#)?, + duration_check!(), + |_, duration| Ok(duration.value().clone().prefixed()) + ); + b.rule_2("il y a ", + b.reg(r#"il y a"#)?, + duration_check!(), + |_, duration| duration.value().ago() + ); + b.rule_2("depuis ", + b.reg(r#"depuis|[cç]a fait"#)?, + duration_check!(), + |_, duration| { + duration.value().ago()? + .span_to(&helpers::cycle_nth(Grain::Second, 0)?, false) + }); + b.rule_3(" apres ", + duration_check!(), + b.reg(r#"apr[eè]s"#)?, + datetime_check!(), + |duration, _, datetime| duration.value().after(datetime.value()) + ); + b.rule_3(" avant ", + duration_check!(), + b.reg(r#"avant"#)?, + datetime_check!(), + |duration, _, datetime| duration.value().before(datetime.value()) + ); + Ok(()) +} \ No newline at end of file diff --git a/grammar/fr/src/rules_number.rs b/grammar/fr/src/rules_number.rs new file mode 100644 index 00000000..29f98e14 --- /dev/null +++ b/grammar/fr/src/rules_number.rs @@ -0,0 +1,580 @@ +use std::f32; + +use rustling::*; +use rustling_ontology_values::dimension::*; +use rustling_ontology_values::helpers; + +pub fn rules_numbers(b: &mut RuleSetBuilder) -> RustlingResult<()> { + b.rule_2("intersect", + number_check!(|number: &NumberValue| number.grain().unwrap_or(0) > 1), + number_check!(), + |a, b| helpers::compose_numbers(&a.value(), &b.value())); + b.rule_1_terminal( + "number (0..16)", + b.reg(r#"(z[eé]ro|une?|deux|trois|quatre|cinq|six|sept|huit|neuf|dix|onze|douze|treize|quatorze|quinze|seize)"#)?, + |text_match| { + let value = match text_match.group(1).as_ref() { + "zéro" => 0, + "zero" => 0, + "un" => 1, + "une" => 1, + "deux" => 2, + "trois" => 3, + "quatre" => 4, + "cinq" => 5, + "six" => 6, + "sept" => 7, + "huit" => 8, + "neuf" => 9, + "dix" => 10, + "onze" => 11, + "douze" => 12, + "treize" => 13, + "quatorze" => 14, + "quinze" => 15, + "seize" => 16, + _ => return Err(RuleError::Invalid.into()), + }; + IntegerValue::new(value) + }); + b.rule_1_terminal("number (20..60)", + b.reg(r#"(vingt|trente|quarante|cinquante|soixante)"#)?, + |text_match| { + let value = match text_match.group(1).as_ref() { + "vingt" => 20, + "trente" => 30, + "quarante" => 40, + "cinquante" => 50, + "soixante" => 60, + _ => return Err(RuleError::Invalid.into()), + }; + IntegerValue::new(value) + }); + b.rule_2("number (17..19)", + integer_check_by_range!(10, 10), + integer_check_by_range!(7, 9), + |_, b| IntegerValue::new(b.value().value + 10)); + b.rule_3("number (17..19)", + integer_check_by_range!(10, 10), + b.reg(r"-")?, + integer_check_by_range!(7, 9), + |_, _, b| IntegerValue::new(b.value().value + 10)); + b.rule_2_terminal("number 80", + b.reg(r#"quatre"#)?, + b.reg(r#"vingts?"#)?, + |_, _| IntegerValue::new(80)); + b.rule_3_terminal("number 80", + b.reg(r#"quatre"#)?, + b.reg(r"-")?, + b.reg(r#"vingts?"#)?, + |_, _, _| IntegerValue::new(80)); + b.rule_3("numbers 21 31 41 51", + integer_check_by_range!(20, 50, |integer: &IntegerValue| integer.value % 10 == 0), + b.reg(r#"-?et-?"#)?, + integer_check_by_range!(1, 1), + |a, _, b| IntegerValue::new(a.value().value + b.value().value)); + b.rule_2("numbers 22..29 32..39 .. 52..59", + integer_check_by_range!(20, 50, |integer: &IntegerValue| integer.value % 10 == 0), + integer_check_by_range!(2, 9), + |a, b| IntegerValue::new(a.value().value + b.value().value)); + b.rule_3("numbers 22..29 32..39 .. 52..59", + integer_check_by_range!(20, 50, |integer: &IntegerValue| integer.value % 10 == 0), + b.reg(r"-")?, + integer_check_by_range!(2, 9), + |a, _, b| IntegerValue::new(a.value().value + b.value().value)); + b.rule_3("numbers 61 71", + integer_check_by_range!(60, 60), + b.reg(r#"-?et-?"#)?, + integer_check_by_range!(1, + 11, + |integer: &IntegerValue| integer.value == 1 || integer.value == 11), + |a, _, b| IntegerValue::new(a.value().value + b.value().value)); + b.rule_2("numbers 81 91", + integer_check_by_range!(80, 80), + integer_check_by_range!(1, + 11, + |integer: &IntegerValue| integer.value == 1 || integer.value == 11), + |a, b| IntegerValue::new(a.value().value + b.value().value)); + b.rule_3("numbers 81 91", + integer_check_by_range!(80, 80), + b.reg(r#"-"#)?, + integer_check_by_range!(1, + 11, + |integer: &IntegerValue| integer.value == 1 || integer.value == 11), + |a, _, b| IntegerValue::new(a.value().value + b.value().value)); + b.rule_2("numbers 62..69 .. 92..99", + integer_check_by_range!(60, + 80, + |integer: &IntegerValue| integer.value == 60 || integer.value == 80), + integer_check_by_range!(2, 19), + |a, b| IntegerValue::new(a.value().value + b.value().value)); + b.rule_3("numbers 62..69 .. 92..99", + integer_check_by_range!(60, + 80, + |integer: &IntegerValue| integer.value == 60 || integer.value == 80), + b.reg(r"-")?, + integer_check_by_range!(2, 19), + |a, _, b| IntegerValue::new(a.value().value + b.value().value)); + b.rule_1_terminal("hundred", + b.reg(r#"cents?"#)?, + |_| IntegerValue::new_with_grain(100, 2) + ); + b.rule_1_terminal("thousand", + b.reg(r#"milles?"#)?, + |_| IntegerValue::new_with_grain(1000, 3) + ); + b.rule_1_terminal("million", + b.reg(r#"millions?"#)?, + |_| IntegerValue::new_with_grain(1000000, 6) + ); + b.rule_1_terminal("billion", + b.reg(r#"milliards?"#)?, + |_| IntegerValue::new_with_grain(1000000000, 9) + ); + b.rule_2("number hundreds", + integer_check_by_range!(1, 99), + b.reg(r#"cents?"#)?, + |a, _| { + Ok(IntegerValue { + value: a.value().value * 100, + grain: Some(2), + ..IntegerValue::default() + }) + }); + b.rule_2("number thousands", + integer_check_by_range!(1, 999), + b.reg(r#"milles?"#)?, + |a, _| { + Ok(IntegerValue { + value: a.value().value * 1000, + grain: Some(3), + ..IntegerValue::default() + }) + }); + b.rule_2("number millions", + integer_check_by_range!(1, 999), + b.reg(r#"millions?"#)?, + |a, _| { + Ok(IntegerValue { + value: a.value().value * 1000000, + grain: Some(6), + ..IntegerValue::default() + }) + }); + b.rule_2("number billions", + integer_check_by_range!(1, 999), + b.reg(r#"milliards?"#)?, + |a, _| { + Ok(IntegerValue { + value: a.value().value * 1000000000, + grain: Some(9), + ..IntegerValue::default() + }) + }); + b.rule_1_terminal("integer (numeric)", + b.reg(r#"(\d{1,18})"#)?, + |text_match| { + let value: i64 = text_match.group(1).parse()?; + IntegerValue::new(value) + }); + b.rule_1_terminal("integer with thousands separator .", + b.reg(r#"(\d{1,3}(\.\d\d\d){1,5})"#)?, + |text_match| { + let reformatted_string = text_match.group(1).replace(".", ""); + let value: i64 = reformatted_string.parse()?; + IntegerValue::new(value) + }); + b.rule_1_terminal("decimal number", + b.reg(r#"(\d*,\d+)"#)?, + |text_match| { + let reformatted_string = text_match.group(1).replace(",", "."); + let value: f32 = reformatted_string.parse()?; + FloatValue::new(value) + }); + b.rule_3("number dot number", + number_check!(|number: &NumberValue| !number.prefixed()), + b.reg(r#"virgule|point"#)?, + number_check!(|number: &NumberValue| !number.suffixed()), + |a, _, b| { + let power = b.value().value().to_string().chars().count(); + let coeff = 10.0_f32.powf(-1.0 * power as f32); + Ok(FloatValue { + value: b.value().value() * coeff + a.value().value(), + ..FloatValue::default() + }) + }); + b.rule_4("number dot zero ... number", + number_check!(|number: &NumberValue| !number.prefixed()), + b.reg(r#"virgule|point"#)?, + b.reg(r#"(?:(?:z[eé]ro )*(?:z[eé]ro))"#)?, + number_check!(|number: &NumberValue| !number.suffixed()), + |a, _, zeros, b| { + let power = zeros.group(0).split_whitespace().count() + b.value().value().to_string().chars().count(); + let coeff = 10.0_f32.powf(-1.0 * power as f32); + Ok(FloatValue { + value: b.value().value() * coeff + a.value().value(), + ..FloatValue::default() + }) + }); + b.rule_1_terminal("decimal with thousands separator", + b.reg(r#"(\d+(\.\d\d\d)+,\d+)"#)?, + |text_match| { + let reformatted_string = text_match.group(1).replace(".", "").replace(",", "."); + let value: f32 = reformatted_string.parse()?; + FloatValue::new(value) + }); + b.rule_2("numbers prefix with -, negative or minus", + b.reg(r#"-|moins"#)?, + number_check!(|number: &NumberValue| !number.prefixed()), + |_, a| -> RuleResult { + Ok(match a.value().clone() { + // checked + NumberValue::Integer(integer) => { + IntegerValue { + value: integer.value * -1, + prefixed: true, + ..integer + } + .into() + } + NumberValue::Float(float) => { + FloatValue { + value: float.value * -1.0, + prefixed: true, + ..float + } + .into() + } + }) + }); + b.rule_2("numbers prefix with +, positive", + b.reg(r#"\+"#)?, + number_check!(|number: &NumberValue| !number.prefixed()), + |_, a| -> RuleResult { + Ok(match a.value().clone() { + // checked + NumberValue::Integer(integer) => { + IntegerValue { + prefixed: true, + ..integer + } + .into() + } + NumberValue::Float(float) => { + FloatValue { + prefixed: true, + ..float + } + .into() + } + }) + } + ); + b.rule_2("numbers suffixes (K, M, G)", + number_check!(|number: &NumberValue| !number.suffixed()), + b.reg_neg_lh(r#"([kmg])"#, r#"^[\W\$€]"#)?, + |a, text_match| -> RuleResult { + let multiplier = match text_match.group(0).as_ref() { + "k" => 1000, + "m" => 1000000, + "g" => 1000000000, + _ => return Err(RuleError::Invalid.into()), + }; + Ok(match a.value().clone() { // checked + NumberValue::Integer(integer) => { + IntegerValue { + value: integer.value * multiplier, + suffixed: true, + ..integer + } + .into() + } + NumberValue::Float(float) => { + let product = float.value * (multiplier as f32); + if product.floor() == product { + IntegerValue { + value: product as i64, + suffixed: true, + ..IntegerValue::default() + } + .into() + } else { + FloatValue { + value: product, + suffixed: true, + ..float + } + .into() + } + } + }) + }); + b.rule_1_terminal("(douzaine ... soixantaine)", + b.reg(r#"(demi[ -]douz|diz|douz|quinz|vingt|trent|quarant|cinquant|soixant|cent)aines?"#)?, + |text_match| { + let value = match text_match.group(1).as_ref() { + "demi douz" => 6, + "demi-douz" => 6, + "diz" => 10, + "douz" => 12, + "quinz" => 15, + "vingt" => 20, + "trent" => 30, + "quarant" => 40, + "cinquant" => 50, + "soixant" => 60, + "cent" => 100, + _ => return Err(RuleError::Invalid.into()), + }; + Ok(IntegerValue { + value, + group: true, + .. IntegerValue::default() + }) + } + ); + b.rule_2("number dozen", + integer_check_by_range!(1, 9), + integer_check!(|integer: &IntegerValue| integer.group), + |a, b| { + Ok(IntegerValue { + value: a.value().value * b.value().value, + grain: b.value().grain, + group: true, + ..IntegerValue::default() + }) + }); + b.rule_1_terminal("ordinal 0", + b.reg(r#"z[eé]rot?i[eè]me"#)?, + |_| { + Ok(OrdinalValue::new(0)) + } + ); + b.rule_1_terminal("ordinal 1", + b.reg(r#"premi[eè]re?"#)?, + |_| { + Ok(OrdinalValue::new(1)) + } + ); + b.rule_1_terminal("ordinal 2", + b.reg(r#"seconde?|deuxi[eè]me"#)?, + |_| { + Ok(OrdinalValue::new(2)) + } + ); + b.rule_1_terminal( + "ordinals (premier..seizieme)", + b.reg(r#"(trois|quatr|cinqu|six|sept|huit|neuv|dix|onz|douz|treiz|quatorz|quinz|seiz)i[eè]me"#)?, + |text_match| { + let value = match text_match.group(1).as_ref() { + "trois" => 3, + "quatr" => 4, + "cinqu" => 5, + "six" => 6, + "sept" => 7, + "huit" => 8, + "neuv" => 9, + "dix" => 10, + "onz" => 11, + "douz" => 12, + "treiz" => 13, + "quatorz" => 14, + "quinz" => 15, + "seiz" => 16, + _ => return Err(RuleError::Invalid.into()), + }; + Ok(OrdinalValue::new(value)) + }); + b.rule_2("17ieme, 18ieme, 19ieme", + b.reg(r#"dix-?"#)?, + ordinal_check_by_range!(7, 9), + |_, ordinal| { + Ok(OrdinalValue::new(10 + ordinal.value().value)) + } + ); + b.rule_1_terminal("20ieme, 30ieme, 40ieme, 50ieme, 60ieme", + b.reg(r#"(vingt|trent|quarant|cinquant|soixant)i[èe]me"#)?, + |text_match| { + let value = match text_match.group(1).as_ref() { + "vingt" => 20, + "trent" => 30, + "quarant" => 40, + "cinquant" => 50, + "soixant" => 60, + _ => return Err(RuleError::Invalid.into()), + }; + Ok(OrdinalValue::new(value)) + } + ); + b.rule_1_terminal("80ieme", + b.reg(r#"quatre[- ]vingts?i[èe]me"#)?, + |_| { + Ok(OrdinalValue::new(80)) + } + ); + b.rule_2("22ieme...29ieme, 32ieme...39ieme, 42ieme...49ieme, 52ieme...59ieme", + integer_check_by_range!(20, 50, |integer: &IntegerValue| integer.value % 10 == 0), + ordinal_check_by_range!(2, 9), + |integer, ordinal| { + Ok(OrdinalValue::new(integer.value().value + ordinal.value().value)) + } + ); + b.rule_3("22ieme...29ieme, 32ieme...39ieme, 42ieme...49ieme, 52ieme...59ieme", + integer_check_by_range!(20, 50, |integer: &IntegerValue| integer.value % 10 == 0), + b.reg(r"-")?, + ordinal_check_by_range!(2, 9), + |integer, _, ordinal| { + Ok(OrdinalValue::new(integer.value().value + ordinal.value().value)) + } + ); + b.rule_2("62ieme...70ieme, 72ieme...79ieme, 90ieme, 92ieme...99ieme", + integer_check_by_range!(60, 80, |integer: &IntegerValue| integer.value == 60 || integer.value == 80), + ordinal_check_by_range!(2, 19), + |integer, ordinal| { + Ok(OrdinalValue::new(integer.value().value + ordinal.value().value)) + } + ); + b.rule_3("62ieme...70ieme, 72ieme...79ieme, 90ieme, 92ieme...99ieme", + integer_check_by_range!(60, 80, |integer: &IntegerValue| integer.value == 60 || integer.value == 80), + b.reg(r"-")?, + ordinal_check_by_range!(2, 19), + |integer, _, ordinal| { + Ok(OrdinalValue::new(integer.value().value + ordinal.value().value)) + } + ); + b.rule_2("21, 31, 41, 51, 61", + integer_check_by_range!(20, 60, |integer: &IntegerValue| integer.value % 10 == 0), + b.reg(r#"(?:et |-)uni[èe]me"#)?, + |integer, _| { + Ok(OrdinalValue::new(integer.value().value + 1)) + } + ); + b.rule_2("81", + integer_check_by_range!(80, 80), + b.reg(r#"(?:et )?uni[èe]me"#)?, + |integer, _| { + Ok(OrdinalValue::new(integer.value().value + 1)) + } + ); + b.rule_2("71, 91", + integer_check_by_range!(60, 60), + b.reg(r#"et onzi[eè]me"#)?, + |integer, _| { + Ok(OrdinalValue::new(integer.value().value + 11)) + } + ); + b.rule_2(" et demi", + integer_check_by_range!(0, 99), + b.reg(r#"et demie?"#)?, + |integer, _| { + FloatValue::new(integer.value().value as f32 + 0.5) + } + ); + b.rule_1_terminal("70, 80, 90 (Belgium and Switzerland)", + b.reg(r#"(sept|huit|non)ante"#)?, + |text_match| { + let value = match text_match.group(1).as_ref() { + "sept" => 70, + "huit" => 80, + "non" => 90, + _ => return Err(RuleError::Invalid.into()), + }; + IntegerValue::new(value) + } + ); + b.rule_1_terminal("71, 81, 91 (Belgium and Switzerland)", + b.reg(r#"(sept|huit|non)ante et une?"#)?, + |text_match| { + let value = match text_match.group(1).as_ref() { + "sept" => 71, + "huit" => 81, + "non" => 91, + _ => return Err(RuleError::Invalid.into()), + }; + IntegerValue::new(value) + } + ); + + b.rule_2("72..79, 82..89, 92..99, (Belgium and Switzerland)", + b.reg(r#"(sept|huit|non)ante"#)?, + integer_check_by_range!(2, 9), + |text_match, integer| { + let value = match text_match.group(1).as_ref() { + "sept" => 70, + "huit" => 80, + "non" => 90, + _ => return Err(RuleError::Invalid.into()), + }; + IntegerValue::new(value + integer.value().value) + } + ); + b.rule_1_terminal("ordinal (100, 1_000, 1_000_000)", + b.reg(r#"(cent|mill|million|milliard)i[èe]me"#)?, + |text_match| { + let (value, grain) = match text_match.group(1).as_ref() { + "cent" => (100, 2), + "mill" => (1_000, 3), + "million" => (1_000_000, 6), + "milliard" => (1_000_000_000, 9), + _ => return Err(RuleError::Invalid.into()), + }; + Ok(OrdinalValue::new_with_grain(value, grain)) + } + ); + + b.rule_2("ordinal (200..900, 2_000..9_000, 2_000_000..9_000_000_000)", + integer_check_by_range!(2, 999), + b.reg(r#"(cent|mill|million|milliard)i[èe]me"#)?, + |integer, text_match| { + let (value, grain) = match text_match.group(1).as_ref() { + "cent" => (100, 2), + "mill" => (1_000, 3), + "million" => (1_000_000, 6), + "milliard" => (1_000_000_000, 9), + _ => return Err(RuleError::Invalid.into()), + }; + Ok(OrdinalValue::new_with_grain(integer.value().value * value, grain)) + } + ); + + b.rule_2("ordinal (1_1_000..9_999_999_000)", + integer_check_by_range!(1000, 99_999_999_000), + ordinal_check!(|ordinal: &OrdinalValue| { + let grain = ordinal.grain.unwrap_or(0); + grain == 2 || grain % 3 == 0 + }), + |integer, ordinal| { + let grain = ordinal.value().grain.unwrap_or(0); + let next_grain = (grain / 3) * 3 + 3; + if integer.value().value % 10i64.pow(next_grain as u32) != 0 { return Err(RuleError::Invalid.into()); } + Ok(OrdinalValue::new(integer.value().value + ordinal.value().value)) + } + ); + + b.rule_2("ordinal (102...9_999_999)", + integer_check!(|integer: &IntegerValue| integer.value >= 100 || integer.value % 100 == 0), + ordinal_check_by_range!(2, 99), + |integer, ordinal| { + Ok(OrdinalValue::new(integer.value().value + ordinal.value().value)) + } + ); + b.rule_2("ordinal (101, 201, 301, ...)", + integer_check!(|integer: &IntegerValue| integer.value >= 100 || integer.value % 100 == 0), + b.reg(r#"(?:et |-)?uni[èe]me"#)?, + |integer, _| { + Ok(OrdinalValue::new(integer.value().value + 1)) + } + ); + b.rule_1_terminal("ordinal (digits)", + b.reg(r#"0*(\d+) ?(ere?|ère|ème|eme|ieme|ième)"#)?, + |text_match| { + let value: i64 = text_match.group(1).parse()?; + Ok(OrdinalValue::new(value)) + }); + b.rule_2("le ", + b.reg(r#"l[ea]"#)?, + ordinal_check!(), + |_, a| Ok((*a.value()).prefixed()) + ); + Ok(()) +} From 475e18633dce517e23ba4a01401bf35e35a86d48 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Mon, 29 Jul 2019 12:00:33 +0200 Subject: [PATCH 056/107] Add helpers and correct celebration. --- grammar/fr/src/rules_celebrations.rs | 2 ++ moment/src/period.rs | 8 ++++++++ values/src/dimension.rs | 20 ++++++++++++++++++++ values/src/helpers.rs | 20 +++++++++++--------- 4 files changed, 41 insertions(+), 9 deletions(-) diff --git a/grammar/fr/src/rules_celebrations.rs b/grammar/fr/src/rules_celebrations.rs index 446de1c8..f338d3be 100644 --- a/grammar/fr/src/rules_celebrations.rs +++ b/grammar/fr/src/rules_celebrations.rs @@ -103,4 +103,6 @@ b.rule_1_terminal("noel", .form(Form::Celebration)) ); + Ok(()) + } \ No newline at end of file diff --git a/moment/src/period.rs b/moment/src/period.rs index 79412424..fa7a7683 100644 --- a/moment/src/period.rs +++ b/moment/src/period.rs @@ -151,6 +151,14 @@ impl Period { .and_then(|(g, _)| Grain::from_usize(g)) } + pub fn coarser_grain(&self) -> Option { + use enum_primitive::FromPrimitive; + self.0 + .iter() + .min_by_key(|&(g, _)| g) + .and_then(|(g, _)| Grain::from_usize(g)) + } + pub fn comps(&self) -> Vec { use enum_primitive::FromPrimitive; self.0.iter() diff --git a/values/src/dimension.rs b/values/src/dimension.rs index 8c1dc342..3043cddd 100644 --- a/values/src/dimension.rs +++ b/values/src/dimension.rs @@ -642,6 +642,22 @@ impl Form { &Form::Span => None, } } + + pub fn is_day(&self) -> bool { + match self { + &Form::Cycle(grain) => { + match grain { + Grain::Day => true, + _ => false, + } + } + &Form::MonthDay(_) => true, + &Form::DayOfWeek { .. } => true, + &Form::DayOfMonth => true, + &Form::Celebration => true, + _ => false, + } + } } #[derive(Debug, Clone, Copy, PartialEq)] @@ -837,6 +853,10 @@ impl DurationValue { self.period.finer_grain().unwrap_or(Grain::Second) } + pub fn get_coarser_grain(&self) -> Grain { + self.period.coarser_grain().unwrap_or(Grain::Second) + } + pub fn from_addition(self, from_addition: FromAddition) -> DurationValue { DurationValue { from_addition: Some(from_addition), .. self} } diff --git a/values/src/helpers.rs b/values/src/helpers.rs index 9e4181f6..26f6b73a 100644 --- a/values/src/helpers.rs +++ b/values/src/helpers.rs @@ -403,10 +403,17 @@ impl MomentToRuleError for MomentResult { } pub fn year(y: i32) -> RuleResult { + // year between 0 and 99 will be normalized after 1950, e.g. 45 => 2045, 60 => 1960, 99 => 1999 let y = normalize_year(y)?; Ok(DatetimeValue::constraint(Year::new(y)).form(Form::Year(y))) } +pub fn weekend() -> RuleResult { + let friday = day_of_week(Weekday::Fri)?.intersect(&hour(18, false)?)?; + let monday = day_of_week(Weekday::Mon)?.intersect(&hour(0, false)?)?; + Ok(friday.span_to(&monday, false)?.datetime_kind(DatetimeKind::DatePeriod)) +} + pub fn month(m: u32) -> RuleResult { if !(1 <= m && m <= 12) { return Err(RuleError::Invalid.into()) @@ -528,7 +535,8 @@ pub fn easter() -> RuleResult { let (year, month, day) = computer_easter(i.start.year()); Some(Interval::ymd(year, month, day)) } - Ok(DatetimeValue::constraint(Month::new(3).invalid_if_err()?.translate_with(offset))) +Ok(DatetimeValue::constraint(Month::new(3).invalid_if_err()?.translate_with(offset)) + .datetime_kind(DatetimeKind::Date)) // otherwise grain is Month; not the cleanest but does the job } pub fn computer_easter(year: i32) -> (i32, u32, u32) { @@ -569,10 +577,7 @@ impl DurationValue { pub fn in_present(&self) -> RuleResult { self.check_period()?; let grain = self.get_grain(); - let datetime_kind = match grain.is_date_grain() { - true => DatetimeKind::Date, - false => DatetimeKind::Time, - }; + let datetime_kind = if grain.is_date_grain() { DatetimeKind::Date } else { DatetimeKind::Time }; Ok(DatetimeValue::constraint(Cycle::rc(Grain::Second) .take_the_nth(0) .shift_by(self.period.clone())).precision(self.precision) @@ -582,10 +587,7 @@ impl DurationValue { pub fn in_present_day(&self) -> RuleResult { self.check_period()?; let grain = self.get_grain(); - let datetime_kind = match grain.is_date_grain() { - true => DatetimeKind::Date, - false => DatetimeKind::Time, - }; + let datetime_kind = if grain.is_date_grain() { DatetimeKind::Date } else { DatetimeKind::Time }; Ok(DatetimeValue::constraint(Cycle::rc(Grain::Day) .take_the_nth(0) .shift_by(self.period.clone())).precision(self.precision) From 38713c6da7dacedf65e51784086a12f5ecfa15a7 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Mon, 29 Jul 2019 14:30:39 +0200 Subject: [PATCH 057/107] rebase task/split-grammar-files-entities-fr task/fr-new-spec-dt-enhancement. --- values/src/helpers.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/values/src/helpers.rs b/values/src/helpers.rs index 26f6b73a..7ca0c907 100644 --- a/values/src/helpers.rs +++ b/values/src/helpers.rs @@ -529,13 +529,18 @@ pub fn cycle_n_not_immediate(grain: Grain, n: i64) -> RuleResult Ok(DatetimeValue::constraint(Cycle::rc(grain).take_not_immediate(n)).form(Form::Cycle(grain))) } +pub fn weekend() -> RuleResult { + let friday = day_of_week(Weekday::Fri)?.intersect(&hour(18, false)?)?; + let monday = day_of_week(Weekday::Mon)?.intersect(&hour(0, false)?)?; + Ok(friday.span_to(&monday, false)?.datetime_kind(DatetimeKind::DatePeriod)) +} pub fn easter() -> RuleResult { fn offset(i: &Interval, _: &Context) -> Option> { let (year, month, day) = computer_easter(i.start.year()); Some(Interval::ymd(year, month, day)) } -Ok(DatetimeValue::constraint(Month::new(3).invalid_if_err()?.translate_with(offset)) + Ok(DatetimeValue::constraint(Month::new(3).invalid_if_err()?.translate_with(offset)) .datetime_kind(DatetimeKind::Date)) // otherwise grain is Month; not the cleanest but does the job } From 3d4739768eaeb619ae2a9833d538e3ea78a15a2c Mon Sep 17 00:00:00 2001 From: Rosa Stern Date: Sun, 9 Jun 2019 16:03:41 +0200 Subject: [PATCH 058/107] Improve French grammar for new Datetime specs (1/2). --- grammar/fr/src/rules_celebrations.rs | 19 +- grammar/fr/src/rules_datetime.rs | 618 ++++++++++++++++++++------- grammar/fr/src/rules_duration.rs | 24 -- grammar/fr/src/training.rs | 22 +- values/src/helpers.rs | 6 +- 5 files changed, 483 insertions(+), 206 deletions(-) diff --git a/grammar/fr/src/rules_celebrations.rs b/grammar/fr/src/rules_celebrations.rs index f338d3be..730c356c 100644 --- a/grammar/fr/src/rules_celebrations.rs +++ b/grammar/fr/src/rules_celebrations.rs @@ -5,12 +5,12 @@ use rustling_ontology_moment::{Weekday, Grain}; pub fn rules_celebration(b: &mut RuleSetBuilder) -> RustlingResult<()> { -b.rule_1_terminal("noel", - b.reg(r#"(?:jour de )?no[eë]l"#)?, + b.rule_1_terminal("noel", + b.reg(r#"(?:(?:le )?jour de )?no[eë]l"#)?, |_| Ok(helpers::month_day(12, 25)?.form(Form::Celebration)) ); b.rule_1_terminal("soir de noël", - b.reg(r#"(soir(?:ée)?|veille) de no[eë]l"#)?, + b.reg(r#"(?:l[ea] )?(?:soir(?:ée)?|veille|r[eé]veillon) de no[eë]l"#)?, |_| { let start = helpers::month_day(12, 24)?.intersect(&helpers::hour(18, false)?)?; let end = helpers::month_day(12, 25)?.intersect(&helpers::hour(0, false)?)?; @@ -18,6 +18,10 @@ b.rule_1_terminal("noel", .form(Form::Celebration)) } ); + b.rule_1_terminal("saint sylvestre", + b.reg(r#"(?:l[ea] )?(?:saint[- ]sylvestre|r[eé]veillon)"#)?, + |_| Ok(helpers::month_day(12, 31)?.form(Form::Celebration)) + ); b.rule_1_terminal("jour de l'an", b.reg(r#"(?:le )?(?:jour de l'|nouvel )an"#)?, |_| Ok(helpers::month_day(1, 1)?.form(Form::Celebration)) @@ -64,8 +68,8 @@ b.rule_1_terminal("noel", .form(Form::Celebration)) ); - b.rule_1_terminal("pencôte", - b.reg(r#"(?:la f[eê]te de la |la |le lundi de la )?penc[oô]te"#)?, + b.rule_1_terminal("pentecôte", + b.reg(r#"(?:la f[eê]te de |(?:le )?lundi de )?(?:la )?pentec[oô]te"#)?, |_| Ok(helpers::cycle_nth_after(Grain::Day, 49, &helpers::easter()?)? .form(Form::Celebration)) ); @@ -102,6 +106,11 @@ b.rule_1_terminal("noel", |_| Ok(helpers::month_day(8, 15)? .form(Form::Celebration)) ); + b.rule_2("à ", + b.reg(r#"au|[aà](?:l['a])?"#)?, + datetime_check!(form!(Form::Celebration)), + |_, a| Ok(a.value().clone()) + ); Ok(()) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index f5306bd6..638ee995 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -1,4 +1,3 @@ - use rustling::*; use rustling_ontology_values::dimension::*; use rustling_ontology_values::helpers; @@ -31,6 +30,10 @@ pub fn rules_cycle(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.reg(r#"mois"#)?, |_| CycleValue::new(Grain::Month) ); + b.rule_1_terminal("trimestre (cycle)", + b.reg(r#"trimestre"#)?, + |_| CycleValue::new(Grain::Quarter) + ); b.rule_1("année (cycle)", b.reg(r#"an(?:n[ée]e?)?s?"#)?, |_| CycleValue::new(Grain::Year) @@ -45,8 +48,9 @@ pub fn rules_cycle(b: &mut RuleSetBuilder) -> RustlingResult<()> { pub fn rules_datetime_with_cycle(b: &mut RuleSetBuilder) -> RustlingResult<()> { + // Cycle patterns relative to now b.rule_2("ce|dans le ", - b.reg(r#"(?:cet?t?e?s?)|(?:dans l[ae']? ?)"#)?, + b.reg(r#"(?:(?:dans )?l[ea' ]|cet?(?:te)?)"#)?, cycle_check!(), |_, cycle| helpers::cycle_nth(cycle.value().grain, 0) ); @@ -56,41 +60,158 @@ pub fn rules_datetime_with_cycle(b: &mut RuleSetBuilder) -> RustlingR b.reg(r#"-?ci"#)?, |_, cycle, _| helpers::cycle_nth(cycle.value().grain, 0) ); - + b.rule_2(" dernier", + cycle_check!(), + b.reg(r#"derni[èe]re?|pass[ée]e?|pr[eé]c[eé]dente?|(?:d')? ?avant"#)?, + |cycle, _| helpers::cycle_nth(cycle.value().grain, -1) + ); b.rule_3("le dernier", b.reg(r#"l[ae']? ?"#)?, cycle_check!(), b.reg(r#"derni[èe]re?|pass[ée]e?"#)?, |_, cycle, _| helpers::cycle_nth(cycle.value().grain, -1) ); + b.rule_3("n derniers ", + integer_check_by_range!(2, 9999), + b.reg(r#"derni.re?s?"#)?, + cycle_check!(), + |integer, _, cycle| { + let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, -1 * integer.value().value)?; + // All grains except Day will trigger the right datetime_kind + if cycle.value().grain == Grain::Day { + res = res.datetime_kind(DatetimeKind::DatePeriod); + } + Ok(res) + } + ); + b.rule_4("(pendant/durant/dans) les n derniers ", + b.reg(r#"(?:pendant |durant |dans )?[cld]es"#)?, + integer_check_by_range!(2, 9999), + b.reg(r#"derni.re?s?"#)?, + cycle_check!(), + |_, integer, _, cycle| { + let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, -1 * integer.value().value)?; + // All grains except Day will trigger the right datetime_kind + if cycle.value().grain == Grain::Day { + res = res.datetime_kind(DatetimeKind::DatePeriod); + } + Ok(res) + } + ); + b.rule_3("n passes|precedents", + integer_check_by_range!(2, 9999), + cycle_check!(), + b.reg(r#"pass[eèé][eèé]?s?|pr[eé]c[eé]dente?s?|(?:d')? ?avant|plus t[oô]t"#)?, + |integer, cycle, _| { + let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, -1 * integer.value().value)?; + // All grains except Day will trigger the right datetime_kind + if cycle.value().grain == Grain::Day { + res = res.datetime_kind(DatetimeKind::DatePeriod); + } + Ok(res) + } + ); + b.rule_4("(pendant/durant/dans) les n passes|precedents", + b.reg(r#"(?:pendant |durant |dans )?[cld]es"#)?, + integer_check_by_range!(2, 9999), + cycle_check!(), + b.reg(r#"pass[eèé][eèé]?s?|pr[eé]c[eé]dente?s?|(?:d')? ?avant|plus t[oô]t"#)?, + |_, integer, cycle, _| helpers::cycle_n_not_immediate(cycle.value().grain, -1 * integer.value().value) + ); + // Incorrect resolution if some follows the expression, + // e.g. "suivant le " (unsupported) + b.rule_2(" prochain|suivant|d'après", + cycle_check!(), + b.reg(r#"prochaine?|suivante?|qui suit|(?:d')? ?apr[eèé]s"#)?, + |cycle, _| helpers::cycle_nth(cycle.value().grain, 1) + ); b.rule_3("le prochain|suivant|d'après", b.reg(r#"l[ae']? ?|une? ?"#)?, cycle_check!(), b.reg(r#"prochaine?|suivante?|qui suit|(?:d'? ?)?apr[eèé]s"#)?, |_, cycle, _| helpers::cycle_nth(cycle.value().grain, 1) ); - b.rule_2(" dernier", + b.rule_3("n prochains ", + integer_check_by_range!(2, 9999), + b.reg(r#"prochaine?s?|suivante?s?|apr[eèé]s"#)?, cycle_check!(), - b.reg(r#"derni[èe]re?|pass[ée]e?|pr[eé]c[eé]dente?|(?:d')? ?avant"#)?, - |cycle, _| helpers::cycle_nth(cycle.value().grain, -1) + |integer, _, cycle| { + let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, integer.value().value)?; + // All grains except Day will trigger the right datetime_kind + if cycle.value().grain == Grain::Day { + res = res.datetime_kind(DatetimeKind::DatePeriod); + } + Ok(res) + } ); - b.rule_2(" prochain|suivant|d'après", + b.rule_4("(pendant/durant/dans) les n prochains ", + b.reg(r#"(?:pendant |durant |dans )?[cld]es"#)?, + integer_check_by_range!(2, 9999), + b.reg(r#"prochaine?s?|suivante?s?|apr[eèé]s"#)?, cycle_check!(), - b.reg(r#"prochaine?|suivante?|qui suit|(?:d')? ?apr[eèé]s"#)?, - |cycle, _| helpers::cycle_nth(cycle.value().grain, 1) + |_, integer, _, cycle| { + let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, integer.value().value)?; + // All grains except Day will trigger the right datetime_kind + if cycle.value().grain == Grain::Day { + res = res.datetime_kind(DatetimeKind::DatePeriod); + } + Ok(res) + } + ); + b.rule_3("n suivants", + integer_check_by_range!(2, 9999), + cycle_check!(), + b.reg(r#"prochaine?s?|suivante?s?|apr[eèé]s|qui sui(?:t|ves?)|plus tard"#)?, + |integer, cycle, _| { + let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, integer.value().value)?; + // All grains except Day will trigger the right datetime_kind + if cycle.value().grain == Grain::Day { + res = res.datetime_kind(DatetimeKind::DatePeriod); + } + Ok(res) + } + ); + b.rule_4("(pendant/durant/dans) les n suivants", + b.reg(r#"(?:pendant |durant |dans )?[cld]es"#)?, + integer_check_by_range!(2, 9999), + cycle_check!(), + b.reg(r#"prochaine?s?|suivante?s?|apr[eèé]s|qui sui(?:t|ves?)|plus tard"#)?, + |_, integer, cycle, _| { + let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, integer.value().value)?; + // All grains except Day will trigger the right datetime_kind + if cycle.value().grain == Grain::Day { + res = res.datetime_kind(DatetimeKind::DatePeriod); + } + Ok(res) + } ); b.rule_3("n avant", integer_check_by_range!(2, 9999), cycle_check!(), b.reg(r#"(?:d')? ?avant|plus t[oô]t"#)?, - |integer, cycle, _| helpers::cycle_nth(cycle.value().grain, -1 * integer.value().value) + |integer, cycle, _| { + let mut res = helpers::cycle_nth(cycle.value().grain, -1 * integer.value().value)?; + // All grains except Day will trigger the right datetime_kind + if cycle.value().grain == Grain::Day { + res = res.datetime_kind(DatetimeKind::DatePeriod); + } + Ok(res) + } ); b.rule_3("n après", integer_check_by_range!(2, 9999), cycle_check!(), b.reg(r#"(?:d')? ?apr[eèé]s|qui sui(?:t|ves?)|plus tard"#)?, - |integer, cycle, _| helpers::cycle_nth(cycle.value().grain, integer.value().value) + |integer, cycle, _| { + let mut res = helpers::cycle_nth(cycle.value().grain, integer.value().value)?; + // All grains except Day will trigger the right datetime_kind + if cycle.value().grain == Grain::Day { + res = res.datetime_kind(DatetimeKind::DatePeriod); + } + Ok(res) + } ); + // Cycle patterns relative to another datetime b.rule_4("le après|suivant ", b.reg(r#"l[ea']? ?"#)?, cycle_check!(), @@ -105,59 +226,6 @@ pub fn rules_datetime_with_cycle(b: &mut RuleSetBuilder) -> RustlingR datetime_check!(), |_, cycle, _, datetime| helpers::cycle_nth_after(cycle.value().grain, -1, datetime.value()) ); - b.rule_4("les n derniers ", - b.reg(r#"[cld]es"#)?, - integer_check_by_range!(2, 9999), - b.reg(r#"derni.re?s?"#)?, - cycle_check!(), - |_, integer, _, cycle| helpers::cycle_n_not_immediate(cycle.value().grain, -1 * integer.value().value) - ); - b.rule_3("n derniers ", - integer_check_by_range!(2, 9999), - b.reg(r#"derni.re?s?"#)?, - cycle_check!(), - |integer, _, cycle| helpers::cycle_n_not_immediate(cycle.value().grain, -1 * integer.value().value) - ); - - b.rule_4("les n prochains ", - b.reg(r#"[cld]es"#)?, - integer_check_by_range!(2, 9999), - b.reg(r#"prochaine?s?|suivante?s?|apr[eèé]s"#)?, - cycle_check!(), - |_, integer, _, cycle| helpers::cycle_n_not_immediate(cycle.value().grain, integer.value().value) - ); - b.rule_3("n prochains ", - integer_check_by_range!(2, 9999), - b.reg(r#"prochaine?s?|suivante?s?|apr[eèé]s"#)?, - cycle_check!(), - |integer, _, cycle| helpers::cycle_n_not_immediate(cycle.value().grain, integer.value().value) - ); - b.rule_4("les n passes|precedents", - b.reg(r#"[cld]es"#)?, - integer_check_by_range!(2, 9999), - cycle_check!(), - b.reg(r#"pass[eèé][eèé]?s?|pr[eé]c[eé]dente?s?|(?:d')? ?avant|plus t[oô]t"#)?, - |_, integer, cycle, _| helpers::cycle_n_not_immediate(cycle.value().grain, -1 * integer.value().value) - ); - b.rule_3("n passes|precedents", - integer_check_by_range!(2, 9999), - cycle_check!(), - b.reg(r#"pass[eèé][eèé]?s?|pr[eé]c[eé]dente?s?|(?:d')? ?avant|plus t[oô]t"#)?, - |integer, cycle, _| helpers::cycle_n_not_immediate(cycle.value().grain, -1 * integer.value().value) - ); - b.rule_4("les n suivants", - b.reg(r#"[cld]es"#)?, - integer_check_by_range!(2, 9999), - cycle_check!(), - b.reg(r#"prochaine?s?|suivante?s?|apr[eèé]s|qui sui(?:t|ves?)|plus tard"#)?, - |_, integer, cycle, _| helpers::cycle_n_not_immediate(cycle.value().grain, integer.value().value) - ); - b.rule_3("n suivants", - integer_check_by_range!(2, 9999), - cycle_check!(), - b.reg(r#"prochaine?s?|suivante?s?|apr[eèé]s|qui sui(?:t|ves?)|plus tard"#)?, - |integer, cycle, _| helpers::cycle_n_not_immediate(cycle.value().grain, integer.value().value) - ); b.rule_4(" de ", ordinal_check_by_range!(1, 9999), cycle_check!(), @@ -185,7 +253,7 @@ pub fn rules_datetime_with_cycle(b: &mut RuleSetBuilder) -> RustlingR datetime_check!(), |_, datetime| helpers::cycle_nth_after_not_immediate(Grain::Day, 1, datetime.value()) ); - b.rule_2("le veille du ", + b.rule_2("la veille du ", b.reg(r#"(la )?veille du"#)?, datetime_check!(), |_, datetime| helpers::cycle_nth_after_not_immediate(Grain::Day, -1, datetime.value()) @@ -356,7 +424,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { |_| helpers::month(8) ); b.rule_1_terminal("named-month", -// b.reg(r#"septembre|sept?\.?"#)?, // "sept" with no dot forbidden (confusion with nb "sept" in "à trois heures trente sept") +// b.reg(r#"septembre|sept?\.?"#)?, // "sept" with no dot forbidden (confusion with nb "sept" in "à trois heures trente sept") b.reg(r#"septembre|sept\.|sep\.?"#)?, |_| helpers::month(9) ); @@ -372,14 +440,17 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.reg(r#"décembre|decembre|déc\.?|dec\.?"#)?, |_| helpers::month(12) ); + b.rule_1_terminal("maintenant", - b.reg(r#"maintenant|(?:tout de suite)"#)?, + b.reg(r#"maintenant|tout de suite|en ce moment"#)?, |_| helpers::cycle_nth(Grain::Second, 0) ); b.rule_1_terminal("aujourd'hui", - b.reg(r#"(?:aujourd'? ?hui)|(?:ce jour)|(?:dans la journ[ée]e?)|(?:en ce moment)"#)?, + b.reg(r#"(?:aujourd'? ?hui)|(?:ce jour)|(?:dans la journ[ée]e?)"#)?, |_| helpers::cycle_nth(Grain::Day, 0) ); + // FIXME: "le lendemain" interpreted as demain, not as relative to another date + // but there is a rule "le lendemain du " - inconsistent b.rule_1_terminal("demain", b.reg(r#"(?:demain)|(?:le lendemain)"#)?, |_| helpers::cycle_nth(Grain::Day, 1) @@ -414,17 +485,30 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.rule_2("ce ", b.reg(r#"ce"#)?, datetime_check!(), - |_, datetime| datetime.value().the_nth(0) + |_, datetime| Ok(datetime.value().the_nth(0)? + .datetime_kind(datetime.value().datetime_kind.clone())) ); b.rule_2(" prochain", datetime_check!(form!(Form::DayOfWeek{..})), b.reg(r#"prochain"#)?, |datetime, _| datetime.value().the_nth_not_immediate(0) ); + b.rule_2(" prochain", + datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), + b.reg(r#"prochaine?"#)?, + |datetime, _| datetime.value().the_nth_not_immediate(0) + ); + b.rule_2("au prochain ", + b.reg(r#"(au|[aà] la) prochaine?"#)?, + datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), + |_, datetime| datetime.value().the_nth_not_immediate(0) + ); b.rule_2(" prochain", - datetime_check!(), + // The direction check is to avoid application of datetime_check(month) on rule result + // "avant " + datetime_check!(|datetime: &DatetimeValue| form!(Form::Month(_))(datetime) && !datetime.direction.is_some()), b.reg(r#"prochain"#)?, - |datetime, _| datetime.value().the_nth(1) + |datetime, _| datetime.value().the_nth_not_immediate(0) ); b.rule_2(" suivant|d'après", datetime_check!(), @@ -474,45 +558,41 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.reg(r#"week(?:\s|-)?end (?:d['eu]|en|du mois de)"#)?, datetime_check!(form!(Form::Month(_))), |ordinal, _, datetime| { - let week_day_start = helpers::day_of_week(Weekday::Fri)?.intersect(&helpers::hour(18, false)?)?; - let week_day_end = helpers::day_of_week(Weekday::Mon)?.intersect(&helpers::hour(0, false)?)?; - let week_day = week_day_start.span_to(&week_day_end, false)?; - let week_ends_of_time = datetime.value().intersect(&week_day)?; - week_ends_of_time.the_nth(ordinal.value().value - 1) + let weekend = helpers::weekend()?; + let nth_week_end = datetime.value().intersect(&weekend)?; + nth_week_end.the_nth(ordinal.value().value - 1) } ); b.rule_2("dernier week-end de ", b.reg(r#"(?:le )?dernier week(?:\s|-)?end (?:du mois d[e']|d['eu]|en)"#)?, datetime_check!(form!(Form::Month(_))), |_, datetime| { - let week_day_start = helpers::day_of_week(Weekday::Fri)?.intersect(&helpers::hour(18, false)?)?; - let week_day_end = helpers::day_of_week(Weekday::Mon)?.intersect(&helpers::hour(0, false)?)?; - let week_day = week_day_start.span_to(&week_day_end, false)?; - week_day.last_of(datetime.value()) + let weekend = helpers::weekend()?; + weekend.last_of(datetime.value()) } ); + // FIXME: change latency ranges for years? E.g. latent until 1900? b.rule_1("year", integer_check_by_range!(1000, 2100), - |integer| { - helpers::year(integer.value().value as i32) - } + |integer| helpers::year(integer.value().value as i32) ); b.rule_1("year (latent)", integer_check_by_range!(-1000, 999), - |integer| { - Ok(helpers::year(integer.value().value as i32)?.latent()) - } + |integer| Ok(helpers::year(integer.value().value as i32)?.latent()) + ); + b.rule_2("l'année ", + b.reg(r#"l[' ]an(?:n[eé]+)?"#)?, + integer_check!(), + |_, integer| helpers::year(integer.value().value as i32) ); b.rule_2("en ", - b.reg(r#"(?:en(?: l'an)?|de l'ann[eé])"#)?, + b.reg(r#"en"#)?, datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::Year(_))(datetime)), |_, year| Ok(year.value().clone()) ); b.rule_1("year (latent)", integer_check_by_range!(2101, 3000), - |integer| { - Ok(helpers::year(integer.value().value as i32)?.latent()) - } + |integer| Ok(helpers::year(integer.value().value as i32)?.latent()) ); b.rule_1_terminal("day of month (premier)", b.reg(r#"premier|prem\.?|1er|1 er"#)?, @@ -523,11 +603,11 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { integer_check_by_range!(1, 31), |_, integer| helpers::day_of_month(integer.value().value as u32) ); - b.rule_4("le à ", + b.rule_4("le à ", b.reg(r#"le"#)?, integer_check_by_range!(1, 31), b.reg(r#"[aà]"#)?, - datetime_check!(), + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(_))(datetime)), |_, integer, _, datetime| { let day_of_month = helpers::day_of_month(integer.value().value as u32)?; day_of_month.intersect(&datetime.value()) @@ -536,39 +616,42 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.rule_2(" ", integer_check_by_range!(1, 31), datetime_check!(form!(Form::Month(_))), - |integer, month| month.value().intersect(&helpers::day_of_month(integer.value().value as u32)?) + |integer, month| Ok(month.value() + .intersect(&helpers::day_of_month(integer.value().value as u32)?)? + .form(Form::DayOfMonth)) ); b.rule_2(" ", datetime_check!(form!(Form::DayOfWeek{..})), // Weird it is not used in the production of the rule integer_check_by_range!(1, 31), |_, integer| helpers::day_of_month(integer.value().value as u32) ); - b.rule_3(" à )", + b.rule_4(" à )", datetime_check!(form!(Form::DayOfWeek{..})), // Weird it is not used in the production of the rule integer_check_by_range!(1, 31), - datetime_check!(form!(Form::TimeOfDay(_))), - |_, integer, tod| helpers::day_of_month(integer.value().value as u32) + b.reg(r#"[aà]"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(_))(datetime)), + |_, integer, _, tod| helpers::day_of_month(integer.value().value as u32) ?.intersect(tod.value()) ); - b.rule_1("time-of-day (latent)", + b.rule_1(" (latent)", integer_check_by_range!(1, 23), |integer| Ok(helpers::hour(integer.value().value as u32, integer.value().value < 12)?.latent()) ); - b.rule_1("time-of-day (latent)", + b.rule_1(" (latent)", integer_check_by_range!(0, 0), |_| Ok(helpers::hour(0, false)?.latent()) ); b.rule_1_terminal("midi", - b.reg(r#"midi"#)?, + b.reg(r#"midi(?: pile| exactement| pr[eé]cises)?"#)?, |_| helpers::hour(12, false) ); b.rule_1_terminal("minuit", - b.reg(r#"minuit"#)?, + b.reg(r#"minuit(?: pile| exactement| pr[eé]cises)?"#)?, |_| helpers::hour(0, false) ); b.rule_2(" heures", datetime_check!(form!(Form::TimeOfDay(TimeOfDayForm::Hour { .. }))), - b.reg(r#"h\.?(?:eure)?s?"#)?, + b.reg(r#"h\.?(?:eure)?s?(?: pile| exactement| pr[eé]cises?)?"#)?, |a, _| Ok(a.value().clone().not_latent()) ); b.rule_2(" (heures) pile", @@ -651,6 +734,16 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { datetime.value().form_time_of_day()?.is_12_clock() ) ); + b.rule_3(" (as relative minutes) exactly", + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(TimeOfDayForm::Hour { .. }))(datetime)), + relative_minute_check!(), + b.reg(r#"pile|exactement|pr[ée]cises?"#)?, + |datetime, minutes, _| helpers::hour_relative_minute( + datetime.value().form_time_of_day()?.full_hour(), + minutes.value().0, + datetime.value().form_time_of_day()?.is_12_clock() + ) + ); b.rule_3(" moins (as relative minutes)", datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(TimeOfDayForm::Hour { .. }))(datetime)), b.reg(r#"moins(?: le)?"#)?, @@ -661,6 +754,17 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { datetime.value().form_time_of_day()?.is_12_clock() ) ); + b.rule_4(" moins (as relative minutes) exactly ", + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(TimeOfDayForm::Hour { .. }))(datetime)), + b.reg(r#"moins(?: le)?"#)?, + relative_minute_check!(), + b.reg(r#"pile|exactement|pr[ée]cises?"#)?, + |datetime, _, minutes, _| helpers::hour_relative_minute( + datetime.value().form_time_of_day()?.full_hour(), + -1 * minutes.value().0, + datetime.value().form_time_of_day()?.is_12_clock() + ) + ); b.rule_3(" et|passé de ", datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(TimeOfDayForm::Hour { .. }))(datetime)), b.reg(r#"et|pass[ée]e?s? de"#)?, @@ -671,7 +775,78 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { datetime.value().form_time_of_day()?.is_12_clock() ) ); - // Written dates in numeric formats + b.rule_4(" et|passé de exactly", + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(TimeOfDayForm::Hour { .. }))(datetime)), + b.reg(r#"et|pass[ée]e?s? de"#)?, + relative_minute_check!(), + b.reg(r#"pile|exactement|pr[ée]cises?"#)?, + |datetime, _, minutes, _| helpers::hour_relative_minute( + datetime.value().form_time_of_day()?.full_hour(), + minutes.value().0, + datetime.value().form_time_of_day()?.is_12_clock() + ) + ); + b.rule_4(" de exactly", + datetime_check!(form!(Form::TimeOfDay(_))), + b.reg(r#"(?:d[eu] |dans )(?:l[ 'a])?"#)?, + datetime_check!(form!(Form::PartOfDay(_))), + b.reg(r#"pile|exactement|pr[eé]cises?"#)?, + |a, _, b, _| Ok(a.value().intersect(b.value())?.form(a.value().form.clone())) + ); + // Adding "pour" here makes time-of-day ambiguous w/ Duration + // Duration has less priority than Datetime types, therefore duration will be output only + // if the output kind filter is set for Duration + b.rule_2("à ", + b.reg(r#"[aà]|pour"#)?, + datetime_check!(form!(Form::TimeOfDay(_))), + |_, a| Ok(a.value().clone().not_latent()) + ); + b.rule_2("vers ", + b.reg(r#"(?:plut[ôo]t )?(?:vers|autour de|[aà] environ|aux alentours de)"#)?, + datetime_check!(form!(Form::TimeOfDay(_))), + |_, a| Ok(a.value().clone().not_latent().precision(Precision::Approximate)) + ); + // Written time/date in numeric formats + b.rule_1_terminal("hh(:|h)mm (time-of-day)", + b.reg(r#"((?:[01]?\d)|(?:2[0-3]))[:h]([0-5]\d)"#)?, + |text_match| { + let hour: u32 = text_match.group(1).parse()?; + let minute: u32 = text_match.group(2).parse()?; + helpers::hour_minute(hour, minute, hour < 12) + } + ); + b.rule_3_terminal("hh(:|h)mm - hh(:|h)mm (time-of-day interval)", + b.reg(r#"((?:[01]?\d)|(?:2[0-3]))[:h]([0-5]\d)"#)?, + b.reg(r#" ?\- ?"#)?, + b.reg(r#"((?:[01]?\d)|(?:2[0-3]))[:h]([0-5]\d)"#)?, + |a, _, b| { + let hour_start: u32 = a.group(1).parse()?; + let minute_start: u32 = a.group(2).parse()?; + let hour_end: u32 = b.group(1).parse()?; + let minute_end: u32 = b.group(2).parse()?; + let start = helpers::hour_minute(hour_start, minute_start, hour_start < 12)?; + let end = helpers::hour_minute(hour_end, minute_end, hour_end < 12)?; + start.smart_span_to(&end, false) + } + ); + b.rule_1_terminal("hh:mm:ss", + b.reg(r#"((?:[01]?\d)|(?:2[0-3]))[:.]([0-5]\d)[:.]([0-5]\d)"#)?, + |text_match| helpers::hour_minute_second( + text_match.group(1).parse()?, + text_match.group(2).parse()?, + text_match.group(3).parse()?, + false + ) + + ); + b.rule_1_terminal("hhmm (military time-of-day)", + b.reg(r#"((?:[01]\d)|(?:2[0-3]))([0-5]\d)"#)?, + |text_match| Ok(helpers::hour_minute( + text_match.group(1).parse()?, + text_match.group(2).parse()?, + false + )?.latent()) + ); b.rule_1_terminal("yyyy-mm-dd - ISO", b.reg(r#"(\d{4})[-/](0?[1-9]|1[0-2])[-/](3[01]|[12]\d|0?[1-9])"#)?, |text_match| helpers::year_month_day( @@ -695,7 +870,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { text_match.group(2).parse()?, text_match.group(1).parse()?) ); - // End of Written dates in numeric formats + // End of Written time/date in numeric formats b.rule_1_terminal("matin", b.reg(r#"mat(?:in[ée]?e?)?"#)?, |_| Ok(helpers::hour(4, false)? @@ -909,63 +1084,83 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { .form(Form::PartOfDay(PartOfDayForm::Night))) } ); - b.rule_2("a l'heure ", + b.rule_2("a l'heure de ", b.reg(r#"(?:[àa] )?l[' ]heure du|au moment du|pendant l[ea']|au|pour l[ea']|l[ea']"#)?, - datetime_check!(|datetime: &DatetimeValue| datetime.latent && form!(Form::Meal)(datetime)), + datetime_check!(|datetime: &DatetimeValue| form!(Form::Meal)(datetime)), |_, a| Ok(a.value().clone().not_latent()) ); - b.rule_2(" ", - datetime_check!(), + b.rule_2(" ", + datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), datetime_check!(form!(Form::Meal)), |a, b| a.value().intersect(b.value()) ); - b.rule_2("du|dans le ", - b.reg(r#"pendant(?: l[ae']?)?|durant(?: l[ae']?)?|du|(?:[aà]|dans) l[ae']?|au|en|l[ae']|d[èe]s(?: l[ae']?)?"#)?, - datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), + b.rule_2("prep? & article ", // This is very catch-all/junky + b.reg(r#"(?:pendant |durant |dans |d[eè]s )?l[ae']?|en|au"#)?, + datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime)), |_, a| Ok(a.value().clone().not_latent()) ); b.rule_2("ce ", b.reg(r#"cet?t?e?"#)?, datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), - |_, a| Ok(helpers::cycle_nth(Grain::Day, 0)?.intersect(a.value())?.form(a.value().form.clone())) + |_, datetime| Ok(helpers::cycle_nth(Grain::Day, 0)? + .intersect(datetime.value())? + .form(datetime.value().form.clone()) + .datetime_kind(DatetimeKind::DatetimeComplement { date_and_time: true, today: true })) ); - b.rule_2(" ", - datetime_check!(excluding_form!(Form::TimeOfDay(_))), + b.rule_2(" ", + datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), |a, b| a.value().intersect(b.value()) ); - b.rule_2(" du matin", + b.rule_2(" du matin", datetime_check!(form!(Form::TimeOfDay(_))), - b.reg(r#"(?:(?:du|dans|de) )?(?:(?:au|le|la) )?mat(?:in[ée]?e?)?"#)?, + b.reg(r#"(?:(?:du|dans|de) )?(?:(?:au|le|la) )?mat(?:in[ée]?e?)?(?: pile| exactement| pr[eé]cises?)?"#)?, |a, _| { let period = helpers::hour(0, false)? .span_to(&helpers::hour(12, false)?, false)?; - a.value().intersect(&period) + Ok(a.value().intersect(&period)?.form(a.value().form.clone())) } ); - b.rule_2(" du soir", + b.rule_2(" de l'apres-midi", datetime_check!(form!(Form::TimeOfDay(_))), - b.reg(r#"(?:(?:du|dans|de) )?(?:(?:au|le|la) )?soir[ée]?e?"#)?, + b.reg(r#"(?:dans |de )?l[' ]apr[eè]s[\- ]midi(?: pile| exactement| pr[eé]cises?)?"#)?, + |a, _| { + let period = helpers::hour(12, false)? + .span_to(&helpers::hour(19, false)?, false)?; + Ok(a.value().intersect(&period)?.form(a.value().form.clone())) + } + ); + b.rule_2(" du soir", + datetime_check!(form!(Form::TimeOfDay(_))), + b.reg(r#"(?:(?:du|dans|de) )?(?:(?:au|le|la) )?soir[ée]?e?(?: pile| exactement| pr[eé]cises?)?"#)?, |a, _| { let period = helpers::hour(16, false)? .span_to(&helpers::hour(0, false)?, false)?; - a.value().intersect(&period) + Ok(a.value().intersect(&period)?.form(a.value().form.clone())) } ); - b.rule_3(" du ", + b.rule_3(" du ", datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), b.reg(r#"du"#)?, - datetime_check!(), + datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), |a, _, b| b.value().intersect(a.value()) ); - b.rule_1_terminal("week-end", - b.reg(r#"week(?:\s|-)?end"#)?, + b.rule_1_terminal("(ce/le) week-end", + b.reg(r#"(?:[cl]e )?week(?:\s|-)?end"#)?, + |_| helpers::weekend() + ); + b.rule_1_terminal("le week-end dernier", + b.reg(r#"le week(?:\s|-)?end dernier"#)?, |_| { - let friday = helpers::day_of_week(Weekday::Fri)? - .intersect(&helpers::hour(18, false)?)?; - let monday = helpers::day_of_week(Weekday::Mon)? - .intersect(&helpers::hour(0, false)?)?; - friday.span_to(&monday, false) + let weekend = helpers::weekend()?; + Ok(weekend.the_nth(-1)?.datetime_kind(DatetimeKind::DatePeriod)) + } + ); + b.rule_1_terminal("le week-end prochain", + b.reg(r#"le week(?:\s|-)?end prochain|le prochain week(?:\s|-)?end"#)?, + |_| { + let weekend = helpers::weekend()?; + Ok(weekend.the_nth(1)?.datetime_kind(DatetimeKind::DatePeriod)) } ); b.rule_1_terminal("début de semaine", @@ -1011,7 +1206,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { ); b.rule_4("dd-dd (interval)", b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, - b.reg(r#"\-|au|jusqu'au"#)?, + b.reg(r#"\-|(?:jusqu')?au"#)?, b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, datetime_check!(form!(Form::Month(_))), |a, _, b, month| { @@ -1022,7 +1217,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { ); b.rule_4("-dd (interval)", datetime_check!(), - b.reg(r#"\-|au|jusqu'au"#)?, + b.reg(r#"\-|(?:jusqu')?au"#)?, b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, datetime_check!(form!(Form::Month(_))), |datetime, _, text_match, month| { @@ -1033,7 +1228,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { ); b.rule_5("- dd (interval)", datetime_check!(), - b.reg(r#"\-|au|jusqu'au"#)?, + b.reg(r#"\-|(?:jusqu')?au"#)?, datetime_check!(form!(Form::DayOfWeek{..})), b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, datetime_check!(form!(Form::Month(_))), @@ -1046,7 +1241,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.rule_6(" 1er- dd (interval)", datetime_check!(form!(Form::DayOfWeek{..})), b.reg(r#"premier|prem\.?|1er|1 er"#)?, - b.reg(r#"\-|au|jusqu'au"#)?, + b.reg(r#"\-|(?:jusqu')?au"#)?, datetime_check!(form!(Form::DayOfWeek{..})), b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, datetime_check!(form!(Form::Month(_))), @@ -1059,7 +1254,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.rule_6("du dd- dd (interval)", b.reg(r#"du"#)?, b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, - b.reg(r#"\-|au|jusqu'au"#)?, + b.reg(r#"\-|(?:jusqu')?au"#)?, datetime_check!(form!(Form::DayOfWeek{..})), b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, datetime_check!(form!(Form::Month(_))), @@ -1072,7 +1267,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.rule_6("du dd- dd (interval)", b.reg(r#"du"#)?, datetime_check!(), - b.reg(r#"\-|au|jusqu'au"#)?, + b.reg(r#"\-|(?:jusqu')?au"#)?, datetime_check!(form!(Form::DayOfWeek{..})), b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, datetime_check!(form!(Form::Month(_))), @@ -1085,7 +1280,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.rule_4("la nuit ", b.reg(r#"(dans|pendant|durant) la nuit (?:du|de)"#)?, datetime_check!(form!(Form::DayOfWeek{..})), - b.reg(r#"\-|au|jusqu'au"#)?, + b.reg(r#"\-|(?:jusqu')?au"#)?, datetime_check!(form!(Form::DayOfWeek{..})), |_, start, _, end| { let start = start.value().intersect(&helpers::hour(22, false)?)?; @@ -1108,7 +1303,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.rule_4_terminal("du dd au dd(interval)", b.reg(r#"du"#)?, b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, - b.reg(r#"au|jusqu'au"#)?, + b.reg(r#"(?:jusqu')?au"#)?, b.reg(r#"(3[01]|[12]\d|0?[1-9])"#)?, |_, a, _, b| { let start = helpers::day_of_month(a.group(1).parse()?)?; @@ -1161,9 +1356,11 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { start.span_to(&end, true) } ); + + /* Intervals */ b.rule_3(" - (interval)", datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), - b.reg(r#"\-|au|[aà]|jusqu'(?:au|[aà])"#)?, + b.reg(r#" \- |(?:jusqu')?(?:au|[aà])"#)?, datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), |a, _, b| a.value().span_to(b.value(), true) ); @@ -1176,7 +1373,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.rule_4("de - (interval)", b.reg(r#"depuis|d[e'u]?"#)?, datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), - b.reg(r#"\-|au|[aà]|jusqu'(?:au|[aà])"#)?, + b.reg(r#" \- |(?:jusqu')?(?:au|[aà])"#)?, datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), |_, a, _, b| a.value().span_to(b.value(), true) ); @@ -1191,7 +1388,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.rule_5("de - (interval)", b.reg(r#"depuis|d[e'u]?"#)?, datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), - b.reg(r#"\-|au|[aà]|jusqu'(?:au|[aà])"#)?, + b.reg(r#" \- |(?:jusqu')?(?:au|[aà])"#)?, datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime) && datetime.is_coarse_grain_greater_than(Grain::Year)), datetime_check!(form!(Form::Year(_))), |_, a, _, b, year| a.value().span_to(b.value(), true)?.intersect(year.value()) @@ -1206,17 +1403,31 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { ); b.rule_3(" - (interval)", datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(_))(datetime)), - b.reg(r#" \- |[aà]|au|jusqu'(?:au|[aà])"#)?, + b.reg(r#" \- |(?:jusqu')?(?:au|[aà])"#)?, datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(_))(datetime)), |a, _, b| a.value().smart_span_to(b.value(), false) ); b.rule_4("de - (interval)", - b.reg(r#"(?:midi )?de"#)?, + b.reg(r#"(?:[aà] partir )?d['e]"#)?, datetime_check!(form!(Form::TimeOfDay(_))), - b.reg(r#"[aà]|au|jusqu'(?:au|[aà])"#)?, + b.reg(r#"(?:jusqu')?(?:au|[aà])"#)?, datetime_check!(form!(Form::TimeOfDay(_))), |_, a, _, b| a.value().smart_span_to(b.value(), false) ); + b.rule_2("de maintenant - (interval)", + b.reg(r#"(?:[aà] partir )?de maintenant (?:jusqu')?(?:au|[aà])"#)?, + datetime_check!(form!(Form::TimeOfDay(_))), + |_, a| helpers::cycle_nth(Grain::Second, 0)?.smart_span_to(a.value(), false) + ); + b.rule_3("de - maintenant (interval)", + b.reg(r#"(?:[aà] partir )?d['e]"#)?, + datetime_check!(form!(Form::TimeOfDay(_))), + b.reg(r#"(?:jusqu')?(?:au|[aà]) maintenant"#)?, + |_, a, _| { + let now = helpers::cycle_nth(Grain::Second, 0)?; + a.value().smart_span_to(&now, false) + } + ); b.rule_4("entre et (interval)", b.reg(r#"entre"#)?, datetime_check!(form!(Form::TimeOfDay(_))), @@ -1224,33 +1435,29 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { datetime_check!(form!(Form::TimeOfDay(_))), |_, a, _, b| a.value().smart_span_to(b.value(), false) ); - b.rule_2("d'ici ", - b.reg(r#"d'ici|dans l(?:'|es?)"#)?, - duration_check!(), - |_, duration| { - let start = helpers::cycle_nth(Grain::Second, 0)?; - let end = duration.value().in_present()?; - start.span_to(&end, false) - } - ); - b.rule_2("avant ", - b.reg(r#"(?:n[ ']importe quand )?jusqu'(?:a|à)"#)?, - datetime_check!(), + b.rule_2("jusqu'à ", + b.reg(r#"(?:n[ ']importe quand )?jusqu'(?:au|[aà]|en)"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent), |_, datetime| Ok(datetime.value().clone().mark_before_end()) ); - b.rule_2("avant ", + b.rule_2("avant ", b.reg(r#"(?:n[ ']importe quand )?avant"#)?, - datetime_check!(), + datetime_check!(|datetime: &DatetimeValue| !datetime.latent), |_, datetime| Ok(datetime.value().clone().mark_before_start()) ); - b.rule_2("après ", - b.reg(r#"apr(?:e|è)s"#)?, - datetime_check!(), + b.rule_2("après ", + b.reg(r#"apr[eè]s"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent), |_, datetime| Ok(datetime.value().clone().mark_after_end()) ); - b.rule_2("après ", - b.reg(r#"(?:a|à) partir de"#)?, - datetime_check!(), + b.rule_2("à partir de ", + b.reg(r#"[aà] partir d['eu]"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent), + |_, datetime| Ok(datetime.value().clone().mark_after_start()) + ); + b.rule_2("à partir de ", + b.reg(r#"[aà] partir d['eu](?:l[ 'a])?"#)?, + datetime_check!(form!(Form::PartOfDay(_))), |_, datetime| Ok(datetime.value().clone().mark_after_start()) ); b.rule_2("après le ", @@ -1259,9 +1466,94 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { |_, integer| Ok(helpers::day_of_month(integer.value().value as u32)?.mark_after_end()) ); b.rule_2("après le ", - b.reg(r#"(?:a|à) partir du"#)?, + b.reg(r#"[aà] partir d['eu]"#)?, integer_check_by_range!(1, 31), |_, integer| Ok(helpers::day_of_month(integer.value().value as u32)?.mark_after_start()) ); + b.rule_2("il y a ", + b.reg(r#"il y a"#)?, + duration_check!(), + |_, duration| duration.value().ago() + ); + // With "depuis/d'ici", interpretation of duration ambiguous with time-of-day we choose time-of-day + // I.e. "x heures (y)", but not "x heures et y minutes", "x minutes", etc. + // FIXME: some time-of-day patterns should be removed, e.g. "x heures y minutes" - they are not + // proper time-of-day, but they can be duration expressions + // TODO: check if Grain::Second here breaks DatePeriod - cf. implem. of equivalent in English? + b.rule_2("depuis ", + b.reg(r#"depuis|[cç]a fait"#)?, + duration_check!(), + |_, duration| { + if duration.value().get_coarser_grain() == Grain::Hour { + return Err(RuleError::Invalid.into()) + } + duration.value().ago()? + .span_to(&helpers::cycle_nth(Grain::Second, 0)?, false) + }); + b.rule_2("depuis ", + b.reg(r#"depuis"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent), + |_, datetime| Ok(datetime.value().the_nth(-1)?.mark_after_start()) + ); + b.rule_2("d'ici ", + b.reg(r#"d'ici|dans l(?:'|es?)"#)?, + duration_check!(), + |_, duration| { + let duration_grain = duration.value().get_coarser_grain(); + // Priority to d'ici + if duration_grain == Grain::Hour && + // FIXME: There must be a better way to do this check! + duration.value().period.0.get(Grain::Hour as usize).unwrap_or(&0) <= &23 { + return Err(RuleError::Invalid.into()) + } + let grain = if duration_grain.is_date_grain() { Grain::Day } else { Grain::Second }; + let start = helpers::cycle_nth(grain, 0)?; + let end = if grain == Grain::Day { duration.value().in_present_day()? } else { duration.value().in_present()? }; + start.span_to(&end, true) + } + ); + b.rule_2("dans le ", + b.reg(r#"dans l(?:'|es?)"#)?, + duration_check!(), + |_, duration| { + let duration_grain = duration.value().get_coarser_grain(); + let grain = if duration_grain.is_date_grain() { Grain::Day } else { Grain::Second }; + let start = helpers::cycle_nth(grain, 0)?; + let end = if grain == Grain::Day { duration.value().in_present_day()? } else { duration.value().in_present()? }; + start.span_to(&end, false) + } + ); + b.rule_2("d'ici ", + b.reg(r#"d'ici"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && form!(Form::TimeOfDay(_))(datetime)), + |_, tod| { + // FIXME: This adds one second to the value of now+then + let now = helpers::cycle_nth(Grain::Second, 0)?; + let then = tod.value().clone().mark_before_start(); + now.span_to(&then, false) + } + ); + b.rule_2("d'ici ", + b.reg(r#"d'ici"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), + |_, date| { + // FIXME: This adds one second to the value of now+then + let today = helpers::cycle_nth(Grain::Day, 0)?; + let then = date.value().clone().mark_before_start(); + today.span_to(&then, false) + } + ); + b.rule_3(" apres ", + duration_check!(), + b.reg(r#"apr[eè]s"#)?, + datetime_check!(), + |duration, _, datetime| duration.value().after(datetime.value()) + ); + b.rule_3(" avant ", + duration_check!(), + b.reg(r#"avant"#)?, + datetime_check!(), + |duration, _, datetime| duration.value().before(datetime.value()) + ); Ok(()) } diff --git a/grammar/fr/src/rules_duration.rs b/grammar/fr/src/rules_duration.rs index 7347e9fb..c7ad7d03 100644 --- a/grammar/fr/src/rules_duration.rs +++ b/grammar/fr/src/rules_duration.rs @@ -144,29 +144,5 @@ pub fn rules_duration(b: &mut RuleSetBuilder) -> RustlingResult<()> { duration_check!(), |_, duration| Ok(duration.value().clone().prefixed()) ); - b.rule_2("il y a ", - b.reg(r#"il y a"#)?, - duration_check!(), - |_, duration| duration.value().ago() - ); - b.rule_2("depuis ", - b.reg(r#"depuis|[cç]a fait"#)?, - duration_check!(), - |_, duration| { - duration.value().ago()? - .span_to(&helpers::cycle_nth(Grain::Second, 0)?, false) - }); - b.rule_3(" apres ", - duration_check!(), - b.reg(r#"apr[eè]s"#)?, - datetime_check!(), - |duration, _, datetime| duration.value().after(datetime.value()) - ); - b.rule_3(" avant ", - duration_check!(), - b.reg(r#"avant"#)?, - datetime_check!(), - |duration, _, datetime| duration.value().before(datetime.value()) - ); Ok(()) } \ No newline at end of file diff --git a/grammar/fr/src/training.rs b/grammar/fr/src/training.rs index 51adc39d..5b2e435b 100644 --- a/grammar/fr/src/training.rs +++ b/grammar/fr/src/training.rs @@ -48,8 +48,8 @@ pub fn examples_finance(v: &mut Vec<::rustling::train::Example>) { pub fn examples_datetime(v: &mut Vec<::rustling::train::Example>) { let c = ResolverContext::new(Interval::starting_at(Moment(Local.ymd(2013, 2, 12).and_hms(4, 30, 0)), Grain::Second)); - example!(v, check_moment!(c, [2013, 2, 12, 4, 30, 00]), "maintenant", "tout de suite"); - example!(v, check_moment!(c, [2013, 2, 12]), "aujourd'hui", "ce jour", "dans la journée", "en ce moment"); + example!(v, check_moment!(c, [2013, 2, 12, 4, 30, 00]), "maintenant", "tout de suite", "en ce moment"); + example!(v, check_moment!(c, [2013, 2, 12]), "aujourd'hui", "ce jour", "dans la journée"); example!(v, check_moment!(c, [2013, 2, 11]), "hier", "le jour d'avant", "le jour précédent", "la veille"); example!(v, check_moment!(c, [2013, 2, 10]), "avant-hier"); example!(v, check_moment!(c, [2013, 2, 13]), "demain", "jour suivant", "le jour d'après", "le lendemain", "un jour après"); @@ -65,16 +65,19 @@ pub fn examples_datetime(v: &mut Vec<::rustling::train::Example>) { example!(v, check_moment!(c, [2013, 3, 1]), "le 1er mars", "premier mars", "le 1 mars", "vendredi 1er mars"); example!(v, check_moment!(c, [2013, 3, 1]), "le premier mars 2013", "1/3/2013", "2013-03-01"); example!(v, check_moment!(c, [2013, 3, 2]), "le 2 mars", "2 mars", "le 2/3"); - example!(v, check_moment!(c, [2013, 3, 2, 5]), "le 2 mars à 5h", "2 mars à 5h", "le 2/3 à 5h", "le 2 mars à 5h du matin", "le 2 mars vers 5h", "2 mars vers 5h", "2 mars à environ 5h", "2 mars aux alentours de 5h", "2 mars autour de 5h", "le 2/3 vers 5h"); + example!(v, check_moment!(c, [2013, 3, 2, 5]), "le 2 mars à 5h", "2 mars à 5h", "le 2/3 à 5h", "le 2 mars à 5h du matin"); + example!(v, check_moment_with_precision!(c, [2013, 3, 2, 5], Precision::Approximate), "le 2 mars vers 5h", "2 mars vers 5h", "2 mars à environ 5h", "2 mars aux alentours de 5h", "2 mars autour de 5h", "le 2/3 vers 5h"); example!(v, check_moment!(c, [2013, 3, 2]), "le 2"); - example!(v, check_moment!(c, [2013, 3, 2, 5]), "le 2 à 5h", "le 2 vers 5h", "le 2 à 5h du mat"); + example!(v, check_moment!(c, [2013, 3, 2, 5]), "le 2 à 5h", "le 2 à 5h du mat"); + example!(v, check_moment_with_precision!(c, [2013, 3, 2, 5], Precision::Approximate), "le 2 vers 5h"); example!(v, check_moment!(c, [2013, 3, 3]), "le 3 mars", "3 mars", "le 3/3"); example!(v, check_moment!(c, [2013, 4, 5]), "le 5 avril", "5 avril"); example!(v, check_moment!(c, [2015, 3, 3]), "le 3 mars 2015", "3 mars 2015", "3/3/2015", "2015-3-3", "2015-03-03"); example!(v, check_moment!(c, [2013, 2, 15]), "le 15 février", "15 février"); example!(v, check_moment!(c, [2013, 2, 15]), "15/02/2013", "15 fev 2013"); example!(v, check_moment!(c, [2013, 2, 16]), "le 16"); - example!(v, check_moment!(c, [2013, 2, 16, 18]), "le 16 à 18h", "le 16 vers 18h", "le 16 plutôt vers 18h", "le 16 à 6h du soir", "le 16 vers 6h du soir", "le 16 vers 6h dans la soirée", "samedi 16 à 18h"); + example!(v, check_moment!(c, [2013, 2, 16, 18]), "le 16 à 18h", "le 16 à 6h du soir", "samedi 16 à 18h"); + example!(v, check_moment_with_precision!(c, [2013, 2, 16, 18], Precision::Approximate), "le 16 vers 18h", "le 16 plutôt vers 18h", "le 16 vers 6h du soir", "le 16 vers 6h dans la soirée"); example!(v, check_moment!(c, [2013, 2, 17]), "17 février", "le 17 février", "17/2", "17/02", "le 17/02", "17 02", "17 2", "le 17 02", "le 17 2"); example!(v, check_moment!(c, [2013, 2, 13]), "mercredi 13"); //when today is Tuesday 12, "mercredi 13" should be tomorrow example!(v, check_moment!(c, [2014, 2, 20]), "20/02/2014", "20/2/2014", "20/02/14", "le 20/02/14", "le 20/2/14", "20 02 2014", "20 02 14", "20 2 2014", "20 2 14", "le 20 02 2014", "le 20 02 14", "le 20 2 2014", "le 20 2 14"); @@ -115,7 +118,8 @@ pub fn examples_datetime(v: &mut Vec<::rustling::train::Example>) { example!(v, check_moment!(c, [2015, 10, 31]), "dernier jour d'octobre 2015", "le dernier jour d'octobre 2015"); example!(v, check_moment!(c, [2014, 9, 22], Grain::Week), "dernière semaine de septembre 2014", "la dernière semaine de septembre 2014"); //Hours - example!(v, check_moment!(c, [2013, 2, 12, 15]), "à quinze heures", "à 15 heures", "à 3 heures cet après-midi", "15h", "15H", "vers 15 heures", "à environ 15 heures"); + example!(v, check_moment!(c, [2013, 2, 12, 15]), "à quinze heures", "à 15 heures", "à 3 heures cet après-midi", "15h", "15H"); + example!(v, check_moment_with_precision!(c, [2013, 2, 12, 15], Precision::Approximate), "vers 15 heures", "à environ 15 heures"); example!(v, check_moment!(c, [2013, 2, 12, 15, 0]), "15:00", "15h00", "15H00"); example!(v, check_moment!(c, [2013, 2, 13, 00]), "minuit"); example!(v, check_moment!(c, [2013, 2, 12, 12]), "midi", "aujourd'hui à midi"); @@ -225,10 +229,10 @@ pub fn examples_datetime(v: &mut Vec<::rustling::train::Example>) { example!(v, check_moment_with_direction!(c, [2013, 2, 15, 12], Direction::After), "vendredi à partir de midi"); example!(v, check_moment_span!(c, [2013, 2, 20], [2013, 2, 20, 18]), "le 20 jusqu'à 18h"); example!(v, check_moment_span!(c, [2014, 9, 14], [2014, 9, 21]), "14 - 20 sept. 2014", "14 - 20 sep 2014"); // but not "14 - 20 sept 2014" - example!(v, check_moment_span!(c, [2013, 2, 12, 4, 30, 0], [2013, 2, 26]), "d'ici 2 semaines"); + example!(v, check_moment_span!(c, [2013, 2, 12], [2013, 2, 27]), "d'ici 2 semaines"); //15j != 2 semaines - example!(v, check_moment_span!(c, [2013, 2, 12, 4, 30, 0], [2013, 5, 12]), "d'ici 3 mois"); - example!(v, check_moment_span!(c, [2013, 2, 12, 4, 30, 0], [2013, 2, 27]), "dans les 15 jours"); + example!(v, check_moment_span!(c, [2013, 2, 12], [2013, 5, 13]), "d'ici 3 mois"); + example!(v, check_moment_span!(c, [2013, 2, 12], [2013, 2, 28]), "dans les 15 jours"); example!(v, check_moment_span!(c, [2013, 2, 12, 5], [2013, 2, 12, 7]), "de 5 à 7"); example!(v, check_moment_span!(c, [2013, 2, 14, 9], [2013, 2, 14, 11]), "jeudi de 9h à 11h"); example!(v, check_moment_span!(c, [2013, 2, 12, 12], [2013, 2, 12, 14]), "entre midi et 2"); diff --git a/values/src/helpers.rs b/values/src/helpers.rs index 7ca0c907..71d07474 100644 --- a/values/src/helpers.rs +++ b/values/src/helpers.rs @@ -529,11 +529,7 @@ pub fn cycle_n_not_immediate(grain: Grain, n: i64) -> RuleResult Ok(DatetimeValue::constraint(Cycle::rc(grain).take_not_immediate(n)).form(Form::Cycle(grain))) } -pub fn weekend() -> RuleResult { - let friday = day_of_week(Weekday::Fri)?.intersect(&hour(18, false)?)?; - let monday = day_of_week(Weekday::Mon)?.intersect(&hour(0, false)?)?; - Ok(friday.span_to(&monday, false)?.datetime_kind(DatetimeKind::DatePeriod)) -} + pub fn easter() -> RuleResult { fn offset(i: &Interval, _: &Context) -> Option> { From 127d2b51f63d881d7071021bbeca3752c436ba1d Mon Sep 17 00:00:00 2001 From: Rosa Stern Date: Sun, 9 Jun 2019 16:42:16 +0200 Subject: [PATCH 059/107] Detail --- grammar/en/src/rules_datetime.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/grammar/en/src/rules_datetime.rs b/grammar/en/src/rules_datetime.rs index 5ac3eb27..220b7112 100644 --- a/grammar/en/src/rules_datetime.rs +++ b/grammar/en/src/rules_datetime.rs @@ -1266,6 +1266,7 @@ pub fn rules_datetime_with_duration(b: &mut RuleSetBuilder) -> Rustli } +// FIXME: rename "rules_datetime_with_cycle" pub fn rules_datetime_with_nth_cycle(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.rule_2("this ", From 717f2d6126ce95b564dc2e9c22e1bb0695d6a042 Mon Sep 17 00:00:00 2001 From: Rosa Stern Date: Sun, 9 Jun 2019 16:43:04 +0200 Subject: [PATCH 060/107] Split French grammar per entity type. --- grammar/fr/src/rules_celebrations.rs | 105 +++++---- grammar/fr/src/rules_duration.rs | 15 +- grammar/fr/src/rules_number.rs | 313 +++++++++++++++++++++++---- 3 files changed, 328 insertions(+), 105 deletions(-) diff --git a/grammar/fr/src/rules_celebrations.rs b/grammar/fr/src/rules_celebrations.rs index 730c356c..357a7361 100644 --- a/grammar/fr/src/rules_celebrations.rs +++ b/grammar/fr/src/rules_celebrations.rs @@ -4,7 +4,6 @@ use rustling_ontology_values::helpers; use rustling_ontology_moment::{Weekday, Grain}; pub fn rules_celebration(b: &mut RuleSetBuilder) -> RustlingResult<()> { - b.rule_1_terminal("noel", b.reg(r#"(?:(?:le )?jour de )?no[eë]l"#)?, |_| Ok(helpers::month_day(12, 25)?.form(Form::Celebration)) @@ -23,88 +22,87 @@ pub fn rules_celebration(b: &mut RuleSetBuilder) -> RustlingResult<() |_| Ok(helpers::month_day(12, 31)?.form(Form::Celebration)) ); b.rule_1_terminal("jour de l'an", - b.reg(r#"(?:le )?(?:jour de l'|nouvel )an"#)?, - |_| Ok(helpers::month_day(1, 1)?.form(Form::Celebration)) + b.reg(r#"(?:le )?(?:jour de l'|nouvel )an"#)?, + |_| Ok(helpers::month_day(1, 1)?.form(Form::Celebration)) ); b.rule_1_terminal("toussaint", - b.reg(r#"(?:(?:la |la journée de la |jour de la )?toussaint|jour des morts)"#)?, - |_| Ok(helpers::month_day(11, 1)?.form(Form::Celebration)) + b.reg(r#"(?:(?:la |la journée de la |jour de la )?toussaint|jour des morts)"#)?, + |_| Ok(helpers::month_day(11, 1)?.form(Form::Celebration)) ); b.rule_1_terminal("Armistice", - b.reg(r#"(?:pour )?l'armistice"#)?, - |_| Ok(helpers::month_day(11, 11)?.form(Form::Celebration)) + b.reg(r#"(?:pour )?l'armistice"#)?, + |_| Ok(helpers::month_day(11, 11)?.form(Form::Celebration)) ); b.rule_1_terminal("Saint Etienne (Alsace)", - b.reg(r#"(?:(?:le jour|la f[eê]te) de )?la (?:saint|st) [eé]tienne"#)?, - |_| Ok(helpers::month_day(12, 26)?.form(Form::Celebration)) + b.reg(r#"(?:(?:le jour|la f[eê]te) de )?la (?:saint|st) [eé]tienne"#)?, + |_| Ok(helpers::month_day(12, 26)?.form(Form::Celebration)) ); b.rule_1_terminal("jeudi saint", - b.reg(r#"(?:le )?jeudi saint"#)?, - |_| Ok(helpers::cycle_nth_after(Grain::Day, -3, &helpers::easter()?)? - .form(Form::Celebration)) + b.reg(r#"(?:le )?jeudi saint"#)?, + |_| Ok(helpers::cycle_nth_after(Grain::Day, -3, &helpers::easter()?)? + .form(Form::Celebration)) ); b.rule_1_terminal("vendredi saint", - b.reg(r#"(?:le )?vendredi saint"#)?, - |_| Ok(helpers::cycle_nth_after(Grain::Day, -2, &helpers::easter()?)? - .form(Form::Celebration)) + b.reg(r#"(?:le )?vendredi saint"#)?, + |_| Ok(helpers::cycle_nth_after(Grain::Day, -2, &helpers::easter()?)? + .form(Form::Celebration)) ); b.rule_1_terminal("samedi saint", - b.reg(r#"(?:le )?samedi saint"#)?, - |_| Ok(helpers::cycle_nth_after(Grain::Day, -1, &helpers::easter()?)? - .form(Form::Celebration)) + b.reg(r#"(?:le )?samedi saint"#)?, + |_| Ok(helpers::cycle_nth_after(Grain::Day, -1, &helpers::easter()?)? + .form(Form::Celebration)) ); b.rule_1_terminal("pâques", - b.reg(r#"(?:la f[eê]te de |le jour de |le dimanche de )?p[âa]ques"#)?, - |_| Ok(helpers::easter()?.form(Form::Celebration)) + b.reg(r#"(?:la f[eê]te de |le jour de |le dimanche de )?p[âa]ques"#)?, + |_| Ok(helpers::easter()?.form(Form::Celebration)) ); b.rule_1_terminal("le lundi de pâques", - b.reg(r#"le lundi de p[âa]ques"#)?, - |_| Ok(helpers::cycle_nth_after(Grain::Day, 1, &helpers::easter()?)? - .form(Form::Celebration)) + b.reg(r#"le lundi de p[âa]ques"#)?, + |_| Ok(helpers::cycle_nth_after(Grain::Day, 1, &helpers::easter()?)? + .form(Form::Celebration)) ); b.rule_1_terminal("ascension", - b.reg(r#"(?:la f[eê]te de l'|le jeudi de l'|l'|le jour de l')ascension"#)?, - |_| Ok(helpers::cycle_nth_after(Grain::Day, 39, &helpers::easter()?)? - .form(Form::Celebration)) - + b.reg(r#"(?:la f[eê]te de l'|le jeudi de l'|l'|le jour de l')ascension"#)?, + |_| Ok(helpers::cycle_nth_after(Grain::Day, 39, &helpers::easter()?)? + .form(Form::Celebration)) ); b.rule_1_terminal("pentecôte", - b.reg(r#"(?:la f[eê]te de |(?:le )?lundi de )?(?:la )?pentec[oô]te"#)?, - |_| Ok(helpers::cycle_nth_after(Grain::Day, 49, &helpers::easter()?)? - .form(Form::Celebration)) + b.reg(r#"(?:la f[eê]te de |(?:le )?lundi de )?(?:la )?pentec[oô]te"#)?, + |_| Ok(helpers::cycle_nth_after(Grain::Day, 49, &helpers::easter()?)? + .form(Form::Celebration)) ); b.rule_1_terminal("1er mai", - b.reg(r#"(?:la )?f(e|ê)te du travail"#)?, - |_| Ok(helpers::month_day(5, 1)?.form(Form::Celebration)) + b.reg(r#"(?:la )?f(e|ê)te du travail"#)?, + |_| Ok(helpers::month_day(5, 1)?.form(Form::Celebration)) ); b.rule_1_terminal("fêtes des pères", - b.reg(r#"(?:la )?f[eê]te des p[eè]res"#)?, - |_| { - let sundays_of_june = helpers::month(6)?.intersect(&helpers::day_of_week(Weekday::Sun)?)?; - let second_week_of_june = helpers::cycle_nth_after(Grain::Week, 2, &helpers::month_day(6, 1)?)?; - Ok(sundays_of_june.intersect(&second_week_of_june)? // third sunday of June - .form(Form::Celebration)) - } + b.reg(r#"(?:la )?f[eê]te des p[eè]res"#)?, + |_| { + let sundays_of_june = helpers::month(6)?.intersect(&helpers::day_of_week(Weekday::Sun)?)?; + let second_week_of_june = helpers::cycle_nth_after(Grain::Week, 2, &helpers::month_day(6, 1)?)?; + Ok(sundays_of_june.intersect(&second_week_of_june)? // third sunday of June + .form(Form::Celebration)) + } ); b.rule_1_terminal("fêtes des mères", - b.reg(r#"(?:la )?f[eê]te des m[eè]res"#)?, - |_| { - // It is the last last sunday of may - // If it is the same day as the Pentecost, it is the first sunday of june - // This case is not supported for now - Ok(helpers::day_of_week(Weekday::Sun)?.last_of(&helpers::month(5)?)? - .form(Form::Celebration)) - } + b.reg(r#"(?:la )?f[eê]te des m[eè]res"#)?, + |_| { + // It is the last last sunday of may + // If it is the same day as the Pentecost, it is the first sunday of june + // This case is not supported for now + Ok(helpers::day_of_week(Weekday::Sun)?.last_of(&helpers::month(5)?)? + .form(Form::Celebration)) + } ); b.rule_1_terminal("fête nationale", - b.reg(r#"(?:la )?f[eê]te (?:nationale|du (?:14|quatorze) juillet)"#)?, - |_| Ok(helpers::month_day(7, 14)? - .form(Form::Celebration)) + b.reg(r#"(?:la )?f[eê]te (?:nationale|du (?:14|quatorze) juillet)"#)?, + |_| Ok(helpers::month_day(7, 14)? + .form(Form::Celebration)) ); b.rule_1_terminal("assomption", - b.reg(r#"(?:la f[eê]te de |le jour de )?l'assomption"#)?, - |_| Ok(helpers::month_day(8, 15)? - .form(Form::Celebration)) + b.reg(r#"(?:la f[eê]te de |le jour de )?l'assomption"#)?, + |_| Ok(helpers::month_day(8, 15)? + .form(Form::Celebration)) ); b.rule_2("à ", b.reg(r#"au|[aà](?:l['a])?"#)?, @@ -113,5 +111,4 @@ pub fn rules_celebration(b: &mut RuleSetBuilder) -> RustlingResult<() ); Ok(()) - } \ No newline at end of file diff --git a/grammar/fr/src/rules_duration.rs b/grammar/fr/src/rules_duration.rs index c7ad7d03..b65a8cd3 100644 --- a/grammar/fr/src/rules_duration.rs +++ b/grammar/fr/src/rules_duration.rs @@ -104,16 +104,6 @@ pub fn rules_duration(b: &mut RuleSetBuilder) -> RustlingResult<()> { integer_check_by_range!(0), |duration, integer| helpers::compose_duration_with_integer(duration.value(), integer.value()) ); - b.rule_2("dans ", - b.reg(r#"dans"#)?, - duration_check!(), - |_, duration| duration.value().in_present() - ); - b.rule_2(" plus tard", - duration_check!(), - b.reg(r"plus tard")?, - |duration, _| duration.value().in_present() - ); b.rule_2("environ ", b.reg(r#"environ|approximativement|à peu près|presque"#)?, duration_check!(), @@ -134,6 +124,9 @@ pub fn rules_duration(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.reg(r#"exactement|précisément|pile"#)?, |duration, _| Ok(duration.value().clone().precision(Precision::Exact)) ); + // Ambiguous w/ time-of-day w/ "pour" + // Duration has less priority than Datetime types, therefore duration will be output only + // if the output kind filter is set for Duration b.rule_2("pendant ", b.reg(r#"pendant|durant|pour"#)?, duration_check!(), @@ -145,4 +138,4 @@ pub fn rules_duration(b: &mut RuleSetBuilder) -> RustlingResult<()> { |_, duration| Ok(duration.value().clone().prefixed()) ); Ok(()) -} \ No newline at end of file +} diff --git a/grammar/fr/src/rules_number.rs b/grammar/fr/src/rules_number.rs index 29f98e14..8bf5e089 100644 --- a/grammar/fr/src/rules_number.rs +++ b/grammar/fr/src/rules_number.rs @@ -10,46 +10,50 @@ pub fn rules_numbers(b: &mut RuleSetBuilder) -> RustlingResult<()> { number_check!(), |a, b| helpers::compose_numbers(&a.value(), &b.value())); b.rule_1_terminal( - "number (0..16)", - b.reg(r#"(z[eé]ro|une?|deux|trois|quatre|cinq|six|sept|huit|neuf|dix|onze|douze|treize|quatorze|quinze|seize)"#)?, - |text_match| { - let value = match text_match.group(1).as_ref() { - "zéro" => 0, - "zero" => 0, - "un" => 1, - "une" => 1, - "deux" => 2, - "trois" => 3, - "quatre" => 4, - "cinq" => 5, - "six" => 6, - "sept" => 7, - "huit" => 8, - "neuf" => 9, - "dix" => 10, - "onze" => 11, - "douze" => 12, - "treize" => 13, - "quatorze" => 14, - "quinze" => 15, - "seize" => 16, - _ => return Err(RuleError::Invalid.into()), - }; - IntegerValue::new(value) - }); + "number (0..16)", + b.reg(r#"(z[eé]ro|une?|deux|trois|quatre|cinq|six|sept|huit|neuf|dix|onze|douze|treize|quatorze|quinze|seize)"#)?, + |text_match| { + let value = match text_match.group(1).as_ref() { + "zéro" => 0, + "zero" => 0, + "un" => 1, + "une" => 1, + "deux" => 2, + "trois" => 3, + "quatre" => 4, + "cinq" => 5, + "six" => 6, + "sept" => 7, + "huit" => 8, + "neuf" => 9, + "dix" => 10, + "onze" => 11, + "douze" => 12, + "treize" => 13, + "quatorze" => 14, + "quinze" => 15, + "seize" => 16, + _ => return Err(RuleError::Invalid.into()), + }; + IntegerValue::new(value) + }); + b.rule_1_terminal("quelques", + b.reg(r#"quelques"#)?, + |_| IntegerValue::new_with_grain(3, 1) + ); b.rule_1_terminal("number (20..60)", - b.reg(r#"(vingt|trente|quarante|cinquante|soixante)"#)?, - |text_match| { - let value = match text_match.group(1).as_ref() { - "vingt" => 20, - "trente" => 30, - "quarante" => 40, - "cinquante" => 50, - "soixante" => 60, - _ => return Err(RuleError::Invalid.into()), - }; - IntegerValue::new(value) - }); + b.reg(r#"(vingt|trente|quarante|cinquante|soixante)"#)?, + |text_match| { + let value = match text_match.group(1).as_ref() { + "vingt" => 20, + "trente" => 30, + "quarante" => 40, + "cinquante" => 50, + "soixante" => 60, + _ => return Err(RuleError::Invalid.into()), + }; + IntegerValue::new(value) + }); b.rule_2("number (17..19)", integer_check_by_range!(10, 10), integer_check_by_range!(7, 9), @@ -570,7 +574,235 @@ pub fn rules_numbers(b: &mut RuleSetBuilder) -> RustlingResult<()> { |text_match| { let value: i64 = text_match.group(1).parse()?; Ok(OrdinalValue::new(value)) - }); + }); + + b.rule_1_terminal("ordinal 0", + b.reg(r#"z[eé]rot?i[eè]me"#)?, + |_| { + Ok(OrdinalValue::new(0)) + } + ); + b.rule_1_terminal("ordinal 1", + b.reg(r#"premi[eè]re?"#)?, + |_| { + Ok(OrdinalValue::new(1)) + } + ); + b.rule_1_terminal("ordinal 2", + b.reg(r#"seconde?|deuxi[eè]me"#)?, + |_| { + Ok(OrdinalValue::new(2)) + } + ); + b.rule_1_terminal( + "ordinals (premier..seizieme)", + b.reg(r#"(trois|quatr|cinqu|six|sept|huit|neuv|dix|onz|douz|treiz|quatorz|quinz|seiz)i[eè]me"#)?, + |text_match| { + let value = match text_match.group(1).as_ref() { + "trois" => 3, + "quatr" => 4, + "cinqu" => 5, + "six" => 6, + "sept" => 7, + "huit" => 8, + "neuv" => 9, + "dix" => 10, + "onz" => 11, + "douz" => 12, + "treiz" => 13, + "quatorz" => 14, + "quinz" => 15, + "seiz" => 16, + _ => return Err(RuleError::Invalid.into()), + }; + Ok(OrdinalValue::new(value)) + }); + b.rule_2("17ieme, 18ieme, 19ieme", + b.reg(r#"dix-?"#)?, + ordinal_check_by_range!(7, 9), + |_, ordinal| { + Ok(OrdinalValue::new(10 + ordinal.value().value)) + } + ); + b.rule_1_terminal("20ieme, 30ieme, 40ieme, 50ieme, 60ieme", + b.reg(r#"(vingt|trent|quarant|cinquant|soixant)i[èe]me"#)?, + |text_match| { + let value = match text_match.group(1).as_ref() { + "vingt" => 20, + "trent" => 30, + "quarant" => 40, + "cinquant" => 50, + "soixant" => 60, + _ => return Err(RuleError::Invalid.into()), + }; + Ok(OrdinalValue::new(value)) + } + ); + b.rule_1_terminal("80ieme", + b.reg(r#"quatre[- ]vingts?i[èe]me"#)?, + |_| { + Ok(OrdinalValue::new(80)) + } + ); + b.rule_2("22ieme...29ieme, 32ieme...39ieme, 42ieme...49ieme, 52ieme...59ieme", + integer_check_by_range!(20, 50, |integer: &IntegerValue| integer.value % 10 == 0), + ordinal_check_by_range!(2, 9), + |integer, ordinal| { + Ok(OrdinalValue::new(integer.value().value + ordinal.value().value)) + } + ); + b.rule_3("22ieme...29ieme, 32ieme...39ieme, 42ieme...49ieme, 52ieme...59ieme", + integer_check_by_range!(20, 50, |integer: &IntegerValue| integer.value % 10 == 0), + b.reg(r"-")?, + ordinal_check_by_range!(2, 9), + |integer, _, ordinal| { + Ok(OrdinalValue::new(integer.value().value + ordinal.value().value)) + } + ); + b.rule_2("62ieme...70ieme, 72ieme...79ieme, 90ieme, 92ieme...99ieme", + integer_check_by_range!(60, 80, |integer: &IntegerValue| integer.value == 60 || integer.value == 80), + ordinal_check_by_range!(2, 19), + |integer, ordinal| { + Ok(OrdinalValue::new(integer.value().value + ordinal.value().value)) + } + ); + b.rule_3("62ieme...70ieme, 72ieme...79ieme, 90ieme, 92ieme...99ieme", + integer_check_by_range!(60, 80, |integer: &IntegerValue| integer.value == 60 || integer.value == 80), + b.reg(r"-")?, + ordinal_check_by_range!(2, 19), + |integer, _, ordinal| { + Ok(OrdinalValue::new(integer.value().value + ordinal.value().value)) + } + ); + b.rule_2("21, 31, 41, 51, 61", + integer_check_by_range!(20, 60, |integer: &IntegerValue| integer.value % 10 == 0), + b.reg(r#"(?:et |-)uni[èe]me"#)?, + |integer, _| { + Ok(OrdinalValue::new(integer.value().value + 1)) + } + ); + b.rule_2("81", + integer_check_by_range!(80, 80), + b.reg(r#"(?:et )?uni[èe]me"#)?, + |integer, _| { + Ok(OrdinalValue::new(integer.value().value + 1)) + } + ); + b.rule_2("71, 91", + integer_check_by_range!(60, 60), + b.reg(r#"et onzi[eè]me"#)?, + |integer, _| { + Ok(OrdinalValue::new(integer.value().value + 11)) + } + ); + b.rule_2(" et demi", + integer_check_by_range!(0, 99), + b.reg(r#"et demie?"#)?, + |integer, _| { + FloatValue::new(integer.value().value as f32 + 0.5) + } + ); + b.rule_1_terminal("70, 80, 90 (Belgium and Switzerland)", + b.reg(r#"(sept|huit|non)ante"#)?, + |text_match| { + let value = match text_match.group(1).as_ref() { + "sept" => 70, + "huit" => 80, + "non" => 90, + _ => return Err(RuleError::Invalid.into()), + }; + IntegerValue::new(value) + } + ); + b.rule_1_terminal("71, 81, 91 (Belgium and Switzerland)", + b.reg(r#"(sept|huit|non)ante et une?"#)?, + |text_match| { + let value = match text_match.group(1).as_ref() { + "sept" => 71, + "huit" => 81, + "non" => 91, + _ => return Err(RuleError::Invalid.into()), + }; + IntegerValue::new(value) + } + ); + + b.rule_2("72..79, 82..89, 92..99, (Belgium and Switzerland)", + b.reg(r#"(sept|huit|non)ante"#)?, + integer_check_by_range!(2, 9), + |text_match, integer| { + let value = match text_match.group(1).as_ref() { + "sept" => 70, + "huit" => 80, + "non" => 90, + _ => return Err(RuleError::Invalid.into()), + }; + IntegerValue::new(value + integer.value().value) + } + ); + b.rule_1_terminal("ordinal (100, 1_000, 1_000_000)", + b.reg(r#"(cent|mill|million|milliard)i[èe]me"#)?, + |text_match| { + let (value, grain) = match text_match.group(1).as_ref() { + "cent" => (100, 2), + "mill" => (1_000, 3), + "million" => (1_000_000, 6), + "milliard" => (1_000_000_000, 9), + _ => return Err(RuleError::Invalid.into()), + }; + Ok(OrdinalValue::new_with_grain(value, grain)) + } + ); + + b.rule_2("ordinal (200..900, 2_000..9_000, 2_000_000..9_000_000_000)", + integer_check_by_range!(2, 999), + b.reg(r#"(cent|mill|million|milliard)i[èe]me"#)?, + |integer, text_match| { + let (value, grain) = match text_match.group(1).as_ref() { + "cent" => (100, 2), + "mill" => (1_000, 3), + "million" => (1_000_000, 6), + "milliard" => (1_000_000_000, 9), + _ => return Err(RuleError::Invalid.into()), + }; + Ok(OrdinalValue::new_with_grain(integer.value().value * value, grain)) + } + ); + + b.rule_2("ordinal (1_1_000..9_999_999_000)", + integer_check_by_range!(1000, 99_999_999_000), + ordinal_check!(|ordinal: &OrdinalValue| { + let grain = ordinal.grain.unwrap_or(0); + grain == 2 || grain % 3 == 0 + }), + |integer, ordinal| { + let grain = ordinal.value().grain.unwrap_or(0); + let next_grain = (grain / 3) * 3 + 3; + if integer.value().value % 10i64.pow(next_grain as u32) != 0 { return Err(RuleError::Invalid.into()); } + Ok(OrdinalValue::new(integer.value().value + ordinal.value().value)) + } + ); + + b.rule_2("ordinal (102...9_999_999)", + integer_check!(|integer: &IntegerValue| integer.value >= 100 || integer.value % 100 == 0), + ordinal_check_by_range!(2, 99), + |integer, ordinal| { + Ok(OrdinalValue::new(integer.value().value + ordinal.value().value)) + } + ); + b.rule_2("ordinal (101, 201, 301, ...)", + integer_check!(|integer: &IntegerValue| integer.value >= 100 || integer.value % 100 == 0), + b.reg(r#"(?:et |-)?uni[èe]me"#)?, + |integer, _| { + Ok(OrdinalValue::new(integer.value().value + 1)) + } + ); + b.rule_1_terminal("ordinal (digits)", + b.reg(r#"0*(\d+) ?(ere?|ère|ème|eme|ieme|ième)"#)?, + |text_match| { + let value: i64 = text_match.group(1).parse()?; + Ok(OrdinalValue::new(value)) + }); b.rule_2("le ", b.reg(r#"l[ea]"#)?, ordinal_check!(), @@ -578,3 +810,4 @@ pub fn rules_numbers(b: &mut RuleSetBuilder) -> RustlingResult<()> { ); Ok(()) } + From 9de02022bfde6dff6b2059584d23e860c6421d84 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Thu, 20 Jun 2019 18:28:04 +0200 Subject: [PATCH 061/107] =?UTF-8?q?Add=20support=20for=20mid/end/beg=20sea?= =?UTF-8?q?son,=20mi-journ=C3=A9e,=20prep=20+=20part-of-day.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- grammar/fr/src/rules_datetime.rs | 74 ++++++++++++++++++++++++++++---- 1 file changed, 66 insertions(+), 8 deletions(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 638ee995..103a1c13 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -801,6 +801,11 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { datetime_check!(form!(Form::TimeOfDay(_))), |_, a| Ok(a.value().clone().not_latent()) ); + b.rule_2("à ", + b.reg(r#"[aà]|pour"#)?, + datetime_check!(form!(Form::PartOfDay(_))), + |_, a| Ok(a.value().clone().not_latent()) + ); b.rule_2("vers ", b.reg(r#"(?:plut[ôo]t )?(?:vers|autour de|[aà] environ|aux alentours de)"#)?, datetime_check!(form!(Form::TimeOfDay(_))), @@ -1025,13 +1030,13 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { } ); b.rule_1_terminal("milieu de journée", - b.reg(r#"milieu de (?:la )?journ[ée]e"#)?, - |_| { - Ok(helpers::hour(11, false)? - .span_to(&helpers::hour(16, false)?, false)? - .latent() - .form(Form::PartOfDay(PartOfDayForm::None))) - } + b.reg(r#"(?:milieu de (?:la )?|(?:(?:[àa] )?la )?mi[ -])journ[ée]e"#)?, + |_| { + Ok(helpers::hour(11, false)? + .span_to(&helpers::hour(16, false)?, false)? + .latent() + .form(Form::PartOfDay(PartOfDayForm::None))) + } ); b.rule_1_terminal("fin de journée", b.reg(r#"fin de (?:la )?journ[ée]e"#)?, @@ -1199,6 +1204,54 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.reg(r#"(?:ce )?printemps"#)?, |_| helpers::month_day(3, 20)?.span_to(&helpers::month_day(6, 21)?, false) ); + b.rule_1_terminal("début de l'été", + b.reg(r#"début de (?:cet |l')?(?:été|ete)"#)?, + |_| helpers::month_day(6, 21)?.span_to(&helpers::month_day(7, 15)?, false) + ); + b.rule_1_terminal("milieu de l'été", + b.reg(r#"milieu de (?:cet |l')?(?:été|ete)"#)?, + |_| helpers::month_day(7, 15)?.span_to(&helpers::month_day(8, 15)?, false) + ); + b.rule_1_terminal("fin de l'été", + b.reg(r#"fin de (?:cet |l')?(?:été|ete)"#)?, + |_| helpers::month_day(8, 15)?.span_to(&helpers::month_day(9, 21)?, false) + ); + b.rule_1_terminal("début de l'automne", + b.reg(r#"début de (?:cet |l')?(?:été|ete)"#)?, + |_| helpers::month_day(9, 21)?.span_to(&helpers::month_day(10, 15)?, false) + ); + b.rule_1_terminal("milieu de l'automne", + b.reg(r#"milieu de (?:cet |l')?(?:été|ete)"#)?, + |_| helpers::month_day(10, 15)?.span_to(&helpers::month_day(11, 15)?, false) + ); + b.rule_1_terminal("fin de l'automne", + b.reg(r#"fin de (?:cet |l')?(?:été|ete)"#)?, + |_| helpers::month_day(11, 15)?.span_to(&helpers::month_day(12, 21)?, false) + ); + b.rule_1_terminal("début de l'hiver", + b.reg(r#"début de (?:cet |l')?(?:été|ete)"#)?, + |_| helpers::month_day(12, 21)?.span_to(&helpers::month_day(1, 15)?, false) + ); + b.rule_1_terminal("milieu de l'hiver", + b.reg(r#"milieu de (?:cet |l')?(?:été|ete)"#)?, + |_| helpers::month_day(1, 15)?.span_to(&helpers::month_day(2, 15)?, false) + ); + b.rule_1_terminal("fin de l'hiver", + b.reg(r#"fin de (?:cet |l')?(?:été|ete)"#)?, + |_| helpers::month_day(2, 15)?.span_to(&helpers::month_day(3, 21)?, false) + ); + b.rule_1_terminal("début du printemps", + b.reg(r#"début de (?:cet |l')?(?:été|ete)"#)?, + |_| helpers::month_day(3, 21)?.span_to(&helpers::month_day(4, 15)?, false) + ); + b.rule_1_terminal("milieu du printemps", + b.reg(r#"milieu de (?:cet |l')?(?:été|ete)"#)?, + |_| helpers::month_day(4, 15)?.span_to(&helpers::month_day(5, 15)?, false) + ); + b.rule_1_terminal("fin du printemps", + b.reg(r#"fin de (?:cet |l')?(?:été|ete)"#)?, + |_| helpers::month_day(5, 15)?.span_to(&helpers::month_day(6, 21)?, false) + ); b.rule_2("le ", b.reg(r#"l[ea]"#)?, datetime_check!(|datetime: &DatetimeValue| !datetime.latent), @@ -1445,6 +1498,11 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { datetime_check!(|datetime: &DatetimeValue| !datetime.latent), |_, datetime| Ok(datetime.value().clone().mark_before_start()) ); + b.rule_2("avant ", + b.reg(r#"(?:n[ ']importe quand )?(avant|jusqu'(?:au|[aà]|en))"#)?, + datetime_check!(form!(Form::PartOfDay(_))), + |_, datetime| Ok(datetime.value().clone().mark_before_start()) + ); b.rule_2("après ", b.reg(r#"apr[eè]s"#)?, datetime_check!(|datetime: &DatetimeValue| !datetime.latent), @@ -1456,7 +1514,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { |_, datetime| Ok(datetime.value().clone().mark_after_start()) ); b.rule_2("à partir de ", - b.reg(r#"[aà] partir d['eu](?:l[ 'a])?"#)?, + b.reg(r#"[aà] partir d['eu](?:l[ 'a])?|après"#)?, datetime_check!(form!(Form::PartOfDay(_))), |_, datetime| Ok(datetime.value().clone().mark_after_start()) ); From e10415532efe7009ce694ea35e6cc0950e397f59 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Thu, 18 Jul 2019 11:59:22 +0200 Subject: [PATCH 062/107] Add support for POD 'sunrise', 'middle of the night', and changed hour range for mid-day. --- grammar/fr/src/rules_datetime.rs | 23 ++++++++++++++++++++--- grammar/fr/src/training.rs | 2 +- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 103a1c13..72aa3262 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -890,6 +890,13 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { .latent() .form(Form::PartOfDay(PartOfDayForm::Morning))) ); + b.rule_1_terminal("lever du soleil", + b.reg(r#"lever du soleil|aurore|aube"#)?, + |_| Ok(helpers::hour(5, false)? + .span_to(&helpers::hour(9, false)?, false)? + .latent() + .form(Form::PartOfDay(PartOfDayForm::Morning))) + ); b.rule_1_terminal("petit dejeuner", b.reg(r#"petit[- ]d[ée]jeuner"#)?, |_| Ok(helpers::hour(5, false)? @@ -898,11 +905,12 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { .form(Form::Meal)) ); b.rule_1_terminal("milieu de matinée", - b.reg(r#"milieu de matin[ée]e"#)?, - |_| Ok(helpers::hour(9, false)? + b.reg(r#"(?:le )?milieu de matin[ée]e"#)?, + |_| Ok(helpers::hour(9, false)? .span_to(&helpers::hour(11, false)?, false)? .latent() .form(Form::PartOfDay(PartOfDayForm::Morning))) + ); b.rule_1_terminal("brunch", b.reg(r#"brunch"#)?, @@ -1032,7 +1040,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.rule_1_terminal("milieu de journée", b.reg(r#"(?:milieu de (?:la )?|(?:(?:[àa] )?la )?mi[ -])journ[ée]e"#)?, |_| { - Ok(helpers::hour(11, false)? + Ok(helpers::hour(12, false)? .span_to(&helpers::hour(16, false)?, false)? .latent() .form(Form::PartOfDay(PartOfDayForm::None))) @@ -1089,6 +1097,15 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { .form(Form::PartOfDay(PartOfDayForm::Night))) } ); + b.rule_1_terminal("milieu de la nuit", + b.reg(r#"(?:le )?milieu de la nuit"#)?, + |_| { + Ok(helpers::hour(2, false)? + .span_to(&helpers::hour(4, false)?, false)? + .latent() + .form(Form::PartOfDay(PartOfDayForm::Night))) + } + ); b.rule_2("a l'heure de ", b.reg(r#"(?:[àa] )?l[' ]heure du|au moment du|pendant l[ea']|au|pour l[ea']|l[ea']"#)?, datetime_check!(|datetime: &DatetimeValue| form!(Form::Meal)(datetime)), diff --git a/grammar/fr/src/training.rs b/grammar/fr/src/training.rs index 5b2e435b..036a392f 100644 --- a/grammar/fr/src/training.rs +++ b/grammar/fr/src/training.rs @@ -174,7 +174,7 @@ pub fn examples_datetime(v: &mut Vec<::rustling::train::Example>) { example!(v, check_moment_span!(c, [2013, 2, 12, 12], [2013, 2, 12, 15]), "en début d'après-midi", "en début d'aprem"); example!(v, check_moment_span!(c, [2013, 2, 12, 17], [2013, 2, 12, 19]), "en fin d'après-midi", "en fin d'aprem"); example!(v, check_moment_span!(c, [2013, 2, 12, 6], [2013, 2, 12, 10]), "en début de journée"); - example!(v, check_moment_span!(c, [2013, 2, 12, 11], [2013, 2, 12, 16]), "milieu de journée"); + example!(v, check_moment_span!(c, [2013, 2, 12, 12], [2013, 2, 12, 16]), "milieu de journée"); example!(v, check_moment_span!(c, [2013, 2, 12, 17], [2013, 2, 12, 21]), "en fin de journée"); example!(v, check_moment_span!(c, [2013, 2, 12, 18], [2013, 2, 13, 00]), "ce soir"); example!(v, check_moment_span!(c, [2013, 2, 12, 18], [2013, 2, 12, 21]), "en début de soirée"); From 6db1dfc793a13f83f4d45450a5811578d6dd2904 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Thu, 18 Jul 2019 14:31:02 +0200 Subject: [PATCH 063/107] Add support for POD 'sunset'. --- grammar/fr/src/rules_datetime.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 72aa3262..a471dc64 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -1064,6 +1064,15 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { .form(Form::PartOfDay(PartOfDayForm::Evening))) } ); + b.rule_1_terminal("coucher du soleil", + b.reg(r#"coucher du soleil|cr[eé]puscule|tomb[ée]e de la nuit"#)?, + |_| { + Ok(helpers::hour(18, false)? + .span_to(&helpers::hour(21, false)?, false)? + .latent() + .form(Form::PartOfDay(PartOfDayForm::Evening))) + } + ); b.rule_1_terminal("début de soirée", b.reg(r#"d[ée]but de (?:la )?soir[ée]e?"#)?, |_| { @@ -1098,7 +1107,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { } ); b.rule_1_terminal("milieu de la nuit", - b.reg(r#"(?:le )?milieu de la nuit"#)?, + b.reg(r#"milieu de la nuit"#)?, |_| { Ok(helpers::hour(2, false)? .span_to(&helpers::hour(4, false)?, false)? From c3c5f7db2f2edc41334dfbd47eea264cdd6c654f Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Thu, 18 Jul 2019 15:17:04 +0200 Subject: [PATCH 064/107] Corrected rule 'lever du soleil', Changed hour range 'aube'. --- grammar/fr/src/rules_datetime.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index a471dc64..51ac29d3 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -891,9 +891,9 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { .form(Form::PartOfDay(PartOfDayForm::Morning))) ); b.rule_1_terminal("lever du soleil", - b.reg(r#"lever du soleil|aurore|aube"#)?, - |_| Ok(helpers::hour(5, false)? - .span_to(&helpers::hour(9, false)?, false)? + b.reg(r#"lever d[ue] soleil|aurore|aube"#)?, + |_| Ok(helpers::hour(4, false)? + .span_to(&helpers::hour(8, false)?, false)? .latent() .form(Form::PartOfDay(PartOfDayForm::Morning))) ); @@ -1067,8 +1067,8 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.rule_1_terminal("coucher du soleil", b.reg(r#"coucher du soleil|cr[eé]puscule|tomb[ée]e de la nuit"#)?, |_| { - Ok(helpers::hour(18, false)? - .span_to(&helpers::hour(21, false)?, false)? + Ok(helpers::hour(19, false)? + .span_to(&helpers::hour(22, false)?, false)? .latent() .form(Form::PartOfDay(PartOfDayForm::Evening))) } @@ -1540,10 +1540,15 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { |_, datetime| Ok(datetime.value().clone().mark_after_start()) ); b.rule_2("à partir de ", - b.reg(r#"[aà] partir d['eu](?:l[ 'a])?|après"#)?, + b.reg(r#"[aà] partir d['eu](?:l[ 'a])?"#)?, datetime_check!(form!(Form::PartOfDay(_))), |_, datetime| Ok(datetime.value().clone().mark_after_start()) ); + b.rule_2("après ", + b.reg(r#"après"#)?, + datetime_check!(form!(Form::PartOfDay(_))), + |_, datetime| Ok(datetime.value().clone().mark_after_end()) + ); b.rule_2("après le ", b.reg(r#"apr(?:e|è)s le"#)?, integer_check_by_range!(1, 31), From be925f367b13a399dfd59e1c7cf59aea09b409a6 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Thu, 18 Jul 2019 15:52:15 +0200 Subject: [PATCH 065/107] =?UTF-8?q?Added=20rule=20from=20=20t?= =?UTF-8?q?o=20.=20Added=20article=20in=20'fin=20de=20la=20ma?= =?UTF-8?q?tin=C3=A9e'.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- grammar/fr/src/rules_datetime.rs | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 51ac29d3..83ed1158 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -920,11 +920,11 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { .form(Form::Meal)) ); b.rule_1_terminal("fin de matinée", - b.reg(r#"fin de matin[ée]e"#)?, - |_| Ok(helpers::hour(10, false)? - .span_to(&helpers::hour(12, false)?, false)? - .latent() - .form(Form::PartOfDay(PartOfDayForm::Morning))) + b.reg(r#"fin de (?:la )?matin[ée]e"#)?, + |_| Ok(helpers::hour(10, false)? + .span_to(&helpers::hour(12, false)?, false)? + .latent() + .form(Form::PartOfDay(PartOfDayForm::Morning))) ); b.rule_1_terminal("déjeuner", b.reg(r#"d[eéè]jeuner"#)?, @@ -1493,6 +1493,22 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { datetime_check!(form!(Form::TimeOfDay(_))), |_, a, _, b| a.value().smart_span_to(b.value(), false) ); + b.rule_4("de - (interval)", + b.reg(r#"(?:[aà] partir )?d['eu]"#)?, + datetime_check!(form!(Form::PartOfDay(_))), + b.reg(r#"(?:jusqu')?(?:au|[aà])"#)?, + datetime_check!(form!(Form::PartOfDay(_))), + //datetime.value().clone().mark_before_start() + |_, a, _, b| a.value().smart_span_to(b.value(), false) + ); + b.rule_4("de - (interval)", + b.reg(r#"(?:[aà] partir )?d['e]"#)?, + datetime_check!(form!(Form::Meal)), + b.reg(r#"(?:jusqu')?(?:au|[aà])"#)?, + datetime_check!(form!(Form::Meal)), + //datetime.value().clone().mark_before_start() + |_, a, _, b| a.value().smart_span_to(b.value(), false) + ); b.rule_2("de maintenant - (interval)", b.reg(r#"(?:[aà] partir )?de maintenant (?:jusqu')?(?:au|[aà])"#)?, datetime_check!(form!(Form::TimeOfDay(_))), From c8738afed1395f7d11843edebafa02c1b784dace Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Thu, 18 Jul 2019 17:03:26 +0200 Subject: [PATCH 066/107] Add support for 'last '. --- grammar/fr/src/rules_datetime.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 83ed1158..287db3e4 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -499,10 +499,15 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { |datetime, _| datetime.value().the_nth_not_immediate(0) ); b.rule_2("au prochain ", - b.reg(r#"(au|[aà] la) prochaine?"#)?, + b.reg(r#"(au |(?:[aà] )?la )prochaine?"#)?, datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), |_, datetime| datetime.value().the_nth_not_immediate(0) ); + b.rule_2("au dernier ", + b.reg(r#"(au |(?:[aà] )?la )dernier?"#)?, + datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), + |_, datetime| datetime.value().the_nth_not_immediate(-1) + ); b.rule_2(" prochain", // The direction check is to avoid application of datetime_check(month) on rule result // "avant " From 9162a523eb7317ba5ad02bd117d86b3d822266fd Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Thu, 18 Jul 2019 17:16:37 +0200 Subject: [PATCH 067/107] Correction rule 'au (prochain|dernier) '. --- grammar/fr/src/rules_datetime.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 287db3e4..25199c8e 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -499,12 +499,12 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { |datetime, _| datetime.value().the_nth_not_immediate(0) ); b.rule_2("au prochain ", - b.reg(r#"(au |(?:[aà] )?la )prochaine?"#)?, + b.reg(r#"(au |(?:[aà] )?l[ae] )prochaine?"#)?, datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), |_, datetime| datetime.value().the_nth_not_immediate(0) ); b.rule_2("au dernier ", - b.reg(r#"(au |(?:[aà] )?la )dernier?"#)?, + b.reg(r#"(au |(?:[aà] )?l[ea] )derni[eè]re?"#)?, datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), |_, datetime| datetime.value().the_nth_not_immediate(-1) ); From 3e0bd4ee740dd8efc7e59a92b1453926a238fb59 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Thu, 18 Jul 2019 17:58:19 +0200 Subject: [PATCH 068/107] Remove useless comments. --- grammar/fr/src/rules_datetime.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 25199c8e..cb65ae7a 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -1503,7 +1503,6 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { datetime_check!(form!(Form::PartOfDay(_))), b.reg(r#"(?:jusqu')?(?:au|[aà])"#)?, datetime_check!(form!(Form::PartOfDay(_))), - //datetime.value().clone().mark_before_start() |_, a, _, b| a.value().smart_span_to(b.value(), false) ); b.rule_4("de - (interval)", @@ -1511,7 +1510,6 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { datetime_check!(form!(Form::Meal)), b.reg(r#"(?:jusqu')?(?:au|[aà])"#)?, datetime_check!(form!(Form::Meal)), - //datetime.value().clone().mark_before_start() |_, a, _, b| a.value().smart_span_to(b.value(), false) ); b.rule_2("de maintenant - (interval)", From 52db5ec1f19f61c263bedeb5a8e609804802470d Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Mon, 22 Jul 2019 11:11:41 +0200 Subject: [PATCH 069/107] Correction rules 'au dernier ', 'au prochain >datetime>'. --- grammar/fr/src/rules_datetime.rs | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index cb65ae7a..3b237708 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -498,15 +498,25 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.reg(r#"prochaine?"#)?, |datetime, _| datetime.value().the_nth_not_immediate(0) ); - b.rule_2("au prochain ", + // TODO: add restrictions on datetime form? + b.rule_2("au prochain ", b.reg(r#"(au |(?:[aà] )?l[ae] )prochaine?"#)?, - datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), - |_, datetime| datetime.value().the_nth_not_immediate(0) + datetime_check!(|datetime: &DatetimeValue| !form!(Form::PartOfDay(_))(datetime) && !form!(Form::Meal)(datetime)), + |_, a| { + Ok(a.value().the_nth(0)? + .form(a.value().form.clone()) + .datetime_kind(a.value().datetime_kind.clone())) + } ); - b.rule_2("au dernier ", + // TODO: add restrictions on datetime form? + b.rule_2("au dernier ", b.reg(r#"(au |(?:[aà] )?l[ea] )derni[eè]re?"#)?, - datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), - |_, datetime| datetime.value().the_nth_not_immediate(-1) + datetime_check!(|datetime: &DatetimeValue| !form!(Form::PartOfDay(_))(datetime) && !form!(Form::Meal)(datetime)), + |_, a| { + Ok(a.value().the_nth(-1)? + .form(a.value().form.clone()) + .datetime_kind(a.value().datetime_kind.clone())) + } ); b.rule_2(" prochain", // The direction check is to avoid application of datetime_check(month) on rule result From d58c29f704971a2bf308189f708aac991e99d6d3 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Mon, 22 Jul 2019 16:02:09 +0200 Subject: [PATCH 070/107] =?UTF-8?q?Add=20support=20for=20'fin=20de=20cette?= =?UTF-8?q?=20ann=C3=A9e'.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- grammar/fr/src/rules_datetime.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 3b237708..a554a97a 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -1293,6 +1293,15 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.reg(r#"fin de (?:cet |l')?(?:été|ete)"#)?, |_| helpers::month_day(5, 15)?.span_to(&helpers::month_day(6, 21)?, false) ); + b.rule_1_terminal("fin de cette année", + b.reg(r#"fin (?:de (?:l'|cette )|d')?ann[ée]e"#)?, + |_| { + let current_year = helpers::cycle_nth(Grain::Year, 0)?; + let start = current_year.intersect(&helpers::month(10)?)?; + let end = current_year.intersect(&helpers::month(12)?)?; + start.span_to(&end, true) + } + ); b.rule_2("le ", b.reg(r#"l[ea]"#)?, datetime_check!(|datetime: &DatetimeValue| !datetime.latent), From 4d6ad6323cefa4ca2b1c92a002ecde3e7a9b5614 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Mon, 22 Jul 2019 16:22:52 +0200 Subject: [PATCH 071/107] Corrections rules 'end/mid/beg of season'. --- grammar/fr/src/rules_datetime.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index a554a97a..4028be96 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -1258,39 +1258,39 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { |_| helpers::month_day(8, 15)?.span_to(&helpers::month_day(9, 21)?, false) ); b.rule_1_terminal("début de l'automne", - b.reg(r#"début de (?:cet |l')?(?:été|ete)"#)?, + b.reg(r#"début de (?:cet |l')?automne"#)?, |_| helpers::month_day(9, 21)?.span_to(&helpers::month_day(10, 15)?, false) ); b.rule_1_terminal("milieu de l'automne", - b.reg(r#"milieu de (?:cet |l')?(?:été|ete)"#)?, + b.reg(r#"milieu de (?:cet |l')?automne"#)?, |_| helpers::month_day(10, 15)?.span_to(&helpers::month_day(11, 15)?, false) ); b.rule_1_terminal("fin de l'automne", - b.reg(r#"fin de (?:cet |l')?(?:été|ete)"#)?, + b.reg(r#"fin de (?:cet |l')?automne"#)?, |_| helpers::month_day(11, 15)?.span_to(&helpers::month_day(12, 21)?, false) ); b.rule_1_terminal("début de l'hiver", - b.reg(r#"début de (?:cet |l')?(?:été|ete)"#)?, + b.reg(r#"début de (?:cet |l')?hiver"#)?, |_| helpers::month_day(12, 21)?.span_to(&helpers::month_day(1, 15)?, false) ); b.rule_1_terminal("milieu de l'hiver", - b.reg(r#"milieu de (?:cet |l')?(?:été|ete)"#)?, + b.reg(r#"milieu de (?:cet |l')?hiver"#)?, |_| helpers::month_day(1, 15)?.span_to(&helpers::month_day(2, 15)?, false) ); b.rule_1_terminal("fin de l'hiver", - b.reg(r#"fin de (?:cet |l')?(?:été|ete)"#)?, + b.reg(r#"fin de (?:cet |l')?hiver"#)?, |_| helpers::month_day(2, 15)?.span_to(&helpers::month_day(3, 21)?, false) ); b.rule_1_terminal("début du printemps", - b.reg(r#"début de (?:cet |l')?(?:été|ete)"#)?, + b.reg(r#"début (?:du|de ce)? printemps"#)?, |_| helpers::month_day(3, 21)?.span_to(&helpers::month_day(4, 15)?, false) ); b.rule_1_terminal("milieu du printemps", - b.reg(r#"milieu de (?:cet |l')?(?:été|ete)"#)?, + b.reg(r#"milieu (?:du|de ce)? printemps"#)?, |_| helpers::month_day(4, 15)?.span_to(&helpers::month_day(5, 15)?, false) ); b.rule_1_terminal("fin du printemps", - b.reg(r#"fin de (?:cet |l')?(?:été|ete)"#)?, + b.reg(r#"fin (?:du|de ce)? printemps"#)?, |_| helpers::month_day(5, 15)?.span_to(&helpers::month_day(6, 21)?, false) ); b.rule_1_terminal("fin de cette année", From 2cec1d7e1022021c2f5addd80797883b98691809 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Mon, 22 Jul 2019 16:39:48 +0200 Subject: [PATCH 072/107] Enlarge coverage rule 'fin du mois'. --- grammar/fr/src/rules_datetime.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 4028be96..d48b3311 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -226,12 +226,15 @@ pub fn rules_datetime_with_cycle(b: &mut RuleSetBuilder) -> RustlingR datetime_check!(), |_, cycle, _, datetime| helpers::cycle_nth_after(cycle.value().grain, -1, datetime.value()) ); - b.rule_4(" de ", - ordinal_check_by_range!(1, 9999), - cycle_check!(), - b.reg(r#"d['eu]|en"#)?, - datetime_check!(), - |ordinal, cycle, _, datetime| helpers::cycle_nth_after_not_immediate(cycle.value().grain, ordinal.value().value - 1, datetime.value()) + b.rule_1_terminal("fin du mois", + b.reg(r#"(?:(?:(?:[aà] )?la|en)? )?fin (?:du|de) mois"#)?, + |_| { + let month = helpers::cycle_nth(Grain::Month, 1)?; + Ok(helpers::cycle_nth_after(Grain::Day, -10, &month)? + .span_to(&month, false)? + .latent() + .form(Form::PartOfMonth)) + } ); b.rule_5("le de ", b.reg(r#"l[ea]"#)?, From 7d448c874d127546318a046bebe272a06e587a67 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Mon, 22 Jul 2019 17:38:53 +0200 Subject: [PATCH 073/107] =?UTF-8?q?Add=20support=20for=20'd=C3=A9but=20de?= =?UTF-8?q?=20l'ann=C3=A9e'.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- grammar/fr/src/rules_datetime.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index d48b3311..0f50ad18 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -1305,8 +1305,18 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { start.span_to(&end, true) } ); + b.rule_1_terminal("début de cette année", + b.reg(r#"d[ée]but (?:de (?:l'|cette )|d')?ann[ée]e"#)?, + |_| { + let current_year = helpers::cycle_nth(Grain::Year, 0)?; + let start = current_year.intersect(&helpers::month(1)?)?; + let end = current_year.intersect(&helpers::month(2)?)?; + start.span_to(&end, true) + } + ); b.rule_2("le ", b.reg(r#"l[ea]"#)?, + //b.reg(r#"l[ea]|en|au|à|pour"#)?, datetime_check!(|datetime: &DatetimeValue| !datetime.latent), |_, a| Ok(a.value().clone()) ); From efd832b11d21e2cd27bc9257d7b769853d75c0ef Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Mon, 22 Jul 2019 17:51:23 +0200 Subject: [PATCH 074/107] =?UTF-8?q?Add=20prep=20+=20datetime=20'a|en|au|?= =?UTF-8?q?=C3=A0|pour'.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- grammar/fr/src/rules_datetime.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 0f50ad18..2c1ac960 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -1315,8 +1315,8 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { } ); b.rule_2("le ", - b.reg(r#"l[ea]"#)?, - //b.reg(r#"l[ea]|en|au|à|pour"#)?, + //b.reg(r#"l[ea]"#)?, + b.reg(r#"l[ea]|en|au|à|pour"#)?, datetime_check!(|datetime: &DatetimeValue| !datetime.latent), |_, a| Ok(a.value().clone()) ); From dbffa98b0a2a80297075df8973853fd778a7ecfc Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Tue, 23 Jul 2019 11:40:17 +0200 Subject: [PATCH 075/107] =?UTF-8?q?Add=20preposition=20'au'=20in=20rule=20?= =?UTF-8?q?'go=C3=BBter'.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- grammar/fr/src/rules_datetime.rs | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 2c1ac960..92d57485 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -4,11 +4,30 @@ use rustling_ontology_values::helpers; use rustling_ontology_moment::{Weekday, Grain}; +<<<<<<< HEAD /* DATETIME - CYCLE DEFINITIONS */ pub fn rules_cycle(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.rule_1_terminal("seconde (cycle)", b.reg(r#"secondes?"#)?, |_| CycleValue::new(Grain::Second) +======= +pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { + b.rule_2("intersect", + datetime_check!(|datetime: &DatetimeValue| !datetime.latent), + datetime_check!(|datetime: &DatetimeValue| !datetime.latent), + |a, b| a.value().intersect(b.value()) + ); + b.rule_2("intersect + ", + datetime_check!(form!(Form::DayOfWeek{..})), + datetime_check!(|datetime: &DatetimeValue| !form!(Form::PartOfDay(_))(datetime) && !form!(Form::Meal)(datetime)), + |a, b| a.value().intersect(b.value()) + ); + b.rule_3("intersect by 'de' or ','", + datetime_check!(|datetime: &DatetimeValue| !datetime.latent), + b.reg(r#"de|,"#)?, + datetime_check!(|datetime: &DatetimeValue| !datetime.latent), + |a, _, b| a.value().intersect(b.value()) +>>>>>>> Add preposition 'au' in rule 'goûter'. ); b.rule_1_terminal("minute (cycle)", b.reg(r#"minutes?"#)?, @@ -1019,10 +1038,10 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { } ); b.rule_1_terminal("gouter", - b.reg(r#"(?:(?:[àa] )?l[' ]heure du|au moment du|pendant le|pour le) go[uû]ter"#)?, - |_| Ok(helpers::hour(16, false)? - .span_to(&helpers::hour(18, false)?, false)? - .form(Form::Meal)) + b.reg(r#"(?:(?:[àa] )?l[' ]heure du|au(?: moment du)?|pendant le|pour le) go[uû]ter"#)?, + |_| Ok(helpers::hour(16, false)? + .span_to(&helpers::hour(18, false)?, false)? + .form(Form::Meal)) ); b.rule_1_terminal("thé", b.reg(r#"(?:(?:[àa] )?l[' ]heure du|au moment du|pendant le|pour le) th[eé]"#)?, From 8b6ec85aea2c8c47aedfcf6786c765b75b8057c0 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Tue, 23 Jul 2019 11:43:58 +0200 Subject: [PATCH 076/107] Delete redundant rules. One rule for intersection . --- grammar/fr/src/rules_datetime.rs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 92d57485..d95d0de7 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -17,11 +17,6 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { datetime_check!(|datetime: &DatetimeValue| !datetime.latent), |a, b| a.value().intersect(b.value()) ); - b.rule_2("intersect + ", - datetime_check!(form!(Form::DayOfWeek{..})), - datetime_check!(|datetime: &DatetimeValue| !form!(Form::PartOfDay(_))(datetime) && !form!(Form::Meal)(datetime)), - |a, b| a.value().intersect(b.value()) - ); b.rule_3("intersect by 'de' or ','", datetime_check!(|datetime: &DatetimeValue| !datetime.latent), b.reg(r#"de|,"#)?, @@ -1157,11 +1152,6 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { datetime_check!(|datetime: &DatetimeValue| form!(Form::Meal)(datetime)), |_, a| Ok(a.value().clone().not_latent()) ); - b.rule_2(" ", - datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), - datetime_check!(form!(Form::Meal)), - |a, b| a.value().intersect(b.value()) - ); b.rule_2("prep? & article ", // This is very catch-all/junky b.reg(r#"(?:pendant |durant |dans |d[eè]s )?l[ae']?|en|au"#)?, datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime)), @@ -1175,7 +1165,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { .form(datetime.value().form.clone()) .datetime_kind(DatetimeKind::DatetimeComplement { date_and_time: true, today: true })) ); - b.rule_2(" ", + b.rule_2("intersect ", datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), |a, b| a.value().intersect(b.value()) From 19c87c22a3b09f60539db727937019e2b87004ae Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Tue, 23 Jul 2019 15:01:40 +0200 Subject: [PATCH 077/107] =?UTF-8?q?Add=20rule=20interval=20=20-=20=20to=20support=20'en?= =?UTF-8?q?tre=204H=20et=20le=20d=C3=AEner'=20for=20example.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- grammar/fr/src/rules_datetime.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index d95d0de7..ddb776e9 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -1509,6 +1509,34 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), |_, a, _, b| a.value().span_to(b.value(), true) ); + b.rule_4("entre et (interval)", + b.reg(r#"entre"#)?, + datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), + b.reg(r#"et"#)?, + datetime_check!(|datetime: &DatetimeValue| form!(Form::TimeOfDay(_))(datetime)), + |_, a, _, b| a.value().span_to(b.value(), true) + ); + b.rule_4("entre et (interval)", + b.reg(r#"d[eu]"#)?, + datetime_check!(|datetime: &DatetimeValue| form!(Form::TimeOfDay(_))(datetime)), + b.reg(r#"(?:jusqu')?(?:à|au)"#)?, + datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), + |_, a, _, b| a.value().span_to(b.value(), true) + ); + b.rule_4("entre et (interval)", + b.reg(r#"d[eu]"#)?, + datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), + b.reg(r#"(?:jusqu')?(?:à|au)"#)?, + datetime_check!(|datetime: &DatetimeValue| form!(Form::TimeOfDay(_))(datetime)), + |_, a, _, b| a.value().span_to(b.value(), true) + ); + b.rule_4("entre et (interval)", + b.reg(r#"entre"#)?, + datetime_check!(|datetime: &DatetimeValue| form!(Form::TimeOfDay(_))(datetime)), + b.reg(r#"et"#)?, + datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), + |_, a, _, b| a.value().span_to(b.value(), true) + ); // Specific case with years b.rule_5("de - (interval)", b.reg(r#"depuis|d[e'u]?"#)?, From 6297761db419825abb2d97ad8506eb837c978f99 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Tue, 23 Jul 2019 15:14:53 +0200 Subject: [PATCH 078/107] =?UTF-8?q?Correction=20rule=20'entre=20?= =?UTF-8?q?=20et=20'=20to=20support=20'=C3=A0=20partir=20de'.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- grammar/fr/src/rules_datetime.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index ddb776e9..78626ea8 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -1097,7 +1097,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { } ); b.rule_1_terminal("coucher du soleil", - b.reg(r#"coucher du soleil|cr[eé]puscule|tomb[ée]e de la nuit"#)?, + b.reg(r#"coucher d[eu] soleil|cr[eé]puscule|tomb[ée]e de la nuit"#)?, |_| { Ok(helpers::hour(19, false)? .span_to(&helpers::hour(22, false)?, false)? @@ -1517,14 +1517,14 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { |_, a, _, b| a.value().span_to(b.value(), true) ); b.rule_4("entre et (interval)", - b.reg(r#"d[eu]"#)?, + b.reg(r#"(?:[aà] partir )?d[eu]"#)?, datetime_check!(|datetime: &DatetimeValue| form!(Form::TimeOfDay(_))(datetime)), b.reg(r#"(?:jusqu')?(?:à|au)"#)?, datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), |_, a, _, b| a.value().span_to(b.value(), true) ); b.rule_4("entre et (interval)", - b.reg(r#"d[eu]"#)?, + b.reg(r#"(?:[aà] partir )?d[eu]"#)?, datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), b.reg(r#"(?:jusqu')?(?:à|au)"#)?, datetime_check!(|datetime: &DatetimeValue| form!(Form::TimeOfDay(_))(datetime)), From 74e03d920b2626fa138e73cc6d69272b75fbb67c Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Tue, 23 Jul 2019 16:13:48 +0200 Subject: [PATCH 079/107] Add reverse order for rule . --- grammar/fr/src/rules_datetime.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 78626ea8..6ca9968d 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -1170,6 +1170,11 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), |a, b| a.value().intersect(b.value()) ); + b.rule_2("intersect ", + datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), + datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), + |a, b| a.value().intersect(b.value()) + ); b.rule_2(" du matin", datetime_check!(form!(Form::TimeOfDay(_))), b.reg(r#"(?:(?:du|dans|de) )?(?:(?:au|le|la) )?mat(?:in[ée]?e?)?(?: pile| exactement| pr[eé]cises?)?"#)?, @@ -1603,7 +1608,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { |_, a, _, b| a.value().smart_span_to(b.value(), false) ); b.rule_2("jusqu'à ", - b.reg(r#"(?:n[ ']importe quand )?jusqu'(?:au|[aà]|en)"#)?, + b.reg(r#"(?:n[ ']importe quand )?jusqu'(?:au|[aà]|en)?"#)?, datetime_check!(|datetime: &DatetimeValue| !datetime.latent), |_, datetime| Ok(datetime.value().clone().mark_before_end()) ); From b93b109d81e6376a1e66575c28a36a489caaa558 Mon Sep 17 00:00:00 2001 From: Rosa Stern Date: Sun, 9 Jun 2019 16:02:53 +0200 Subject: [PATCH 080/107] Add moment and values utils to improve grammar. --- values/src/helpers.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/values/src/helpers.rs b/values/src/helpers.rs index 71d07474..9583ab23 100644 --- a/values/src/helpers.rs +++ b/values/src/helpers.rs @@ -529,6 +529,11 @@ pub fn cycle_n_not_immediate(grain: Grain, n: i64) -> RuleResult Ok(DatetimeValue::constraint(Cycle::rc(grain).take_not_immediate(n)).form(Form::Cycle(grain))) } +pub fn weekend() -> RuleResult { + let friday = day_of_week(Weekday::Fri)?.intersect(&hour(18, false)?)?; + let monday = day_of_week(Weekday::Mon)?.intersect(&hour(0, false)?)?; + Ok(friday.span_to(&monday, false)?.datetime_kind(DatetimeKind::DatePeriod)) +} pub fn easter() -> RuleResult { From 88ab7c0944935ab8e200bd718bb6a64749ee28f5 Mon Sep 17 00:00:00 2001 From: Rosa Stern Date: Sun, 9 Jun 2019 16:03:41 +0200 Subject: [PATCH 081/107] Improve French grammar for new Datetime specs (1/2). --- grammar/fr/src/rules_datetime.rs | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 6ca9968d..1fe38f2d 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -3,26 +3,11 @@ use rustling_ontology_values::dimension::*; use rustling_ontology_values::helpers; use rustling_ontology_moment::{Weekday, Grain}; - -<<<<<<< HEAD /* DATETIME - CYCLE DEFINITIONS */ pub fn rules_cycle(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.rule_1_terminal("seconde (cycle)", b.reg(r#"secondes?"#)?, |_| CycleValue::new(Grain::Second) -======= -pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { - b.rule_2("intersect", - datetime_check!(|datetime: &DatetimeValue| !datetime.latent), - datetime_check!(|datetime: &DatetimeValue| !datetime.latent), - |a, b| a.value().intersect(b.value()) - ); - b.rule_3("intersect by 'de' or ','", - datetime_check!(|datetime: &DatetimeValue| !datetime.latent), - b.reg(r#"de|,"#)?, - datetime_check!(|datetime: &DatetimeValue| !datetime.latent), - |a, _, b| a.value().intersect(b.value()) ->>>>>>> Add preposition 'au' in rule 'goûter'. ); b.rule_1_terminal("minute (cycle)", b.reg(r#"minutes?"#)?, From f97b569c4afe48fb6a0270a5f5154efc27da8807 Mon Sep 17 00:00:00 2001 From: Rosa Stern Date: Sun, 9 Jun 2019 16:43:04 +0200 Subject: [PATCH 082/107] Split French grammar per entity type. --- grammar/fr/src/rules_celebrations.rs | 1 + grammar/fr/src/rules_duration.rs | 1 + grammar/fr/src/rules_number.rs | 230 --------------------------- 3 files changed, 2 insertions(+), 230 deletions(-) diff --git a/grammar/fr/src/rules_celebrations.rs b/grammar/fr/src/rules_celebrations.rs index 357a7361..329893f7 100644 --- a/grammar/fr/src/rules_celebrations.rs +++ b/grammar/fr/src/rules_celebrations.rs @@ -16,6 +16,7 @@ pub fn rules_celebration(b: &mut RuleSetBuilder) -> RustlingResult<() Ok(start.span_to(&end, false)? .form(Form::Celebration)) } + ); b.rule_1_terminal("saint sylvestre", b.reg(r#"(?:l[ea] )?(?:saint[- ]sylvestre|r[eé]veillon)"#)?, diff --git a/grammar/fr/src/rules_duration.rs b/grammar/fr/src/rules_duration.rs index b65a8cd3..64d18c7d 100644 --- a/grammar/fr/src/rules_duration.rs +++ b/grammar/fr/src/rules_duration.rs @@ -139,3 +139,4 @@ pub fn rules_duration(b: &mut RuleSetBuilder) -> RustlingResult<()> { ); Ok(()) } + diff --git a/grammar/fr/src/rules_number.rs b/grammar/fr/src/rules_number.rs index 8bf5e089..ab853698 100644 --- a/grammar/fr/src/rules_number.rs +++ b/grammar/fr/src/rules_number.rs @@ -1,5 +1,4 @@ use std::f32; - use rustling::*; use rustling_ontology_values::dimension::*; use rustling_ontology_values::helpers; @@ -575,234 +574,6 @@ pub fn rules_numbers(b: &mut RuleSetBuilder) -> RustlingResult<()> { let value: i64 = text_match.group(1).parse()?; Ok(OrdinalValue::new(value)) }); - - b.rule_1_terminal("ordinal 0", - b.reg(r#"z[eé]rot?i[eè]me"#)?, - |_| { - Ok(OrdinalValue::new(0)) - } - ); - b.rule_1_terminal("ordinal 1", - b.reg(r#"premi[eè]re?"#)?, - |_| { - Ok(OrdinalValue::new(1)) - } - ); - b.rule_1_terminal("ordinal 2", - b.reg(r#"seconde?|deuxi[eè]me"#)?, - |_| { - Ok(OrdinalValue::new(2)) - } - ); - b.rule_1_terminal( - "ordinals (premier..seizieme)", - b.reg(r#"(trois|quatr|cinqu|six|sept|huit|neuv|dix|onz|douz|treiz|quatorz|quinz|seiz)i[eè]me"#)?, - |text_match| { - let value = match text_match.group(1).as_ref() { - "trois" => 3, - "quatr" => 4, - "cinqu" => 5, - "six" => 6, - "sept" => 7, - "huit" => 8, - "neuv" => 9, - "dix" => 10, - "onz" => 11, - "douz" => 12, - "treiz" => 13, - "quatorz" => 14, - "quinz" => 15, - "seiz" => 16, - _ => return Err(RuleError::Invalid.into()), - }; - Ok(OrdinalValue::new(value)) - }); - b.rule_2("17ieme, 18ieme, 19ieme", - b.reg(r#"dix-?"#)?, - ordinal_check_by_range!(7, 9), - |_, ordinal| { - Ok(OrdinalValue::new(10 + ordinal.value().value)) - } - ); - b.rule_1_terminal("20ieme, 30ieme, 40ieme, 50ieme, 60ieme", - b.reg(r#"(vingt|trent|quarant|cinquant|soixant)i[èe]me"#)?, - |text_match| { - let value = match text_match.group(1).as_ref() { - "vingt" => 20, - "trent" => 30, - "quarant" => 40, - "cinquant" => 50, - "soixant" => 60, - _ => return Err(RuleError::Invalid.into()), - }; - Ok(OrdinalValue::new(value)) - } - ); - b.rule_1_terminal("80ieme", - b.reg(r#"quatre[- ]vingts?i[èe]me"#)?, - |_| { - Ok(OrdinalValue::new(80)) - } - ); - b.rule_2("22ieme...29ieme, 32ieme...39ieme, 42ieme...49ieme, 52ieme...59ieme", - integer_check_by_range!(20, 50, |integer: &IntegerValue| integer.value % 10 == 0), - ordinal_check_by_range!(2, 9), - |integer, ordinal| { - Ok(OrdinalValue::new(integer.value().value + ordinal.value().value)) - } - ); - b.rule_3("22ieme...29ieme, 32ieme...39ieme, 42ieme...49ieme, 52ieme...59ieme", - integer_check_by_range!(20, 50, |integer: &IntegerValue| integer.value % 10 == 0), - b.reg(r"-")?, - ordinal_check_by_range!(2, 9), - |integer, _, ordinal| { - Ok(OrdinalValue::new(integer.value().value + ordinal.value().value)) - } - ); - b.rule_2("62ieme...70ieme, 72ieme...79ieme, 90ieme, 92ieme...99ieme", - integer_check_by_range!(60, 80, |integer: &IntegerValue| integer.value == 60 || integer.value == 80), - ordinal_check_by_range!(2, 19), - |integer, ordinal| { - Ok(OrdinalValue::new(integer.value().value + ordinal.value().value)) - } - ); - b.rule_3("62ieme...70ieme, 72ieme...79ieme, 90ieme, 92ieme...99ieme", - integer_check_by_range!(60, 80, |integer: &IntegerValue| integer.value == 60 || integer.value == 80), - b.reg(r"-")?, - ordinal_check_by_range!(2, 19), - |integer, _, ordinal| { - Ok(OrdinalValue::new(integer.value().value + ordinal.value().value)) - } - ); - b.rule_2("21, 31, 41, 51, 61", - integer_check_by_range!(20, 60, |integer: &IntegerValue| integer.value % 10 == 0), - b.reg(r#"(?:et |-)uni[èe]me"#)?, - |integer, _| { - Ok(OrdinalValue::new(integer.value().value + 1)) - } - ); - b.rule_2("81", - integer_check_by_range!(80, 80), - b.reg(r#"(?:et )?uni[èe]me"#)?, - |integer, _| { - Ok(OrdinalValue::new(integer.value().value + 1)) - } - ); - b.rule_2("71, 91", - integer_check_by_range!(60, 60), - b.reg(r#"et onzi[eè]me"#)?, - |integer, _| { - Ok(OrdinalValue::new(integer.value().value + 11)) - } - ); - b.rule_2(" et demi", - integer_check_by_range!(0, 99), - b.reg(r#"et demie?"#)?, - |integer, _| { - FloatValue::new(integer.value().value as f32 + 0.5) - } - ); - b.rule_1_terminal("70, 80, 90 (Belgium and Switzerland)", - b.reg(r#"(sept|huit|non)ante"#)?, - |text_match| { - let value = match text_match.group(1).as_ref() { - "sept" => 70, - "huit" => 80, - "non" => 90, - _ => return Err(RuleError::Invalid.into()), - }; - IntegerValue::new(value) - } - ); - b.rule_1_terminal("71, 81, 91 (Belgium and Switzerland)", - b.reg(r#"(sept|huit|non)ante et une?"#)?, - |text_match| { - let value = match text_match.group(1).as_ref() { - "sept" => 71, - "huit" => 81, - "non" => 91, - _ => return Err(RuleError::Invalid.into()), - }; - IntegerValue::new(value) - } - ); - - b.rule_2("72..79, 82..89, 92..99, (Belgium and Switzerland)", - b.reg(r#"(sept|huit|non)ante"#)?, - integer_check_by_range!(2, 9), - |text_match, integer| { - let value = match text_match.group(1).as_ref() { - "sept" => 70, - "huit" => 80, - "non" => 90, - _ => return Err(RuleError::Invalid.into()), - }; - IntegerValue::new(value + integer.value().value) - } - ); - b.rule_1_terminal("ordinal (100, 1_000, 1_000_000)", - b.reg(r#"(cent|mill|million|milliard)i[èe]me"#)?, - |text_match| { - let (value, grain) = match text_match.group(1).as_ref() { - "cent" => (100, 2), - "mill" => (1_000, 3), - "million" => (1_000_000, 6), - "milliard" => (1_000_000_000, 9), - _ => return Err(RuleError::Invalid.into()), - }; - Ok(OrdinalValue::new_with_grain(value, grain)) - } - ); - - b.rule_2("ordinal (200..900, 2_000..9_000, 2_000_000..9_000_000_000)", - integer_check_by_range!(2, 999), - b.reg(r#"(cent|mill|million|milliard)i[èe]me"#)?, - |integer, text_match| { - let (value, grain) = match text_match.group(1).as_ref() { - "cent" => (100, 2), - "mill" => (1_000, 3), - "million" => (1_000_000, 6), - "milliard" => (1_000_000_000, 9), - _ => return Err(RuleError::Invalid.into()), - }; - Ok(OrdinalValue::new_with_grain(integer.value().value * value, grain)) - } - ); - - b.rule_2("ordinal (1_1_000..9_999_999_000)", - integer_check_by_range!(1000, 99_999_999_000), - ordinal_check!(|ordinal: &OrdinalValue| { - let grain = ordinal.grain.unwrap_or(0); - grain == 2 || grain % 3 == 0 - }), - |integer, ordinal| { - let grain = ordinal.value().grain.unwrap_or(0); - let next_grain = (grain / 3) * 3 + 3; - if integer.value().value % 10i64.pow(next_grain as u32) != 0 { return Err(RuleError::Invalid.into()); } - Ok(OrdinalValue::new(integer.value().value + ordinal.value().value)) - } - ); - - b.rule_2("ordinal (102...9_999_999)", - integer_check!(|integer: &IntegerValue| integer.value >= 100 || integer.value % 100 == 0), - ordinal_check_by_range!(2, 99), - |integer, ordinal| { - Ok(OrdinalValue::new(integer.value().value + ordinal.value().value)) - } - ); - b.rule_2("ordinal (101, 201, 301, ...)", - integer_check!(|integer: &IntegerValue| integer.value >= 100 || integer.value % 100 == 0), - b.reg(r#"(?:et |-)?uni[èe]me"#)?, - |integer, _| { - Ok(OrdinalValue::new(integer.value().value + 1)) - } - ); - b.rule_1_terminal("ordinal (digits)", - b.reg(r#"0*(\d+) ?(ere?|ère|ème|eme|ieme|ième)"#)?, - |text_match| { - let value: i64 = text_match.group(1).parse()?; - Ok(OrdinalValue::new(value)) - }); b.rule_2("le ", b.reg(r#"l[ea]"#)?, ordinal_check!(), @@ -810,4 +581,3 @@ pub fn rules_numbers(b: &mut RuleSetBuilder) -> RustlingResult<()> { ); Ok(()) } - From d73f0e677c2770f8b80e5b396a21649f734cb285 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Thu, 20 Jun 2019 18:28:04 +0200 Subject: [PATCH 083/107] =?UTF-8?q?Add=20support=20for=20mid/end/beg=20sea?= =?UTF-8?q?son,=20mi-journ=C3=A9e,=20prep=20+=20part-of-day.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- grammar/fr/src/rules_datetime.rs | 50 +++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 1fe38f2d..09e29587 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -1313,6 +1313,54 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { start.span_to(&end, true) } ); + b.rule_1_terminal("début de l'été", + b.reg(r#"début de (?:cet |l')?(?:été|ete)"#)?, + |_| helpers::month_day(6, 21)?.span_to(&helpers::month_day(7, 15)?, false) + ); + b.rule_1_terminal("milieu de l'été", + b.reg(r#"milieu de (?:cet |l')?(?:été|ete)"#)?, + |_| helpers::month_day(7, 15)?.span_to(&helpers::month_day(8, 15)?, false) + ); + b.rule_1_terminal("fin de l'été", + b.reg(r#"fin de (?:cet |l')?(?:été|ete)"#)?, + |_| helpers::month_day(8, 15)?.span_to(&helpers::month_day(9, 21)?, false) + ); + b.rule_1_terminal("début de l'automne", + b.reg(r#"début de (?:cet |l')?(?:été|ete)"#)?, + |_| helpers::month_day(9, 21)?.span_to(&helpers::month_day(10, 15)?, false) + ); + b.rule_1_terminal("milieu de l'automne", + b.reg(r#"milieu de (?:cet |l')?(?:été|ete)"#)?, + |_| helpers::month_day(10, 15)?.span_to(&helpers::month_day(11, 15)?, false) + ); + b.rule_1_terminal("fin de l'automne", + b.reg(r#"fin de (?:cet |l')?(?:été|ete)"#)?, + |_| helpers::month_day(11, 15)?.span_to(&helpers::month_day(12, 21)?, false) + ); + b.rule_1_terminal("début de l'hiver", + b.reg(r#"début de (?:cet |l')?(?:été|ete)"#)?, + |_| helpers::month_day(12, 21)?.span_to(&helpers::month_day(1, 15)?, false) + ); + b.rule_1_terminal("milieu de l'hiver", + b.reg(r#"milieu de (?:cet |l')?(?:été|ete)"#)?, + |_| helpers::month_day(1, 15)?.span_to(&helpers::month_day(2, 15)?, false) + ); + b.rule_1_terminal("fin de l'hiver", + b.reg(r#"fin de (?:cet |l')?(?:été|ete)"#)?, + |_| helpers::month_day(2, 15)?.span_to(&helpers::month_day(3, 21)?, false) + ); + b.rule_1_terminal("début du printemps", + b.reg(r#"début de (?:cet |l')?(?:été|ete)"#)?, + |_| helpers::month_day(3, 21)?.span_to(&helpers::month_day(4, 15)?, false) + ); + b.rule_1_terminal("milieu du printemps", + b.reg(r#"milieu de (?:cet |l')?(?:été|ete)"#)?, + |_| helpers::month_day(4, 15)?.span_to(&helpers::month_day(5, 15)?, false) + ); + b.rule_1_terminal("fin du printemps", + b.reg(r#"fin de (?:cet |l')?(?:été|ete)"#)?, + |_| helpers::month_day(5, 15)?.span_to(&helpers::month_day(6, 21)?, false) + ); b.rule_2("le ", //b.reg(r#"l[ea]"#)?, b.reg(r#"l[ea]|en|au|à|pour"#)?, @@ -1618,7 +1666,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { |_, datetime| Ok(datetime.value().clone().mark_after_start()) ); b.rule_2("à partir de ", - b.reg(r#"[aà] partir d['eu](?:l[ 'a])?"#)?, + b.reg(r#"[aà] partir d['eu](?:l[ 'a])?|après"#)?, datetime_check!(form!(Form::PartOfDay(_))), |_, datetime| Ok(datetime.value().clone().mark_after_start()) ); From fb49a7a198daa463df4054d6eb0d807d9bfcd071 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Thu, 18 Jul 2019 11:59:22 +0200 Subject: [PATCH 084/107] Add support for POD 'sunrise', 'middle of the night', and changed hour range for mid-day. --- grammar/fr/src/rules_datetime.rs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 09e29587..be563a87 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -914,6 +914,13 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { .latent() .form(Form::PartOfDay(PartOfDayForm::Morning))) ); + b.rule_1_terminal("lever du soleil", + b.reg(r#"lever du soleil|aurore|aube"#)?, + |_| Ok(helpers::hour(5, false)? + .span_to(&helpers::hour(9, false)?, false)? + .latent() + .form(Form::PartOfDay(PartOfDayForm::Morning))) + ); b.rule_1_terminal("petit dejeuner", b.reg(r#"petit[- ]d[ée]jeuner"#)?, |_| Ok(helpers::hour(5, false)? @@ -922,12 +929,11 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { .form(Form::Meal)) ); b.rule_1_terminal("milieu de matinée", - b.reg(r#"(?:le )?milieu de matin[ée]e"#)?, + b.reg(r#"(?:le )?milieu de (?:la )?matin[ée]e"#)?, |_| Ok(helpers::hour(9, false)? .span_to(&helpers::hour(11, false)?, false)? .latent() .form(Form::PartOfDay(PartOfDayForm::Morning))) - ); b.rule_1_terminal("brunch", b.reg(r#"brunch"#)?, @@ -1132,6 +1138,15 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { .form(Form::PartOfDay(PartOfDayForm::Night))) } ); + b.rule_1_terminal("milieu de la nuit", + b.reg(r#"(?:le )?milieu de la nuit"#)?, + |_| { + Ok(helpers::hour(2, false)? + .span_to(&helpers::hour(4, false)?, false)? + .latent() + .form(Form::PartOfDay(PartOfDayForm::Night))) + } + ); b.rule_2("a l'heure de ", b.reg(r#"(?:[àa] )?l[' ]heure du|au moment du|pendant l[ea']|au|pour l[ea']|l[ea']"#)?, datetime_check!(|datetime: &DatetimeValue| form!(Form::Meal)(datetime)), From c45d027a3e0698c5c83ecf0c179b9e6f18fa9eba Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Thu, 18 Jul 2019 14:31:02 +0200 Subject: [PATCH 085/107] Add support for POD 'sunset'. --- grammar/fr/src/rules_datetime.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index be563a87..7a53860a 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -1096,6 +1096,15 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { .form(Form::PartOfDay(PartOfDayForm::Evening))) } ); + b.rule_1_terminal("coucher du soleil", + b.reg(r#"coucher du soleil|cr[eé]puscule|tomb[ée]e de la nuit"#)?, + |_| { + Ok(helpers::hour(18, false)? + .span_to(&helpers::hour(21, false)?, false)? + .latent() + .form(Form::PartOfDay(PartOfDayForm::Evening))) + } + ); b.rule_1_terminal("début de soirée", b.reg(r#"d[ée]but de (?:la )?soir[ée]e?"#)?, |_| { @@ -1139,7 +1148,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { } ); b.rule_1_terminal("milieu de la nuit", - b.reg(r#"(?:le )?milieu de la nuit"#)?, + b.reg(r#"milieu de la nuit"#)?, |_| { Ok(helpers::hour(2, false)? .span_to(&helpers::hour(4, false)?, false)? From 150fa1fe505817de174052af3f626ee11c53fe44 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Thu, 18 Jul 2019 15:17:04 +0200 Subject: [PATCH 086/107] Corrected rule 'lever du soleil', Changed hour range 'aube'. --- grammar/fr/src/rules_datetime.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 7a53860a..2cddbdd8 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -915,9 +915,9 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { .form(Form::PartOfDay(PartOfDayForm::Morning))) ); b.rule_1_terminal("lever du soleil", - b.reg(r#"lever du soleil|aurore|aube"#)?, - |_| Ok(helpers::hour(5, false)? - .span_to(&helpers::hour(9, false)?, false)? + b.reg(r#"lever d[ue] soleil|aurore|aube"#)?, + |_| Ok(helpers::hour(4, false)? + .span_to(&helpers::hour(8, false)?, false)? .latent() .form(Form::PartOfDay(PartOfDayForm::Morning))) ); @@ -1099,8 +1099,8 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.rule_1_terminal("coucher du soleil", b.reg(r#"coucher du soleil|cr[eé]puscule|tomb[ée]e de la nuit"#)?, |_| { - Ok(helpers::hour(18, false)? - .span_to(&helpers::hour(21, false)?, false)? + Ok(helpers::hour(19, false)? + .span_to(&helpers::hour(22, false)?, false)? .latent() .form(Form::PartOfDay(PartOfDayForm::Evening))) } @@ -1690,7 +1690,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { |_, datetime| Ok(datetime.value().clone().mark_after_start()) ); b.rule_2("à partir de ", - b.reg(r#"[aà] partir d['eu](?:l[ 'a])?|après"#)?, + b.reg(r#"[aà] partir d['eu](?:l[ 'a])?"#)?, datetime_check!(form!(Form::PartOfDay(_))), |_, datetime| Ok(datetime.value().clone().mark_after_start()) ); From 627a91da605e7df30e19a77d1029d88a100a8516 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Thu, 18 Jul 2019 17:03:26 +0200 Subject: [PATCH 087/107] Add support for 'last '. --- grammar/fr/src/rules_datetime.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 2cddbdd8..61f44ed5 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -520,6 +520,16 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { .datetime_kind(a.value().datetime_kind.clone())) } ); + b.rule_2("au prochain ", + b.reg(r#"(au |(?:[aà] )?la )prochaine?"#)?, + datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), + |_, datetime| datetime.value().the_nth_not_immediate(0) + ); + b.rule_2("au dernier ", + b.reg(r#"(au |(?:[aà] )?la )dernier?"#)?, + datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), + |_, datetime| datetime.value().the_nth_not_immediate(-1) + ); b.rule_2(" prochain", // The direction check is to avoid application of datetime_check(month) on rule result // "avant " From ab9b62d639864536ae669a6cdf10dbe6d9b03cf8 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Thu, 18 Jul 2019 17:16:37 +0200 Subject: [PATCH 088/107] Correction rule 'au (prochain|dernier) '. --- grammar/fr/src/rules_datetime.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 61f44ed5..8d3cd684 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -521,12 +521,12 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { } ); b.rule_2("au prochain ", - b.reg(r#"(au |(?:[aà] )?la )prochaine?"#)?, + b.reg(r#"(au |(?:[aà] )?l[ae] )prochaine?"#)?, datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), |_, datetime| datetime.value().the_nth_not_immediate(0) ); b.rule_2("au dernier ", - b.reg(r#"(au |(?:[aà] )?la )dernier?"#)?, + b.reg(r#"(au |(?:[aà] )?l[ea] )derni[eè]re?"#)?, datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), |_, datetime| datetime.value().the_nth_not_immediate(-1) ); From 894b9bd065aeb7a9536d1fba5d0a5db6eb47e32f Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Mon, 22 Jul 2019 11:11:41 +0200 Subject: [PATCH 089/107] Correction rules 'au dernier ', 'au prochain >datetime>'. --- grammar/fr/src/rules_datetime.rs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 8d3cd684..551d6286 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -522,13 +522,22 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { ); b.rule_2("au prochain ", b.reg(r#"(au |(?:[aà] )?l[ae] )prochaine?"#)?, - datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), - |_, datetime| datetime.value().the_nth_not_immediate(0) + datetime_check!(|datetime: &DatetimeValue| !form!(Form::PartOfDay(_))(datetime) && !form!(Form::Meal)(datetime)), + |_, a| { + Ok(a.value().the_nth(0)? + .form(a.value().form.clone()) + .datetime_kind(a.value().datetime_kind.clone())) + } ); - b.rule_2("au dernier ", + // TODO: add restrictions on datetime form? + b.rule_2("au dernier ", b.reg(r#"(au |(?:[aà] )?l[ea] )derni[eè]re?"#)?, - datetime_check!(|datetime: &DatetimeValue| datetime.form.is_day()), - |_, datetime| datetime.value().the_nth_not_immediate(-1) + datetime_check!(|datetime: &DatetimeValue| !form!(Form::PartOfDay(_))(datetime) && !form!(Form::Meal)(datetime)), + |_, a| { + Ok(a.value().the_nth(-1)? + .form(a.value().form.clone()) + .datetime_kind(a.value().datetime_kind.clone())) + } ); b.rule_2(" prochain", // The direction check is to avoid application of datetime_check(month) on rule result From f19370c508897a0553e0839cfb736f7b11d74e68 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Mon, 22 Jul 2019 16:02:09 +0200 Subject: [PATCH 090/107] =?UTF-8?q?Add=20support=20for=20'fin=20de=20cette?= =?UTF-8?q?=20ann=C3=A9e'.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- grammar/fr/src/rules_datetime.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 551d6286..7d6ef027 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -1404,6 +1404,15 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.reg(r#"fin de (?:cet |l')?(?:été|ete)"#)?, |_| helpers::month_day(5, 15)?.span_to(&helpers::month_day(6, 21)?, false) ); + b.rule_1_terminal("fin de cette année", + b.reg(r#"fin (?:de (?:l'|cette )|d')?ann[ée]e"#)?, + |_| { + let current_year = helpers::cycle_nth(Grain::Year, 0)?; + let start = current_year.intersect(&helpers::month(10)?)?; + let end = current_year.intersect(&helpers::month(12)?)?; + start.span_to(&end, true) + } + ); b.rule_2("le ", //b.reg(r#"l[ea]"#)?, b.reg(r#"l[ea]|en|au|à|pour"#)?, From b0404376a8a963b65022bf95f1df38d2cf66ca42 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Mon, 22 Jul 2019 16:22:52 +0200 Subject: [PATCH 091/107] Corrections rules 'end/mid/beg of season'. --- grammar/fr/src/rules_datetime.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 7d6ef027..f67cfb1e 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -1369,39 +1369,39 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { |_| helpers::month_day(8, 15)?.span_to(&helpers::month_day(9, 21)?, false) ); b.rule_1_terminal("début de l'automne", - b.reg(r#"début de (?:cet |l')?(?:été|ete)"#)?, + b.reg(r#"début de (?:cet |l')?automne"#)?, |_| helpers::month_day(9, 21)?.span_to(&helpers::month_day(10, 15)?, false) ); b.rule_1_terminal("milieu de l'automne", - b.reg(r#"milieu de (?:cet |l')?(?:été|ete)"#)?, + b.reg(r#"milieu de (?:cet |l')?automne"#)?, |_| helpers::month_day(10, 15)?.span_to(&helpers::month_day(11, 15)?, false) ); b.rule_1_terminal("fin de l'automne", - b.reg(r#"fin de (?:cet |l')?(?:été|ete)"#)?, + b.reg(r#"fin de (?:cet |l')?automne"#)?, |_| helpers::month_day(11, 15)?.span_to(&helpers::month_day(12, 21)?, false) ); b.rule_1_terminal("début de l'hiver", - b.reg(r#"début de (?:cet |l')?(?:été|ete)"#)?, + b.reg(r#"début de (?:cet |l')?hiver"#)?, |_| helpers::month_day(12, 21)?.span_to(&helpers::month_day(1, 15)?, false) ); b.rule_1_terminal("milieu de l'hiver", - b.reg(r#"milieu de (?:cet |l')?(?:été|ete)"#)?, + b.reg(r#"milieu de (?:cet |l')?hiver"#)?, |_| helpers::month_day(1, 15)?.span_to(&helpers::month_day(2, 15)?, false) ); b.rule_1_terminal("fin de l'hiver", - b.reg(r#"fin de (?:cet |l')?(?:été|ete)"#)?, + b.reg(r#"fin de (?:cet |l')?hiver"#)?, |_| helpers::month_day(2, 15)?.span_to(&helpers::month_day(3, 21)?, false) ); b.rule_1_terminal("début du printemps", - b.reg(r#"début de (?:cet |l')?(?:été|ete)"#)?, + b.reg(r#"début (?:du|de ce)? printemps"#)?, |_| helpers::month_day(3, 21)?.span_to(&helpers::month_day(4, 15)?, false) ); b.rule_1_terminal("milieu du printemps", - b.reg(r#"milieu de (?:cet |l')?(?:été|ete)"#)?, + b.reg(r#"milieu (?:du|de ce)? printemps"#)?, |_| helpers::month_day(4, 15)?.span_to(&helpers::month_day(5, 15)?, false) ); b.rule_1_terminal("fin du printemps", - b.reg(r#"fin de (?:cet |l')?(?:été|ete)"#)?, + b.reg(r#"fin (?:du|de ce)? printemps"#)?, |_| helpers::month_day(5, 15)?.span_to(&helpers::month_day(6, 21)?, false) ); b.rule_1_terminal("fin de cette année", From 4ff66f640e70a493a9055f85eeb55614ec2ba820 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Mon, 22 Jul 2019 17:38:53 +0200 Subject: [PATCH 092/107] =?UTF-8?q?Add=20support=20for=20'd=C3=A9but=20de?= =?UTF-8?q?=20l'ann=C3=A9e'.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- grammar/fr/src/rules_datetime.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index f67cfb1e..2fdae54f 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -1413,6 +1413,15 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { start.span_to(&end, true) } ); + b.rule_1_terminal("début de cette année", + b.reg(r#"d[ée]but (?:de (?:l'|cette )|d')?ann[ée]e"#)?, + |_| { + let current_year = helpers::cycle_nth(Grain::Year, 0)?; + let start = current_year.intersect(&helpers::month(1)?)?; + let end = current_year.intersect(&helpers::month(2)?)?; + start.span_to(&end, true) + } + ); b.rule_2("le ", //b.reg(r#"l[ea]"#)?, b.reg(r#"l[ea]|en|au|à|pour"#)?, From e54fe4ece5b3f18640e3f07676d642c876565836 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Tue, 23 Jul 2019 15:14:53 +0200 Subject: [PATCH 093/107] =?UTF-8?q?Correction=20rule=20'entre=20?= =?UTF-8?q?=20et=20'=20to=20support=20'=C3=A0=20partir=20de'.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- grammar/fr/src/rules_datetime.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 2fdae54f..43b604f5 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -1116,7 +1116,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { } ); b.rule_1_terminal("coucher du soleil", - b.reg(r#"coucher du soleil|cr[eé]puscule|tomb[ée]e de la nuit"#)?, + b.reg(r#"coucher d[eu] soleil|cr[eé]puscule|tomb[ée]e de la nuit"#)?, |_| { Ok(helpers::hour(19, false)? .span_to(&helpers::hour(22, false)?, false)? From c8abdcbc495c36090e913bbe1255daf41b1421cd Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Wed, 24 Jul 2019 10:43:56 +0200 Subject: [PATCH 094/107] Include + prefix in rules_numbers.rs. --- grammar/fr/src/rules_number.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/grammar/fr/src/rules_number.rs b/grammar/fr/src/rules_number.rs index ab853698..fb889dc1 100644 --- a/grammar/fr/src/rules_number.rs +++ b/grammar/fr/src/rules_number.rs @@ -273,6 +273,34 @@ pub fn rules_numbers(b: &mut RuleSetBuilder) -> RustlingResult<()> { }) } ); + b.rule_2("numbers suffixes (K, M, G)", + number_check!(|number: &NumberValue| !number.suffixed()), + b.reg_neg_lh(r#"([kmg])"#, r#"^[\W\$€]"#)?, + |a, text_match| -> RuleResult { + let multiplier = match text_match.group(0).as_ref() { + "k" => 1000, + "m" => 1000000, + "g" => 1000000000, + _ => return Err(RuleError::Invalid.into()), + }; + Ok(match a.value().clone() { // checked + NumberValue::Integer(integer) => { + IntegerValue { + prefixed: true, + ..integer + } + .into() + } + NumberValue::Float(float) => { + FloatValue { + prefixed: true, + ..float + } + .into() + } + }) + } + ); b.rule_2("numbers suffixes (K, M, G)", number_check!(|number: &NumberValue| !number.suffixed()), b.reg_neg_lh(r#"([kmg])"#, r#"^[\W\$€]"#)?, From 395e26d03d5b455559aa5cfbb6c33e2737ac5438 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Wed, 24 Jul 2019 16:49:13 +0200 Subject: [PATCH 095/107] Corrected rules interval - . --- grammar/fr/src/rules_datetime.rs | 38 ++++++++++++++++---------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 43b604f5..3ccb843a 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -1608,34 +1608,34 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), |_, a, _, b| a.value().span_to(b.value(), true) ); - b.rule_4("entre et (interval)", + b.rule_4("entre et (interval)", b.reg(r#"entre"#)?, - datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), + datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime)), b.reg(r#"et"#)?, datetime_check!(|datetime: &DatetimeValue| form!(Form::TimeOfDay(_))(datetime)), |_, a, _, b| a.value().span_to(b.value(), true) ); - b.rule_4("entre et (interval)", + b.rule_4("entre et (interval)", + b.reg(r#"entre"#)?, + datetime_check!(|datetime: &DatetimeValue| form!(Form::TimeOfDay(_))(datetime)), + b.reg(r#"et"#)?, + datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime)), + |_, a, _, b| a.value().span_to(b.value(), true) + ); + b.rule_4("de à (interval)", b.reg(r#"(?:[aà] partir )?d[eu]"#)?, datetime_check!(|datetime: &DatetimeValue| form!(Form::TimeOfDay(_))(datetime)), b.reg(r#"(?:jusqu')?(?:à|au)"#)?, datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), |_, a, _, b| a.value().span_to(b.value(), true) ); - b.rule_4("entre et (interval)", + b.rule_4("de à (interval)", b.reg(r#"(?:[aà] partir )?d[eu]"#)?, datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), b.reg(r#"(?:jusqu')?(?:à|au)"#)?, datetime_check!(|datetime: &DatetimeValue| form!(Form::TimeOfDay(_))(datetime)), |_, a, _, b| a.value().span_to(b.value(), true) ); - b.rule_4("entre et (interval)", - b.reg(r#"entre"#)?, - datetime_check!(|datetime: &DatetimeValue| form!(Form::TimeOfDay(_))(datetime)), - b.reg(r#"et"#)?, - datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), - |_, a, _, b| a.value().span_to(b.value(), true) - ); // Specific case with years b.rule_5("de - (interval)", b.reg(r#"depuis|d[e'u]?"#)?, @@ -1666,18 +1666,18 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { datetime_check!(form!(Form::TimeOfDay(_))), |_, a, _, b| a.value().smart_span_to(b.value(), false) ); - b.rule_4("de - (interval)", + b.rule_4("de à (interval)", b.reg(r#"(?:[aà] partir )?d['eu]"#)?, - datetime_check!(form!(Form::PartOfDay(_))), + datetime_check!(|datetime: &DatetimeValue| !form!(Form::PartOfDay(_))(datetime)), b.reg(r#"(?:jusqu')?(?:au|[aà])"#)?, - datetime_check!(form!(Form::PartOfDay(_))), + datetime_check!(|datetime: &DatetimeValue| !form!(Form::PartOfDay(_))(datetime)), |_, a, _, b| a.value().smart_span_to(b.value(), false) ); - b.rule_4("de - (interval)", - b.reg(r#"(?:[aà] partir )?d['e]"#)?, - datetime_check!(form!(Form::Meal)), - b.reg(r#"(?:jusqu')?(?:au|[aà])"#)?, - datetime_check!(form!(Form::Meal)), + b.rule_4("entre et (interval)", + b.reg(r#"entre"#)?, + datetime_check!(|datetime: &DatetimeValue| !form!(Form::PartOfDay(_))(datetime)), + b.reg(r#"et"#)?, + datetime_check!(|datetime: &DatetimeValue| !form!(Form::PartOfDay(_))(datetime)), |_, a, _, b| a.value().smart_span_to(b.value(), false) ); b.rule_2("de maintenant - (interval)", From 080319e74ef4d8d56235e594668971cc6873f9a1 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Thu, 25 Jul 2019 11:23:38 +0200 Subject: [PATCH 096/107] Small correction rule 'gouter' added article 'le'. --- grammar/fr/src/rules_datetime.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 3ccb843a..b678b58f 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -1043,7 +1043,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { } ); b.rule_1_terminal("gouter", - b.reg(r#"(?:(?:[àa] )?l[' ]heure du|au(?: moment du)?|pendant le|pour le) go[uû]ter"#)?, + b.reg(r#"(?:(?:[àa] )?l[' ]heure du|au(?: moment du)?|pendant le|(?:pour )?le) go[uû]ter"#)?, |_| Ok(helpers::hour(16, false)? .span_to(&helpers::hour(18, false)?, false)? .form(Form::Meal)) From b8835465a1e2b41998dbc7672acb2baac305640a Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Thu, 25 Jul 2019 15:39:16 +0200 Subject: [PATCH 097/107] Add trimestre to duration, add new training ex to account for newly supported cases. --- grammar/fr/src/rules_datetime.rs | 219 ++++++++++++++++++++++++++++++- grammar/fr/src/rules_duration.rs | 4 + grammar/fr/src/training.rs | 7 +- 3 files changed, 224 insertions(+), 6 deletions(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index b678b58f..63be6c61 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -29,10 +29,6 @@ pub fn rules_cycle(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.reg(r#"mois"#)?, |_| CycleValue::new(Grain::Month) ); - b.rule_1_terminal("trimestre (cycle)", - b.reg(r#"trimestre"#)?, - |_| CycleValue::new(Grain::Quarter) - ); b.rule_1("année (cycle)", b.reg(r#"an(?:n[ée]e?)?s?"#)?, |_| CycleValue::new(Grain::Year) @@ -1831,5 +1827,220 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { datetime_check!(), |duration, _, datetime| duration.value().before(datetime.value()) ); + b.rule_2("dans ", + b.reg(r#"dans"#)?, + duration_check!(), + |_, duration| duration.value().in_present() + ); + b.rule_2(" plus tard", + duration_check!(), + b.reg(r"plus tard")?, + |duration, _| duration.value().in_present() + ); + Ok(()) +} + + +pub fn rules_datetime_with_cycle(b: &mut RuleSetBuilder) -> RustlingResult<()> { + // Cycle patterns relative to now + b.rule_2("ce|dans le ", + b.reg(r#"(?:(?:dans )?l[ea' ]|cet?(?:te)?)"#)?, + cycle_check!(), + |_, cycle| helpers::cycle_nth(cycle.value().grain, 0) + ); + b.rule_3("ce (la ou ci)", + b.reg(r#"cet?t?e?s?"#)?, + cycle_check!(), + b.reg(r#"-?ci"#)?, + |_, cycle, _| helpers::cycle_nth(cycle.value().grain, 0) + ); + b.rule_2(" dernier", + cycle_check!(), + b.reg(r#"derni[èe]re?|pass[ée]e?|pr[eé]c[eé]dente?|(?:d')? ?avant"#)?, + |cycle, _| helpers::cycle_nth(cycle.value().grain, -1) + ); + b.rule_3("le dernier", + b.reg(r#"l[ae']? ?"#)?, + cycle_check!(), + b.reg(r#"derni[èe]re?|pass[ée]e?"#)?, + |_, cycle, _| helpers::cycle_nth(cycle.value().grain, -1) + ); + b.rule_3("n derniers ", + integer_check_by_range!(2, 9999), + b.reg(r#"derni.re?s?"#)?, + cycle_check!(), + |integer, _, cycle| { + let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, -1 * integer.value().value)?; + // All grains except Day will trigger the right datetime_kind + if cycle.value().grain == Grain::Day { + res = res.datetime_kind(DatetimeKind::DatePeriod); + } + Ok(res) + } + ); + b.rule_4("(pendant/durant/dans) les n derniers ", + b.reg(r#"(?:pendant |durant |dans )?[cld]es"#)?, + integer_check_by_range!(2, 9999), + b.reg(r#"derni.re?s?"#)?, + cycle_check!(), + |_, integer, _, cycle| { + let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, -1 * integer.value().value)?; + // All grains except Day will trigger the right datetime_kind + if cycle.value().grain == Grain::Day { + res = res.datetime_kind(DatetimeKind::DatePeriod); + } + Ok(res) + } + ); + b.rule_3("n passes|precedents", + integer_check_by_range!(2, 9999), + cycle_check!(), + b.reg(r#"pass[eèé][eèé]?s?|pr[eé]c[eé]dente?s?|(?:d')? ?avant|plus t[oô]t"#)?, + |integer, cycle, _| { + let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, -1 * integer.value().value)?; + // All grains except Day will trigger the right datetime_kind + if cycle.value().grain == Grain::Day { + res = res.datetime_kind(DatetimeKind::DatePeriod); + } + Ok(res) + } + ); + b.rule_4("(pendant/durant/dans) les n passes|precedents", + b.reg(r#"(?:pendant |durant |dans )?[cld]es"#)?, + integer_check_by_range!(2, 9999), + cycle_check!(), + b.reg(r#"pass[eèé][eèé]?s?|pr[eé]c[eé]dente?s?|(?:d')? ?avant|plus t[oô]t"#)?, + |_, integer, cycle, _| helpers::cycle_n_not_immediate(cycle.value().grain, -1 * integer.value().value) + ); + // Incorrect resolution if some follows the expression, + // e.g. "suivant le " (unsupported) + b.rule_2(" prochain|suivant|d'après", + cycle_check!(), + b.reg(r#"prochaine?|suivante?|qui suit|(?:d')? ?apr[eèé]s"#)?, + |cycle, _| helpers::cycle_nth(cycle.value().grain, 1) + ); + b.rule_3("le prochain|suivant|d'après", + b.reg(r#"l[ae']? ?|une? ?"#)?, + cycle_check!(), + b.reg(r#"prochaine?|suivante?|qui suit|(?:d'? ?)?apr[eèé]s"#)?, + |_, cycle, _| helpers::cycle_nth(cycle.value().grain, 1) + ); + b.rule_3("n prochains ", + integer_check_by_range!(2, 9999), + b.reg(r#"prochaine?s?|suivante?s?|apr[eèé]s"#)?, + cycle_check!(), + |integer, _, cycle| { + let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, integer.value().value)?; + // All grains except Day will trigger the right datetime_kind + if cycle.value().grain == Grain::Day { + res = res.datetime_kind(DatetimeKind::DatePeriod); + } + Ok(res) + } + ); + b.rule_4("(pendant/durant/dans) les n prochains ", + b.reg(r#"(?:pendant |durant |dans )?[cld]es"#)?, + integer_check_by_range!(2, 9999), + b.reg(r#"prochaine?s?|suivante?s?|apr[eèé]s"#)?, + cycle_check!(), + |_, integer, _, cycle| { + let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, integer.value().value)?; + // All grains except Day will trigger the right datetime_kind + if cycle.value().grain == Grain::Day { + res = res.datetime_kind(DatetimeKind::DatePeriod); + } + Ok(res) + } + ); + b.rule_3("n suivants", + integer_check_by_range!(2, 9999), + cycle_check!(), + b.reg(r#"prochaine?s?|suivante?s?|apr[eèé]s|qui sui(?:t|ves?)|plus tard"#)?, + |integer, cycle, _| { + let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, integer.value().value)?; + // All grains except Day will trigger the right datetime_kind + if cycle.value().grain == Grain::Day { + res = res.datetime_kind(DatetimeKind::DatePeriod); + } + Ok(res) + } + ); + b.rule_4("(pendant/durant/dans) les n suivants", + b.reg(r#"(?:pendant |durant |dans )?[cld]es"#)?, + integer_check_by_range!(2, 9999), + cycle_check!(), + b.reg(r#"prochaine?s?|suivante?s?|apr[eèé]s|qui sui(?:t|ves?)|plus tard"#)?, + |_, integer, cycle, _| { + let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, integer.value().value)?; + // All grains except Day will trigger the right datetime_kind + if cycle.value().grain == Grain::Day { + res = res.datetime_kind(DatetimeKind::DatePeriod); + } + Ok(res) + } + ); + b.rule_3("n avant", + integer_check_by_range!(2, 9999), + cycle_check!(), + b.reg(r#"(?:d')? ?avant|plus t[oô]t"#)?, + |integer, cycle, _| { + let mut res = helpers::cycle_nth(cycle.value().grain, -1 * integer.value().value)?; + // All grains except Day will trigger the right datetime_kind + if cycle.value().grain == Grain::Day { + res = res.datetime_kind(DatetimeKind::DatePeriod); + } + Ok(res) + } + ); + b.rule_3("n après", + integer_check_by_range!(2, 9999), + cycle_check!(), + b.reg(r#"(?:d')? ?apr[eèé]s|qui sui(?:t|ves?)|plus tard"#)?, + |integer, cycle, _| { + let mut res = helpers::cycle_nth(cycle.value().grain, integer.value().value)?; + // All grains except Day will trigger the right datetime_kind + if cycle.value().grain == Grain::Day { + res = res.datetime_kind(DatetimeKind::DatePeriod); + } + Ok(res) + } + ); + // Cycle patterns relative to another datetime + b.rule_4("le après|suivant ", + b.reg(r#"l[ea']? ?"#)?, + cycle_check!(), + b.reg(r#"suivante?|apr[eèé]s"#)?, + datetime_check!(), + |_, cycle, _, datetime| helpers::cycle_nth_after(cycle.value().grain, 1, datetime.value()) + ); + b.rule_4("le avant|précédent ", + b.reg(r#"l[ea']? ?"#)?, + cycle_check!(), + b.reg(r#"avant|pr[ée]c[ée]dent"#)?, + datetime_check!(), + |_, cycle, _, datetime| helpers::cycle_nth_after(cycle.value().grain, -1, datetime.value()) + ); + b.rule_4(" de ", + ordinal_check!(), + cycle_check!(), + b.reg(r#"d['eu]|en"#)?, + datetime_check!(), + |ordinal, cycle, _, datetime| helpers::cycle_nth_after_not_immediate(cycle.value().grain, ordinal.value().value - 1, datetime.value()) + ); + b.rule_5("le de ", + b.reg(r#"l[ea]"#)?, + ordinal_check!(), + cycle_check!(), + b.reg(r#"d['eu]|en"#)?, + datetime_check!(), + |_, ordinal, cycle, _, datetime| helpers::cycle_nth_after_not_immediate(cycle.value().grain, ordinal.value().value - 1, datetime.value()) + ); + b.rule_4("le de ", + b.reg(r#"l[ea]"#)?, + cycle_check!(), + b.reg(r#"d['eu]|en"#)?, + datetime_check!(), + |_, cycle, _, datetime| helpers::cycle_nth_after_not_immediate(cycle.value().grain, 0, datetime.value()) + ); Ok(()) } diff --git a/grammar/fr/src/rules_duration.rs b/grammar/fr/src/rules_duration.rs index 64d18c7d..907c38e8 100644 --- a/grammar/fr/src/rules_duration.rs +++ b/grammar/fr/src/rules_duration.rs @@ -36,6 +36,10 @@ pub fn rules_duration(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.reg(r#"trimestres?"#)?, |_| Ok(UnitOfDurationValue::new(Grain::Quarter)) ); + b.rule_1_terminal("trimestre (unit-of-duration)", + b.reg(r#"trimestres?"#)?, + |_| Ok(UnitOfDurationValue::new(Grain::Quarter)) + ); b.rule_1_terminal("un quart heure", b.reg(r#"(1/4\s?h(?:eure)?|(?:un|1) quart d'heure)"#)?, |_| Ok(DurationValue::new(PeriodComp::minutes(15).into())) diff --git a/grammar/fr/src/training.rs b/grammar/fr/src/training.rs index 036a392f..00b4c27a 100644 --- a/grammar/fr/src/training.rs +++ b/grammar/fr/src/training.rs @@ -118,7 +118,7 @@ pub fn examples_datetime(v: &mut Vec<::rustling::train::Example>) { example!(v, check_moment!(c, [2015, 10, 31]), "dernier jour d'octobre 2015", "le dernier jour d'octobre 2015"); example!(v, check_moment!(c, [2014, 9, 22], Grain::Week), "dernière semaine de septembre 2014", "la dernière semaine de septembre 2014"); //Hours - example!(v, check_moment!(c, [2013, 2, 12, 15]), "à quinze heures", "à 15 heures", "à 3 heures cet après-midi", "15h", "15H"); + example!(v, check_moment!(c, [2013, 2, 12, 15]), "à quinze heures", "à 15 heures", "15h précises", "15 heures pile", "à 3 heures cet après-midi", "15h", "15H"); example!(v, check_moment_with_precision!(c, [2013, 2, 12, 15], Precision::Approximate), "vers 15 heures", "à environ 15 heures"); example!(v, check_moment!(c, [2013, 2, 12, 15, 0]), "15:00", "15h00", "15H00"); example!(v, check_moment!(c, [2013, 2, 13, 00]), "minuit"); @@ -154,12 +154,14 @@ pub fn examples_datetime(v: &mut Vec<::rustling::train::Example>) { example!(v, check_moment!(c, [2011, 2]), "il y a deux ans"); //Seasons example!(v, check_moment_span!(c, [2013, 6, 21], [2013, 9, 24]), "cet été"); + example!(v, check_moment_span!(c, [2013, 7, 15], [2013, 8, 16]), "au milieu de cet été"); example!(v, check_moment_span!(c, [2012, 12, 21], [2013, 3, 21]), "cet hiver"); example!(v, check_moment!(c, [2013, 12, 25]), "Noel", "noël", "jour de noel"); example!(v, check_moment_span!(c, [2013, 12, 24, 18], [2013, 12, 25, 00]), "le soir de noël"); example!(v, check_moment!(c, [2014, 1, 1]), "jour de l'an", "nouvel an", "premier janvier"); + example!(v, check_moment!(c, [2013, 12, 31]), "le réveillon de la saint sylvestre", "pour la saint-sylvestre"); example!(v, check_moment!(c, [2013, 11, 1]), "la toussaint", "le jour de la toussaint", "la journée de la toussaint", "toussaint", "le jour des morts"); - example!(v, check_moment!(c, [2013, 05, 1]), "fête du travail"); + example!(v, check_moment!(c, [2013, 05, 1]), "fête du travail", "à la prochaine fête du travail"); //Part of day (morning, afternoon...) example!(v, check_moment_span!(c, [2013, 2, 12, 12], [2013, 2, 12, 19]), "cet après-midi", "l'après-midi"); example!(v, check_moment_span!(c, [2013, 2, 12, 15], [2013, 2, 12, 17]), "en milieu d'après-midi"); @@ -254,6 +256,7 @@ pub fn examples_datetime(v: &mut Vec<::rustling::train::Example>) { pub fn examples_durations(v: &mut Vec<::rustling::train::Example>) { example!(v, check_duration!([0, 0, 0, 0, 2]), "pendant deux heures", "durant deux heures", "pour une durée de deux heures", "une durée de deux heures"); example!(v, check_duration!([0, 0, 0, 1]), "pendant un jour", "une journée"); + example!(v, check_duration!([0, 0, 0, 3]), "pour une durée de quelques jours", "quelques jours"); example!(v, check_duration!([0, 1, 0]), "durant un mois"); example!(v, check_duration!([1]), "durant une année"); example!(v, check_duration!([0, 0, 0, 0, 0, 1, 3]), "pendant une minute et trois secondes"); From 59dee607305cde5e99d2fc672aefcfaea5ff4fc0 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Thu, 25 Jul 2019 17:16:31 +0200 Subject: [PATCH 098/107] Add training examples, Correct rule sunrise. --- grammar/fr/src/rules_datetime.rs | 2 +- grammar/fr/src/training.rs | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 63be6c61..87fd867e 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -930,7 +930,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { .form(Form::PartOfDay(PartOfDayForm::Morning))) ); b.rule_1_terminal("lever du soleil", - b.reg(r#"lever d[ue] soleil|aurore|aube"#)?, + b.reg(r#"lever d[ue] soleil|(?:aux )?aurores?|aube"#)?, |_| Ok(helpers::hour(4, false)? .span_to(&helpers::hour(8, false)?, false)? .latent() diff --git a/grammar/fr/src/training.rs b/grammar/fr/src/training.rs index 00b4c27a..251e2c00 100644 --- a/grammar/fr/src/training.rs +++ b/grammar/fr/src/training.rs @@ -154,8 +154,12 @@ pub fn examples_datetime(v: &mut Vec<::rustling::train::Example>) { example!(v, check_moment!(c, [2011, 2]), "il y a deux ans"); //Seasons example!(v, check_moment_span!(c, [2013, 6, 21], [2013, 9, 24]), "cet été"); + example!(v, check_moment_span!(c, [2013, 6, 21], [2013, 7, 16]), "au début de cet été"); example!(v, check_moment_span!(c, [2013, 7, 15], [2013, 8, 16]), "au milieu de cet été"); + example!(v, check_moment_span!(c, [2013, 8, 15], [2013, 9, 22]), "à la fin de cet été"); example!(v, check_moment_span!(c, [2012, 12, 21], [2013, 3, 21]), "cet hiver"); + example!(v, check_moment_span!(c, [2013, 10, 01], [2014, 01, 01]), "en fin d'année"); + example!(v, check_moment_span!(c, [2013, 01, 01], [2013, 03, 01]), "en début d'année"); example!(v, check_moment!(c, [2013, 12, 25]), "Noel", "noël", "jour de noel"); example!(v, check_moment_span!(c, [2013, 12, 24, 18], [2013, 12, 25, 00]), "le soir de noël"); example!(v, check_moment!(c, [2014, 1, 1]), "jour de l'an", "nouvel an", "premier janvier"); @@ -181,6 +185,9 @@ pub fn examples_datetime(v: &mut Vec<::rustling::train::Example>) { example!(v, check_moment_span!(c, [2013, 2, 12, 18], [2013, 2, 13, 00]), "ce soir"); example!(v, check_moment_span!(c, [2013, 2, 12, 18], [2013, 2, 12, 21]), "en début de soirée"); example!(v, check_moment_span!(c, [2013, 2, 12, 21], [2013, 2, 13, 00]), "en fin de soirée"); + example!(v, check_moment_span!(c, [2013, 2, 13, 02], [2013, 2, 13, 04]), "au milieu de la nuit"); + example!(v, check_moment_span!(c, [2013, 2, 12, 19], [2013, 2, 12, 22]), "au coucher du soleil", "à la tombée de la nuit", "pour le crépuscule"); + example!(v, check_moment_span!(c, [2013, 2, 12, 04], [2013, 2, 12, 08]), "au lever du soleil", "à l'aube", "aux aurores", "à l'aurore"); example!(v, check_moment_span!(c, [2013, 2, 13, 18], [2013, 2, 14, 00]), "demain soir", "mercredi soir", "mercredi en soirée"); example!(v, check_moment_span!(c, [2013, 2, 11, 18], [2013, 2, 12, 00]), "hier soir", "la veille au soir"); example!(v, check_moment_span!(c, [2013, 2, 15, 18], [2013, 2, 18, 00]), "ce week-end"); From 486cc8931e14838256d4e68e51b9fdc67f3082af Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Mon, 29 Jul 2019 17:42:53 +0200 Subject: [PATCH 099/107] Buf fixes after rebase. --- grammar/fr/src/rules_datetime.rs | 245 +++---------------------------- grammar/fr/src/rules_number.rs | 28 ---- values/src/helpers.rs | 6 - 3 files changed, 18 insertions(+), 261 deletions(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 87fd867e..05552675 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -40,225 +40,6 @@ pub fn rules_cycle(b: &mut RuleSetBuilder) -> RustlingResult<()> { Ok(()) } - -pub fn rules_datetime_with_cycle(b: &mut RuleSetBuilder) -> RustlingResult<()> { - - // Cycle patterns relative to now - b.rule_2("ce|dans le ", - b.reg(r#"(?:(?:dans )?l[ea' ]|cet?(?:te)?)"#)?, - cycle_check!(), - |_, cycle| helpers::cycle_nth(cycle.value().grain, 0) - ); - b.rule_3("ce (la ou ci)", - b.reg(r#"cet?t?e?s?"#)?, - cycle_check!(), - b.reg(r#"-?ci"#)?, - |_, cycle, _| helpers::cycle_nth(cycle.value().grain, 0) - ); - b.rule_2(" dernier", - cycle_check!(), - b.reg(r#"derni[èe]re?|pass[ée]e?|pr[eé]c[eé]dente?|(?:d')? ?avant"#)?, - |cycle, _| helpers::cycle_nth(cycle.value().grain, -1) - ); - b.rule_3("le dernier", - b.reg(r#"l[ae']? ?"#)?, - cycle_check!(), - b.reg(r#"derni[èe]re?|pass[ée]e?"#)?, - |_, cycle, _| helpers::cycle_nth(cycle.value().grain, -1) - ); - b.rule_3("n derniers ", - integer_check_by_range!(2, 9999), - b.reg(r#"derni.re?s?"#)?, - cycle_check!(), - |integer, _, cycle| { - let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, -1 * integer.value().value)?; - // All grains except Day will trigger the right datetime_kind - if cycle.value().grain == Grain::Day { - res = res.datetime_kind(DatetimeKind::DatePeriod); - } - Ok(res) - } - ); - b.rule_4("(pendant/durant/dans) les n derniers ", - b.reg(r#"(?:pendant |durant |dans )?[cld]es"#)?, - integer_check_by_range!(2, 9999), - b.reg(r#"derni.re?s?"#)?, - cycle_check!(), - |_, integer, _, cycle| { - let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, -1 * integer.value().value)?; - // All grains except Day will trigger the right datetime_kind - if cycle.value().grain == Grain::Day { - res = res.datetime_kind(DatetimeKind::DatePeriod); - } - Ok(res) - } - ); - b.rule_3("n passes|precedents", - integer_check_by_range!(2, 9999), - cycle_check!(), - b.reg(r#"pass[eèé][eèé]?s?|pr[eé]c[eé]dente?s?|(?:d')? ?avant|plus t[oô]t"#)?, - |integer, cycle, _| { - let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, -1 * integer.value().value)?; - // All grains except Day will trigger the right datetime_kind - if cycle.value().grain == Grain::Day { - res = res.datetime_kind(DatetimeKind::DatePeriod); - } - Ok(res) - } - ); - b.rule_4("(pendant/durant/dans) les n passes|precedents", - b.reg(r#"(?:pendant |durant |dans )?[cld]es"#)?, - integer_check_by_range!(2, 9999), - cycle_check!(), - b.reg(r#"pass[eèé][eèé]?s?|pr[eé]c[eé]dente?s?|(?:d')? ?avant|plus t[oô]t"#)?, - |_, integer, cycle, _| helpers::cycle_n_not_immediate(cycle.value().grain, -1 * integer.value().value) - ); - // Incorrect resolution if some follows the expression, - // e.g. "suivant le " (unsupported) - b.rule_2(" prochain|suivant|d'après", - cycle_check!(), - b.reg(r#"prochaine?|suivante?|qui suit|(?:d')? ?apr[eèé]s"#)?, - |cycle, _| helpers::cycle_nth(cycle.value().grain, 1) - ); - b.rule_3("le prochain|suivant|d'après", - b.reg(r#"l[ae']? ?|une? ?"#)?, - cycle_check!(), - b.reg(r#"prochaine?|suivante?|qui suit|(?:d'? ?)?apr[eèé]s"#)?, - |_, cycle, _| helpers::cycle_nth(cycle.value().grain, 1) - ); - b.rule_3("n prochains ", - integer_check_by_range!(2, 9999), - b.reg(r#"prochaine?s?|suivante?s?|apr[eèé]s"#)?, - cycle_check!(), - |integer, _, cycle| { - let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, integer.value().value)?; - // All grains except Day will trigger the right datetime_kind - if cycle.value().grain == Grain::Day { - res = res.datetime_kind(DatetimeKind::DatePeriod); - } - Ok(res) - } - ); - b.rule_4("(pendant/durant/dans) les n prochains ", - b.reg(r#"(?:pendant |durant |dans )?[cld]es"#)?, - integer_check_by_range!(2, 9999), - b.reg(r#"prochaine?s?|suivante?s?|apr[eèé]s"#)?, - cycle_check!(), - |_, integer, _, cycle| { - let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, integer.value().value)?; - // All grains except Day will trigger the right datetime_kind - if cycle.value().grain == Grain::Day { - res = res.datetime_kind(DatetimeKind::DatePeriod); - } - Ok(res) - } - ); - b.rule_3("n suivants", - integer_check_by_range!(2, 9999), - cycle_check!(), - b.reg(r#"prochaine?s?|suivante?s?|apr[eèé]s|qui sui(?:t|ves?)|plus tard"#)?, - |integer, cycle, _| { - let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, integer.value().value)?; - // All grains except Day will trigger the right datetime_kind - if cycle.value().grain == Grain::Day { - res = res.datetime_kind(DatetimeKind::DatePeriod); - } - Ok(res) - } - ); - b.rule_4("(pendant/durant/dans) les n suivants", - b.reg(r#"(?:pendant |durant |dans )?[cld]es"#)?, - integer_check_by_range!(2, 9999), - cycle_check!(), - b.reg(r#"prochaine?s?|suivante?s?|apr[eèé]s|qui sui(?:t|ves?)|plus tard"#)?, - |_, integer, cycle, _| { - let mut res = helpers::cycle_n_not_immediate(cycle.value().grain, integer.value().value)?; - // All grains except Day will trigger the right datetime_kind - if cycle.value().grain == Grain::Day { - res = res.datetime_kind(DatetimeKind::DatePeriod); - } - Ok(res) - } - ); - b.rule_3("n avant", - integer_check_by_range!(2, 9999), - cycle_check!(), - b.reg(r#"(?:d')? ?avant|plus t[oô]t"#)?, - |integer, cycle, _| { - let mut res = helpers::cycle_nth(cycle.value().grain, -1 * integer.value().value)?; - // All grains except Day will trigger the right datetime_kind - if cycle.value().grain == Grain::Day { - res = res.datetime_kind(DatetimeKind::DatePeriod); - } - Ok(res) - } - ); - b.rule_3("n après", - integer_check_by_range!(2, 9999), - cycle_check!(), - b.reg(r#"(?:d')? ?apr[eèé]s|qui sui(?:t|ves?)|plus tard"#)?, - |integer, cycle, _| { - let mut res = helpers::cycle_nth(cycle.value().grain, integer.value().value)?; - // All grains except Day will trigger the right datetime_kind - if cycle.value().grain == Grain::Day { - res = res.datetime_kind(DatetimeKind::DatePeriod); - } - Ok(res) - } - ); - // Cycle patterns relative to another datetime - b.rule_4("le après|suivant ", - b.reg(r#"l[ea']? ?"#)?, - cycle_check!(), - b.reg(r#"suivante?|apr[eèé]s"#)?, - datetime_check!(), - |_, cycle, _, datetime| helpers::cycle_nth_after(cycle.value().grain, 1, datetime.value()) - ); - b.rule_4("le avant|précédent ", - b.reg(r#"l[ea']? ?"#)?, - cycle_check!(), - b.reg(r#"avant|pr[ée]c[ée]dent"#)?, - datetime_check!(), - |_, cycle, _, datetime| helpers::cycle_nth_after(cycle.value().grain, -1, datetime.value()) - ); - b.rule_1_terminal("fin du mois", - b.reg(r#"(?:(?:(?:[aà] )?la|en)? )?fin (?:du|de) mois"#)?, - |_| { - let month = helpers::cycle_nth(Grain::Month, 1)?; - Ok(helpers::cycle_nth_after(Grain::Day, -10, &month)? - .span_to(&month, false)? - .latent() - .form(Form::PartOfMonth)) - } - ); - b.rule_5("le de ", - b.reg(r#"l[ea]"#)?, - ordinal_check_by_range!(1, 9999), - cycle_check!(), - b.reg(r#"d['eu]|en"#)?, - datetime_check!(), - |_, ordinal, cycle, _, datetime| helpers::cycle_nth_after_not_immediate(cycle.value().grain, ordinal.value().value - 1, datetime.value()) - ); - b.rule_4("le de ", - b.reg(r#"l[ea]"#)?, - cycle_check!(), - b.reg(r#"d['eu]|en"#)?, - datetime_check!(), - |_, cycle, _, datetime| helpers::cycle_nth_after_not_immediate(cycle.value().grain, 0, datetime.value()) - ); - b.rule_2("le lendemain du ", - b.reg(r#"(?:le|au)? ?lendemain du"#)?, - datetime_check!(), - |_, datetime| helpers::cycle_nth_after_not_immediate(Grain::Day, 1, datetime.value()) - ); - b.rule_2("la veille du ", - b.reg(r#"(la )?veille du"#)?, - datetime_check!(), - |_, datetime| helpers::cycle_nth_after_not_immediate(Grain::Day, -1, datetime.value()) - ); - Ok(()) -} - pub fn rules_datetime_with_duration(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.rule_2("il y a ", b.reg(r#"il y a"#)?, @@ -458,14 +239,14 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { |_| helpers::cycle_nth(Grain::Day, -1) ); b.rule_1_terminal("fin du mois", - b.reg(r#"(?:(?:[aà] )?la )?fin (?:du|de) mois"#)?, - |_| { - let month = helpers::cycle_nth(Grain::Month, 1)?; - Ok(helpers::cycle_nth_after(Grain::Day, -10, &month)? - .span_to(&month, false)? - .latent() - .form(Form::PartOfMonth)) - } + b.reg(r#"(?:(?:(?:[aà] )?la|en)? )?fin (?:du|de) mois"#)?, + |_| { + let month = helpers::cycle_nth(Grain::Month, 1)?; + Ok(helpers::cycle_nth_after(Grain::Day, -10, &month)? + .span_to(&month, false)? + .latent() + .form(Form::PartOfMonth)) + } ); b.rule_1_terminal("après-demain", b.reg(r#"apr(?:e|è)s[- ]?demain"#)?, @@ -475,6 +256,16 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.reg(r#"avant[- ]?hier"#)?, |_| helpers::cycle_nth(Grain::Day, -2) ); + b.rule_2("le lendemain du ", + b.reg(r#"(?:le|au)? ?lendemain du"#)?, + datetime_check!(), + |_, datetime| helpers::cycle_nth_after_not_immediate(Grain::Day, 1, datetime.value()) + ); + b.rule_2("la veille du ", + b.reg(r#"(la )?veille du"#)?, + datetime_check!(), + |_, datetime| helpers::cycle_nth_after_not_immediate(Grain::Day, -1, datetime.value()) + ); b.rule_2("ce ", b.reg(r#"ce"#)?, datetime_check!(form!(Form::DayOfWeek{..})), diff --git a/grammar/fr/src/rules_number.rs b/grammar/fr/src/rules_number.rs index fb889dc1..ab853698 100644 --- a/grammar/fr/src/rules_number.rs +++ b/grammar/fr/src/rules_number.rs @@ -273,34 +273,6 @@ pub fn rules_numbers(b: &mut RuleSetBuilder) -> RustlingResult<()> { }) } ); - b.rule_2("numbers suffixes (K, M, G)", - number_check!(|number: &NumberValue| !number.suffixed()), - b.reg_neg_lh(r#"([kmg])"#, r#"^[\W\$€]"#)?, - |a, text_match| -> RuleResult { - let multiplier = match text_match.group(0).as_ref() { - "k" => 1000, - "m" => 1000000, - "g" => 1000000000, - _ => return Err(RuleError::Invalid.into()), - }; - Ok(match a.value().clone() { // checked - NumberValue::Integer(integer) => { - IntegerValue { - prefixed: true, - ..integer - } - .into() - } - NumberValue::Float(float) => { - FloatValue { - prefixed: true, - ..float - } - .into() - } - }) - } - ); b.rule_2("numbers suffixes (K, M, G)", number_check!(|number: &NumberValue| !number.suffixed()), b.reg_neg_lh(r#"([kmg])"#, r#"^[\W\$€]"#)?, diff --git a/values/src/helpers.rs b/values/src/helpers.rs index 9583ab23..50e45d1c 100644 --- a/values/src/helpers.rs +++ b/values/src/helpers.rs @@ -408,12 +408,6 @@ pub fn year(y: i32) -> RuleResult { Ok(DatetimeValue::constraint(Year::new(y)).form(Form::Year(y))) } -pub fn weekend() -> RuleResult { - let friday = day_of_week(Weekday::Fri)?.intersect(&hour(18, false)?)?; - let monday = day_of_week(Weekday::Mon)?.intersect(&hour(0, false)?)?; - Ok(friday.span_to(&monday, false)?.datetime_kind(DatetimeKind::DatePeriod)) -} - pub fn month(m: u32) -> RuleResult { if !(1 <= m && m <= 12) { return Err(RuleError::Invalid.into()) From 7bdc633051253d8d14200a03ff2bd71678d7bb58 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Mon, 29 Jul 2019 18:32:01 +0200 Subject: [PATCH 100/107] reintegrate deleted rules in numbers --- grammar/fr/src/rules_number.rs | 39 ++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/grammar/fr/src/rules_number.rs b/grammar/fr/src/rules_number.rs index 9e065b39..9d1989c0 100644 --- a/grammar/fr/src/rules_number.rs +++ b/grammar/fr/src/rules_number.rs @@ -273,6 +273,45 @@ pub fn rules_numbers(b: &mut RuleSetBuilder) -> RustlingResult<()> { }) } ); + b.rule_2("numbers suffixes (K, M, G)", + number_check!(|number: &NumberValue| !number.suffixed()), + b.reg_neg_lh(r#"([kmg])"#, r#"^[\W\$€]"#)?, + |a, text_match| -> RuleResult { + let multiplier = match text_match.group(0).as_ref() { + "k" => 1000, + "m" => 1000000, + "g" => 1000000000, + _ => return Err(RuleError::Invalid.into()), + }; + Ok(match a.value().clone() { // checked + NumberValue::Integer(integer) => { + IntegerValue { + value: integer.value * multiplier, + suffixed: true, + ..integer + } + .into() + } + NumberValue::Float(float) => { + let product = float.value * (multiplier as f32); + if product.floor() == product { + IntegerValue { + value: product as i64, + suffixed: true, + ..IntegerValue::default() + } + .into() + } else { + FloatValue { + value: product, + suffixed: true, + ..float + } + .into() + } + } + }) + }); b.rule_1_terminal("(douzaine ... soixantaine)", b.reg(r#"(demi[ -]douz|diz|douz|quinz|vingt|trent|quarant|cinquant|soixant|cent)aines?"#)?, |text_match| { From 8b1d6a35c1d6c4c52b11471e895e4d030cdc8819 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Tue, 30 Jul 2019 11:17:46 +0200 Subject: [PATCH 101/107] =?UTF-8?q?Change=20det=20qqs=20->=203=20to=20qqs?= =?UTF-8?q?=20=3D=202=20in=20number,=20delete=20redondant=20rule=20'vers',?= =?UTF-8?q?=20change=20name=20rule=20'=C3=A0=20'.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- grammar/fr/src/rules_datetime.rs | 7 +------ grammar/fr/src/rules_number.rs | 2 +- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 2afe2737..bdd3e82f 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -369,11 +369,6 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.reg(r#"pile"#)?, |a, _| Ok(a.value().clone().not_latent()) ); - b.rule_2("à|vers ", - b.reg(r#"(?:vers|autour de|[aà] environ|aux alentours de|[aà])"#)?, - datetime_check!(form!(Form::TimeOfDay(_))), - |_, a| Ok(a.value().clone().not_latent()) - ); b.rule_1_terminal("hh(:|h)mm (time-of-day)", b.reg(r#"((?:[01]?\d)|(?:2[0-3]))[:h]([0-5]\d)"#)?, |text_match| { @@ -511,7 +506,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { datetime_check!(form!(Form::TimeOfDay(_))), |_, a| Ok(a.value().clone().not_latent()) ); - b.rule_2("à ", + b.rule_2("à ", b.reg(r#"[aà]|pour"#)?, datetime_check!(form!(Form::PartOfDay(_))), |_, a| Ok(a.value().clone().not_latent()) diff --git a/grammar/fr/src/rules_number.rs b/grammar/fr/src/rules_number.rs index 9d1989c0..6cb50604 100644 --- a/grammar/fr/src/rules_number.rs +++ b/grammar/fr/src/rules_number.rs @@ -38,7 +38,7 @@ pub fn rules_numbers(b: &mut RuleSetBuilder) -> RustlingResult<()> { }); b.rule_1_terminal("quelques", b.reg(r#"quelques"#)?, - |_| IntegerValue::new_with_grain(3, 1) + |_| IntegerValue::new_with_grain(2, 1) ); b.rule_1_terminal("number (20..60)", b.reg(r#"(vingt|trente|quarante|cinquante|soixante)"#)?, From 364381c95e2ef65035311404cc759216dbeb1c6a Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Tue, 30 Jul 2019 11:43:19 +0200 Subject: [PATCH 102/107] =?UTF-8?q?Add=20support=20for=20'depuis=20+=20'=20and=20change=20'go=C3=BBter'=20hour=20range.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- grammar/fr/src/rules_datetime.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index bdd3e82f..8beaf1b1 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -707,7 +707,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.rule_1_terminal("gouter", b.reg(r#"(?:(?:[àa] )?l[' ]heure du|au(?: moment du)?|pendant le|(?:pour )?le) go[uû]ter"#)?, |_| Ok(helpers::hour(16, false)? - .span_to(&helpers::hour(18, false)?, false)? + .span_to(&helpers::hour(17, false)?, false)? .form(Form::Meal)) ); b.rule_1_terminal("thé", @@ -1366,12 +1366,12 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { |_, datetime| Ok(datetime.value().clone().mark_after_end()) ); b.rule_2("à partir de ", - b.reg(r#"[aà] partir d['eu]"#)?, + b.reg(r#"[aà] partir d['eu]|depuis"#)?, datetime_check!(|datetime: &DatetimeValue| !datetime.latent), |_, datetime| Ok(datetime.value().clone().mark_after_start()) ); b.rule_2("à partir de ", - b.reg(r#"[aà] partir d['eu](?:l[ 'a])?"#)?, + b.reg(r#"[aà] partir d['eu](?:l[ 'a])?|depuis"#)?, datetime_check!(form!(Form::PartOfDay(_))), |_, datetime| Ok(datetime.value().clone().mark_after_start()) ); From 22922c35cb09ff01970073a6a53ce183961c7898 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Tue, 30 Jul 2019 11:52:24 +0200 Subject: [PATCH 103/107] Delete 'depuis' in rule 'from + datetime' (ambiguity depuis + duration?). --- grammar/fr/src/rules_datetime.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grammar/fr/src/rules_datetime.rs b/grammar/fr/src/rules_datetime.rs index 8beaf1b1..54b05d7a 100644 --- a/grammar/fr/src/rules_datetime.rs +++ b/grammar/fr/src/rules_datetime.rs @@ -1366,7 +1366,7 @@ pub fn rules_datetime(b: &mut RuleSetBuilder) -> RustlingResult<()> { |_, datetime| Ok(datetime.value().clone().mark_after_end()) ); b.rule_2("à partir de ", - b.reg(r#"[aà] partir d['eu]|depuis"#)?, + b.reg(r#"[aà] partir d['eu]"#)?, datetime_check!(|datetime: &DatetimeValue| !datetime.latent), |_, datetime| Ok(datetime.value().clone().mark_after_start()) ); From 39f5701030042bdabcd879f1af707b937d1be805 Mon Sep 17 00:00:00 2001 From: Johanna Simoens Date: Tue, 30 Jul 2019 12:14:20 +0200 Subject: [PATCH 104/107] Delete rules.rs file (split into several files). --- grammar/fr/src/rules.rs | 2264 --------------------------------------- 1 file changed, 2264 deletions(-) delete mode 100644 grammar/fr/src/rules.rs diff --git a/grammar/fr/src/rules.rs b/grammar/fr/src/rules.rs deleted file mode 100644 index 0f8dc87c..00000000 --- a/grammar/fr/src/rules.rs +++ /dev/null @@ -1,2264 +0,0 @@ -use rustling::*; -use rustling_ontology_values::dimension::*; -use rustling_ontology_values::dimension::Precision::*; -use rustling_ontology_values::helpers; -use rustling_ontology_moment::{Weekday, Grain, PeriodComp, Period}; - -pub fn rules_percentage(b: &mut RuleSetBuilder) -> RustlingResult<()> { - b.rule_2(" per cent", - number_check!(), - b.reg(r"(?:%|p\.c\.|p. cents?|pour[ -]?cents?)")?, - |number, _| Ok(PercentageValue(number.value().value())) - ); - Ok(()) -} - -pub fn rules_finance(b: &mut RuleSetBuilder) -> RustlingResult<()> { - b.rule_2("intersect (X cents)", - amount_of_money_check!(), - amount_of_money_check!(|money: &AmountOfMoneyValue| money.unit == Some("cent")), - |a, b| helpers::compose_money(a.value(), b.value())); - b.rule_3("intersect (and X cents)", - amount_of_money_check!(), - b.reg(r#"et"#)?, - amount_of_money_check!(|money: &AmountOfMoneyValue| money.unit == Some("cent")), - |a, _, b| helpers::compose_money(&a.value(), &b.value())); - b.rule_2("intersect", - amount_of_money_check!(|money: &AmountOfMoneyValue| money.unit != Some("cent")), - number_check!(), - |a, b| helpers::compose_money_number(&a.value(), &b.value())); - b.rule_1_terminal("$", - b.reg(r#"\$|dollars?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("$") }) - ); - b.rule_1_terminal("EUR", - b.reg(r#"€|(?:[e€]uro?s?)"#)?, - |_| Ok(MoneyUnitValue { unit: Some("EUR") }) - ); - b.rule_1_terminal("£", - b.reg(r#"£|livres?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("£") }) - ); - b.rule_1_terminal("USD", - b.reg(r#"us[d\$]|dollars? am[eé]ricains?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("USD") }) - ); - b.rule_1_terminal("AUD", - b.reg(r#"au[d\$]|dollars? australiens?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("AUD") }) - ); - b.rule_1_terminal("CAD", - b.reg(r#"cad|dollars? canadiens?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("CAD") }) - ); - b.rule_1_terminal("HKD", - b.reg(r#"hkd|dollars? de hong[- ]kong"#)?, - |_| Ok(MoneyUnitValue { unit: Some("HKD") }) - ); - b.rule_1_terminal("KR", - b.reg(r#"kr|couronnes?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("KR") }) - ); - b.rule_1_terminal("DKK", - b.reg(r#"dkk|couronnes? danoises?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("DKK") }) - ); - b.rule_1_terminal("NOK", - b.reg(r#"nok|couronnes? norv[ée]giennes?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("NOK") }) - ); - b.rule_1_terminal("SEK", - b.reg(r#"sek|couronnes? su[ée]doises?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("SEK") }) - ); - b.rule_1_terminal("CHF", - b.reg(r#"chf|francs? suisses?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("CHF") }) - ); - b.rule_1_terminal("RUB", - b.reg(r#"rub|roubles?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("RUB") }) - ); - b.rule_1_terminal("INR", - b.reg(r#"inr|roupies?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("INR") }) - ); - b.rule_1_terminal("JPY", - b.reg(r#"jpy|yens?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("JPY") }) - ); - b.rule_1_terminal("RMB|CNH|CNY", - b.reg(r#"cny|cnh|rmb|yuans?|renmimbis?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("CNY") }) - ); - b.rule_1_terminal("¥", - b.reg(r#"¥"#)?, - |_| Ok(MoneyUnitValue { unit: Some("¥") }) - ); - b.rule_1_terminal("KRW", - b.reg(r#"₩|krw|wons? (?:sud[- ])?cor[ée]ns?|wons?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("KRW") }) - ); - b.rule_1_terminal("Bitcoin", - b.reg(r#"฿|bitcoins?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("฿") }) - ); - b.rule_1_terminal("GBP", - b.reg(r#"gbp|livres? sterlings?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("GBP") }) - ); - b.rule_1_terminal("cent", - b.reg(r#"centimes?|cents?|penn(?:y|ies)|fens?"#)?, - |_| Ok(MoneyUnitValue { unit: Some("cent") }) - ); - b.rule_1_terminal("unnamed currency", - b.reg(r#"(?:balle)s?"#)?, - |_| Ok(MoneyUnitValue { unit: None }) - ); - b.rule_2(" ", - number_check!(), - money_unit!(), - |a, b| { - Ok(AmountOfMoneyValue { - value: a.value().value(), - unit: b.value().unit, - ..AmountOfMoneyValue::default() - }) - }); - b.rule_3(" de ", // "un million de dollars" - integer_check!(|integer: &IntegerValue| !integer.group), - b.reg(r#"d[e']"#)?, - money_unit!(), - |a, _, b| { - Ok(AmountOfMoneyValue { - value: a.value().value as f32, - precision: Exact, - unit: b.value().unit, - ..AmountOfMoneyValue::default() - }) - }); - b.rule_3(" de ", // "une douzaine de dollars" - integer_check!(|integer: &IntegerValue| integer.group), - b.reg(r#"d[e']"#)?, - money_unit!(), - |a, _, b| { - Ok(AmountOfMoneyValue { - value: a.value().value as f32, - precision: Approximate, - unit: b.value().unit, - ..AmountOfMoneyValue::default() - }) - }); - b.rule_2("about ", - b.reg(r#"(?:autour|pas loin|pr[eè]s|aux alentours) d[e']|environ|presque|(?:approximative|quasi)ment"#)?, - amount_of_money_check!(), - |_, a| { - Ok(AmountOfMoneyValue { - precision: Approximate, - ..a.value().clone() - }) - }); - b.rule_2("exactly ", - b.reg(r#"(?:tr[eè]s )?exactement|pr[eé]cis[eé]ment|pile(?: poil)?"#)?, - amount_of_money_check!(), - |_, a| { - Ok(AmountOfMoneyValue { - precision: Exact, - ..a.value().clone() - }) - }); - b.rule_2("exactly ", - amount_of_money_check!(), - b.reg(r#"pile(?: poil)?|tout rond"#)?, - |a, _| { - Ok(AmountOfMoneyValue { - precision: Exact, - ..a.value().clone() - }) - } - ); - Ok(()) -} - -pub fn rules_duration(b: &mut RuleSetBuilder) -> RustlingResult<()> { - b.rule_1_terminal("seconde (unit-of-duration)", - b.reg(r#"sec(?:onde)?s?"#)?, - |_| Ok(UnitOfDurationValue::new(Grain::Second)) - ); - b.rule_1_terminal("minute (unit-of-duration)", - b.reg(r#"min(?:ute)?s?"#)?, - |_| Ok(UnitOfDurationValue::new(Grain::Minute)) - ); - b.rule_1_terminal("heure (unit-of-duration)", - b.reg(r#"h(?:eure)?s?"#)?, - |_| Ok(UnitOfDurationValue::new(Grain::Hour)) - ); - b.rule_1_terminal("jour (unit-of-duration)", - b.reg(r#"jour(?:n[ée]e?)?s?"#)?, - |_| Ok(UnitOfDurationValue::new(Grain::Day)) - ); - b.rule_1_terminal("semaine (unit-of-duration)", - b.reg(r#"semaines?"#)?, - |_| Ok(UnitOfDurationValue::new(Grain::Week)) - ); - b.rule_1_terminal("mois (unit-of-duration)", - b.reg(r#"mois?"#)?, - |_| Ok(UnitOfDurationValue::new(Grain::Month)) - ); - b.rule_1_terminal("année (unit-of-duration)", - b.reg(r#"an(?:n[ée]e?)?s?"#)?, - |_| Ok(UnitOfDurationValue::new(Grain::Year)) - ); - b.rule_1_terminal("trimestre (unit-of-duration)", - b.reg(r#"trimestres?"#)?, - |_| Ok(UnitOfDurationValue::new(Grain::Quarter)) - ); - b.rule_1_terminal("un quart heure", - b.reg(r#"(1/4\s?h(?:eure)?|(?:un|1) quart d'heure)"#)?, - |_| Ok(DurationValue::new(PeriodComp::minutes(15).into())) - ); - b.rule_1_terminal("une demi heure", - b.reg(r#"(?:1/2\s?h(?:eure)?|(?:1|une) demi(?:e)?(?:\s|-)heure)"#)?, - |_| Ok(DurationValue::new(PeriodComp::minutes(30).into())) - ); - b.rule_1_terminal("trois quarts d'heure", - b.reg(r#"(?:3/4\s?h(?:eure)?|(?:3|trois) quart(?:s)? d'heure)"#)?, - |_| Ok(DurationValue::new(PeriodComp::minutes(45).into())) - ); - b.rule_2(" ", - integer_check_by_range!(0), - unit_of_duration_check!(), - |integer, unit| Ok(DurationValue::new(PeriodComp::new(unit.value().grain, integer.value().value).into())) - ); - b.rule_3(" de ", - integer_check!(|integer: &IntegerValue| integer.value >= 0 && integer.group), - b.reg(r#"d[e']"#)?, - unit_of_duration_check!(), - |integer, _, unit| Ok(DurationValue::new(PeriodComp::new(unit.value().grain, integer.value().value).into())) - ); - b.rule_4(" h ", - integer_check_by_range!(0), - b.reg(r#"h(?:eures?)?"#)?, - integer_check_by_range!(0,59), - b.reg(r#"m(?:inutes?)?"#)?, - |hour, _, minute, _| { - let hour_period = Period::from(PeriodComp::new(Grain::Hour, hour.value().clone().value)); - let minute_period = Period::from(PeriodComp::new(Grain::Minute, minute.value().clone().value)); - Ok(DurationValue::new(hour_period + minute_period)) - } - ); - b.rule_3(" et quart", - integer_check_by_range!(0), - unit_of_duration_check!(), - b.reg(r#"et quart"#)?, - |integer, uod, _| { - let quarter_period: Period = uod.value().grain.quarter_period().map(|a| a.into()).ok_or_else(|| RuleError::Invalid)?; - Ok(DurationValue::new(quarter_period + PeriodComp::new(uod.value().grain, integer.value().value))) - } - ); - b.rule_3(" et demie", - integer_check_by_range!(0), - unit_of_duration_check!(), - b.reg(r#"et demie?"#)?, - |integer, uod, _| { - let half_period: Period = uod.value().grain.half_period().map(|a| a.into()).ok_or_else(|| RuleError::Invalid)?; - Ok(DurationValue::new(half_period + PeriodComp::new(uod.value().grain, integer.value().value))) - } - ); - b.rule_3(" et ", - duration_check!(|duration: &DurationValue| !duration.suffixed), - b.reg(r#"et"#)?, - duration_check!(|duration: &DurationValue| !duration.prefixed), - |a, _, b| Ok(a.value() + b.value()) - ); - b.rule_2(" ", - duration_check!(|duration: &DurationValue| !duration.suffixed), - duration_check!(|duration: &DurationValue| !duration.prefixed), - |a, b| Ok(a.value() + b.value()) - ); - b.rule_2(" ", - duration_check!(|duration: &DurationValue| !duration.prefixed), - integer_check_by_range!(0), - |duration, integer| helpers::compose_duration_with_integer(duration.value(), integer.value()) - ); - b.rule_2("dans ", - b.reg(r#"dans"#)?, - duration_check!(), - |_, duration| duration.value().in_present() - ); - b.rule_2(" plus tard", - duration_check!(), - b.reg(r"plus tard")?, - |duration, _| duration.value().in_present() - ); - b.rule_2("environ ", - b.reg(r#"environ|approximativement|à peu près|presque"#)?, - duration_check!(), - |_, duration| Ok(duration.value().clone().precision(Precision::Approximate)) - ); - b.rule_2(" environ", - duration_check!(), - b.reg(r#"environ|approximativement|à peu près"#)?, - |duration, _| Ok(duration.value().clone().precision(Precision::Approximate)) - ); - b.rule_2("exactement ", - b.reg(r#"exactement|précisément"#)?, - duration_check!(), - |_, duration| Ok(duration.value().clone().precision(Precision::Exact)) - ); - b.rule_2(" exactement", - duration_check!(), - b.reg(r#"exactement|précisément|pile"#)?, - |duration, _| Ok(duration.value().clone().precision(Precision::Exact)) - ); - b.rule_2("pendant ", - b.reg(r#"pendant|durant|pour"#)?, - duration_check!(), - |_, duration| Ok(duration.value().clone().prefixed()) - ); - b.rule_2("une durée de ", - b.reg(r#"une dur[ée]e d['e]"#)?, - duration_check!(), - |_, duration| Ok(duration.value().clone().prefixed()) - ); - b.rule_2("il y a ", - b.reg(r#"il y a"#)?, - duration_check!(), - |_, duration| duration.value().ago() - ); - b.rule_2("depuis ", - b.reg(r#"depuis|[cç]a fait"#)?, - duration_check!(), - |_, duration| { - duration.value().ago()? - .span_to(&helpers::cycle_nth(Grain::Second, 0)?, false) - }); - b.rule_3(" apres