diff --git a/grammar/en/src/lib.rs b/grammar/en/src/lib.rs index a1d77c2c..56fb351f 100644 --- a/grammar/en/src/lib.rs +++ b/grammar/en/src/lib.rs @@ -23,7 +23,7 @@ pub fn rule_set() -> ::rustling::RustlingResult<::rustling::RuleSet) -> Rustli } -pub fn rules_datetime_with_nth_cycle(b: &mut RuleSetBuilder) -> RustlingResult<()> { +// FIXME: rename "rules_datetime_with_cycle" +pub fn rules_datetime_with_cycle(b: &mut RuleSetBuilder) -> RustlingResult<()> { b.rule_2("this ", b.reg(r#"this|current|coming"#)?, 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)) - ); - 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(()) -} - -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("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) - ); - b.rule_2("ce|dans le ", - b.reg(r#"(?:cet?t?e?s?)|(?:dans l[ae']? ?)"#)?, - 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_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("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", - 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_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("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) - ); - 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) - ); - 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("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!(), - 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("le 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#"(?: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) - ); - b.rule_1_terminal("aujourd'hui", - b.reg(r#"(?:aujourd'? ?hui)|(?:ce jour)|(?:dans la journ[ée]e?)|(?:en ce moment)"#)?, - |_| helpers::cycle_nth(Grain::Day, 0) - ); - 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| datetime.value().the_nth(0) - ); - 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!(), - b.reg(r#"prochain"#)?, - |datetime, _| datetime.value().the_nth(1) - ); - 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 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) - } - ); - 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()) - } - ); - 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("en ", - b.reg(r#"(?:en(?: l'an)?|de l'ann[eé])"#)?, - 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!(), - |_, 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| month.value().intersect(&helpers::day_of_month(integer.value().value as u32)?) - ); - 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(" à )", - 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) - ?.intersect(tod.value()) - ); - b.rule_1("time-of-day (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)", - integer_check_by_range!(0, 0), - |_| Ok(helpers::hour(0, false)?.latent()) - ); - b.rule_1_terminal("midi", - b.reg(r#"midi"#)?, - |_| helpers::hour(12, false) - ); - b.rule_1_terminal("minuit", - b.reg(r#"minuit"#)?, - |_| helpers::hour(0, false) - ); - b.rule_2(" heures", - datetime_check!(form!(Form::TimeOfDay(TimeOfDayForm::Hour { .. }))), - b.reg(r#"h\.?(?:eure)?s?"#)?, - |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(" 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_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() - ) - ); - // Written dates in numeric formats - 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 dates 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 ", - 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)), - |_, a| Ok(a.value().clone().not_latent()) - ); - b.rule_2(" ", - datetime_check!(), - 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)), - |_, 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())) - ); - b.rule_2(" ", - datetime_check!(excluding_form!(Form::TimeOfDay(_))), - 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?)?"#)?, - |a, _| { - let period = helpers::hour(0, false)? - .span_to(&helpers::hour(12, false)?, false)?; - a.value().intersect(&period) - } - ); - b.rule_2(" du soir", - datetime_check!(form!(Form::TimeOfDay(_))), - b.reg(r#"(?:(?:du|dans|de) )?(?:(?:au|le|la) )?soir[ée]?e?"#)?, - |a, _| { - let period = helpers::hour(16, false)? - .span_to(&helpers::hour(0, false)?, false)?; - a.value().intersect(&period) - } - ); - b.rule_3(" du ", - datetime_check!(|datetime: &DatetimeValue| form!(Form::PartOfDay(_))(datetime) || form!(Form::Meal)(datetime)), - b.reg(r#"du"#)?, - datetime_check!(), - |a, _, b| b.value().intersect(a.value()) - ); - b.rule_1_terminal("week-end", - b.reg(r#"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) - } - ); - 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#"\-|au|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#"\-|au|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#"\-|au|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#"\-|au|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#"\-|au|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#"\-|au|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#"\-|au|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#"au|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) - } - ); - b.rule_3(" - (interval)", - datetime_check!(|datetime: &DatetimeValue| !datetime.latent && excluding_form!(Form::TimeOfDay(_))(datetime)), - b.reg(r#"\-|au|[aà]|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#"\-|au|[aà]|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#"\-|au|[aà]|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#" \- |[aà]|au|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"#)?, - datetime_check!(form!(Form::TimeOfDay(_))), - b.reg(r#"[aà]|au|jusqu'(?:au|[aà])"#)?, - datetime_check!(form!(Form::TimeOfDay(_))), - |_, a, _, b| a.value().smart_span_to(b.value(), 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("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!(), - |_, datetime| Ok(datetime.value().clone().mark_before_end()) - ); - b.rule_2("avant ", - b.reg(r#"(?:n[ ']importe quand )?avant"#)?, - datetime_check!(), - |_, datetime| Ok(datetime.value().clone().mark_before_start()) - ); - b.rule_2("après ", - b.reg(r#"apr(?:e|è)s"#)?, - datetime_check!(), - |_, datetime| Ok(datetime.value().clone().mark_after_end()) - ); - b.rule_2("après ", - b.reg(r#"(?:a|à) partir de"#)?, - datetime_check!(), - |_, 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 du"#)?, - integer_check_by_range!(1, 31), - |_, integer| Ok(helpers::day_of_month(integer.value().value as u32)?.mark_after_start()) - ); - 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_amount.rs b/grammar/fr/src/rules_amount.rs new file mode 100644 index 00000000..11a7f155 --- /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(()) +} 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..42b5305e --- /dev/null +++ b/grammar/fr/src/rules_datetime.rs @@ -0,0 +1,1731 @@ +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à] |pour )?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"#)?, + |_| 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) + ); + // TODO: add restrictions on datetime form? + b.rule_2("au prochain ", + b.reg(r#"(au |(?:[aà] )?l[ae] )prochaine?"#)?, + 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())) + } + ); + // 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| !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("au prochain ", + b.reg(r#"(au |(?:[aà] )?l[ae] )prochaine?"#)?, + 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())) + } + ); + // 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| !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 + // "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|prochain"#)?, + |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_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("à ", + 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(_))), + |_, 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("lever du soleil", + b.reg(r#"lever d[ue] soleil|(?:aux )?aurores?|aube"#)?, + |_| Ok(helpers::hour(4, false)? + .span_to(&helpers::hour(8, 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#"(?: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"#)?, + |_| 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 (?: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"#)?, + |_| 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(17, 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 )?|(?:(?:[àa] )?la )?mi[ -])journ[ée]e"#)?, + |_| { + Ok(helpers::hour(12, 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("coucher du soleil", + 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)? + .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_1_terminal("milieu de la nuit", + b.reg(r#"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)), + |_, a| Ok(a.value().clone().not_latent()) + ); + 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("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()) + ); + 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?)?"#)?, + |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_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')?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')?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')?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')?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')?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')?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 (?: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 (?: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 (?: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", + 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_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_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')?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')?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')?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')?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')?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')?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 (?: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 (?: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 (?: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", + 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_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()) + ); + 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) + ); + 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::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)), + |_, 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("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) + ); + // 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_4("de à (interval)", + b.reg(r#"(?:[aà] partir )?d['eu]"#)?, + datetime_check!(|datetime: &DatetimeValue| !form!(Form::PartOfDay(_))(datetime)), + b.reg(r#"(?:jusqu')?(?:au|[aà])"#)?, + datetime_check!(|datetime: &DatetimeValue| !form!(Form::PartOfDay(_))(datetime)), + |_, a, _, b| a.value().smart_span_to(b.value(), false) + ); + 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)", + 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("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), + |_, 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])?|depuis"#)?, + 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), + |_, 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()) + ); + 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_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()) + ); + 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#"trimestres?"#)?, + |_| 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..73e5a76a --- /dev/null +++ b/grammar/fr/src/rules_duration.rs @@ -0,0 +1,142 @@ +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("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(()) +} + diff --git a/grammar/fr/src/rules_number.rs b/grammar/fr/src/rules_number.rs new file mode 100644 index 00000000..6cb50604 --- /dev/null +++ b/grammar/fr/src/rules_number.rs @@ -0,0 +1,810 @@ +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(2, 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_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/training.rs b/grammar/fr/src/training.rs index 51adc39d..251e2c00 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", "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"); example!(v, check_moment!(c, [2013, 2, 12, 12]), "midi", "aujourd'hui à midi"); @@ -150,12 +154,18 @@ 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"); + 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"); @@ -170,11 +180,14 @@ 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"); 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"); @@ -225,10 +238,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"); @@ -250,6 +263,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"); 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)