1use itertools::Itertools;
2use mlua::{FromLua, IntoLuaMulti, Lua, LuaSerdeExt, UserData, Value};
3use std::{cmp::Ordering, collections::HashMap, marker::PhantomData};
4use strum::IntoEnumIterator;
5use strum_macros::EnumIter;
6use thiserror::Error;
7
8use serde::{
9 de::{self, DeserializeOwned},
10 Deserialize, Deserializer,
11};
12use serde_enum_str::{Deserialize_enum_str, Serialize_enum_str};
13
14use super::{DisplayAsLuaKV, DisplayLuaKV, DisplayLuaValue};
15
16#[derive(Deserialize_enum_str, Serialize_enum_str, PartialEq, Eq, Hash, Debug, Clone, EnumIter)]
19#[serde(rename_all = "lowercase")]
20#[strum(serialize_all = "lowercase")]
21pub enum PlatformIdentifier {
22 Unix,
24 Windows,
25 Win32,
26 Cygwin,
27 MacOSX,
28 Linux,
29 FreeBSD,
30 #[serde(other)]
31 Unknown(String),
32}
33
34impl Default for PlatformIdentifier {
35 fn default() -> Self {
36 target_identifier()
37 }
38}
39
40impl PartialOrd for PlatformIdentifier {
42 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
43 match (self, other) {
44 (PlatformIdentifier::Unix, PlatformIdentifier::Cygwin) => Some(Ordering::Less),
45 (PlatformIdentifier::Unix, PlatformIdentifier::MacOSX) => Some(Ordering::Less),
46 (PlatformIdentifier::Unix, PlatformIdentifier::Linux) => Some(Ordering::Less),
47 (PlatformIdentifier::Unix, PlatformIdentifier::FreeBSD) => Some(Ordering::Less),
48 (PlatformIdentifier::Windows, PlatformIdentifier::Win32) => Some(Ordering::Greater),
49 (PlatformIdentifier::Win32, PlatformIdentifier::Windows) => Some(Ordering::Less),
50 (PlatformIdentifier::Cygwin, PlatformIdentifier::Unix) => Some(Ordering::Greater),
51 (PlatformIdentifier::MacOSX, PlatformIdentifier::Unix) => Some(Ordering::Greater),
52 (PlatformIdentifier::Linux, PlatformIdentifier::Unix) => Some(Ordering::Greater),
53 (PlatformIdentifier::FreeBSD, PlatformIdentifier::Unix) => Some(Ordering::Greater),
54 _ if self == other => Some(Ordering::Equal),
55 _ => None,
56 }
57 }
58}
59
60impl FromLua for PlatformIdentifier {
61 fn from_lua(value: Value, lua: &Lua) -> mlua::Result<Self> {
62 let string = String::from_lua(value, lua)?;
63 Ok(string
64 .parse()
65 .unwrap_or(PlatformIdentifier::Unknown(string)))
66 }
67}
68
69fn target_identifier() -> PlatformIdentifier {
76 if cfg!(target_env = "msvc") {
77 PlatformIdentifier::Windows
78 } else if cfg!(target_os = "linux") {
79 PlatformIdentifier::Linux
80 } else if cfg!(target_os = "macos") || cfg!(target_vendor = "apple") {
81 PlatformIdentifier::MacOSX
82 } else if cfg!(target_os = "freebsd") {
83 PlatformIdentifier::FreeBSD
84 } else if which::which("cygpath").is_ok() {
85 PlatformIdentifier::Cygwin
86 } else {
87 PlatformIdentifier::Unix
88 }
89}
90
91impl PlatformIdentifier {
92 pub fn get_subsets(&self) -> Vec<Self> {
95 PlatformIdentifier::iter()
96 .filter(|identifier| identifier.is_subset_of(self))
97 .collect()
98 }
99
100 pub fn get_extended_platforms(&self) -> Vec<Self> {
103 PlatformIdentifier::iter()
104 .filter(|identifier| identifier.is_extension_of(self))
105 .collect()
106 }
107
108 fn is_subset_of(&self, other: &PlatformIdentifier) -> bool {
110 self.partial_cmp(other) == Some(Ordering::Less)
111 }
112
113 fn is_extension_of(&self, other: &PlatformIdentifier) -> bool {
115 self.partial_cmp(other) == Some(Ordering::Greater)
116 }
117}
118
119#[derive(Clone, Debug, PartialEq)]
120pub struct PlatformSupport {
121 platform_map: HashMap<PlatformIdentifier, bool>,
123}
124
125impl Default for PlatformSupport {
126 fn default() -> Self {
127 Self {
128 platform_map: PlatformIdentifier::iter()
129 .filter(|identifier| !matches!(identifier, PlatformIdentifier::Unknown(_)))
130 .map(|identifier| (identifier, true))
131 .collect(),
132 }
133 }
134}
135
136impl UserData for PlatformSupport {
137 fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
138 methods.add_method("is_supported", |_, this, platform: PlatformIdentifier| {
139 Ok(this.is_supported(&platform))
140 });
141 }
142}
143
144impl<'de> Deserialize<'de> for PlatformSupport {
145 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
146 where
147 D: Deserializer<'de>,
148 {
149 let platforms: Vec<String> = Vec::deserialize(deserializer)?;
150 Self::parse(&platforms).map_err(de::Error::custom)
151 }
152}
153
154impl DisplayAsLuaKV for PlatformSupport {
155 fn display_lua(&self) -> DisplayLuaKV {
156 DisplayLuaKV {
157 key: "supported_platforms".to_string(),
158 value: DisplayLuaValue::List(
159 self.platforms()
160 .iter()
161 .map(|(platform, supported)| {
162 DisplayLuaValue::String(format!(
163 "{}{}",
164 if *supported { "" } else { "!" },
165 platform,
166 ))
167 })
168 .collect(),
169 ),
170 }
171 }
172}
173
174#[derive(Error, Debug)]
175pub enum PlatformValidationError {
176 #[error("error when parsing platform identifier: {0}")]
177 ParseError(String),
178
179 #[error("conflicting supported platform entries")]
180 ConflictingEntries,
181}
182
183impl PlatformSupport {
184 fn validate_platforms(
185 platforms: &[String],
186 ) -> Result<HashMap<PlatformIdentifier, bool>, PlatformValidationError> {
187 platforms
188 .iter()
189 .try_fold(HashMap::new(), |mut platforms, platform| {
190 let (is_positive_assertion, platform) = platform
194 .strip_prefix('!')
195 .map(|str| (false, str))
196 .unwrap_or((true, platform));
197
198 let platform_identifier = platform
199 .parse::<PlatformIdentifier>()
200 .map_err(|err| PlatformValidationError::ParseError(err.to_string()))?;
201
202 if platforms
206 .get(&platform_identifier)
207 .unwrap_or(&is_positive_assertion)
208 != &is_positive_assertion
209 {
210 return Err(PlatformValidationError::ConflictingEntries);
211 }
212
213 platforms.insert(platform_identifier.clone(), is_positive_assertion);
214
215 let subset_or_extended_platforms = if is_positive_assertion {
216 platform_identifier.get_extended_platforms()
217 } else {
218 platform_identifier.get_subsets()
219 };
220
221 for sub_platform in subset_or_extended_platforms {
222 if platforms
223 .get(&sub_platform)
224 .unwrap_or(&is_positive_assertion)
225 != &is_positive_assertion
226 {
227 return Err(PlatformValidationError::ConflictingEntries);
229 }
230
231 platforms.insert(sub_platform, is_positive_assertion);
232 }
233
234 Ok(platforms)
235 })
236 }
237
238 pub fn parse(platforms: &[String]) -> Result<Self, PlatformValidationError> {
239 match platforms {
243 [] => Ok(Self::default()),
244 platforms if platforms.iter().any(|platform| platform.starts_with('!')) => {
245 let mut platform_map = Self::validate_platforms(platforms)?;
246
247 for identifier in PlatformIdentifier::iter() {
250 if !matches!(identifier, PlatformIdentifier::Unknown(_)) {
251 platform_map.entry(identifier).or_insert(true);
252 }
253 }
254
255 Ok(Self { platform_map })
256 }
257 platforms => Ok(Self {
259 platform_map: Self::validate_platforms(platforms)?,
260 }),
261 }
262 }
263
264 pub fn is_supported(&self, platform: &PlatformIdentifier) -> bool {
265 self.platform_map.get(platform).cloned().unwrap_or(false)
266 }
267
268 pub(crate) fn platforms(&self) -> &HashMap<PlatformIdentifier, bool> {
269 &self.platform_map
270 }
271}
272
273pub trait PartialOverride: Sized {
274 type Err: std::error::Error;
275
276 fn apply_overrides(&self, override_val: &Self) -> Result<Self, Self::Err>;
277}
278
279pub trait PlatformOverridable: PartialOverride {
280 type Err: std::error::Error;
281
282 fn on_nil<T>() -> Result<PerPlatform<T>, <Self as PlatformOverridable>::Err>
283 where
284 T: PlatformOverridable,
285 T: Default;
286}
287
288pub trait FromPlatformOverridable<T: PlatformOverridable, G: FromPlatformOverridable<T, G>> {
289 type Err: std::error::Error;
290
291 fn from_platform_overridable(internal: T) -> Result<G, Self::Err>;
292}
293
294#[derive(Clone, Debug, PartialEq)]
296pub struct PerPlatform<T> {
297 pub(crate) default: T,
299 pub(crate) per_platform: HashMap<PlatformIdentifier, T>,
301}
302
303impl<T> PerPlatform<T> {
304 pub(crate) fn new(default: T) -> Self {
305 Self {
306 default,
307 per_platform: HashMap::default(),
308 }
309 }
310
311 pub fn current_platform(&self) -> &T {
314 self.for_platform_identifier(&target_identifier())
315 }
316
317 fn for_platform_identifier(&self, identifier: &PlatformIdentifier) -> &T {
318 self.get(identifier)
319 }
320
321 pub fn get(&self, platform: &PlatformIdentifier) -> &T {
322 self.per_platform.get(platform).unwrap_or(
323 platform
324 .get_subsets()
325 .into_iter()
326 .sorted_by(|a, b| b.partial_cmp(a).unwrap_or(Ordering::Equal))
330 .find(|identifier| self.per_platform.contains_key(identifier))
331 .and_then(|identifier| self.per_platform.get(&identifier))
332 .unwrap_or(&self.default),
333 )
334 }
335
336 pub(crate) fn map<U, F>(&self, cb: F) -> PerPlatform<U>
337 where
338 F: Fn(&T) -> U,
339 {
340 PerPlatform {
341 default: cb(&self.default),
342 per_platform: self
343 .per_platform
344 .iter()
345 .map(|(identifier, value)| (identifier.clone(), cb(value)))
346 .collect(),
347 }
348 }
349}
350
351impl<U, E> PerPlatform<Result<U, E>>
352where
353 E: std::error::Error,
354{
355 pub fn transpose(self) -> Result<PerPlatform<U>, E> {
356 Ok(PerPlatform {
357 default: self.default?,
358 per_platform: self
359 .per_platform
360 .into_iter()
361 .map(|(identifier, value)| Ok((identifier, value?)))
362 .try_collect()?,
363 })
364 }
365}
366
367impl<T: Default> Default for PerPlatform<T> {
368 fn default() -> Self {
369 Self {
370 default: T::default(),
371 per_platform: HashMap::default(),
372 }
373 }
374}
375
376impl<'de, T> Deserialize<'de> for PerPlatform<T>
377where
378 T: Deserialize<'de>,
379 T: Clone,
380 T: PartialOverride,
381{
382 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
383 where
384 D: Deserializer<'de>,
385 {
386 let mut map = toml::map::Map::deserialize(deserializer)?;
387
388 let mut per_platform: HashMap<PlatformIdentifier, T> = map
389 .remove("platforms")
390 .map_or(Ok(HashMap::default()), |platforms| platforms.try_into())
391 .map_err(serde::de::Error::custom)?;
392
393 let default: T = map.try_into().map_err(serde::de::Error::custom)?;
394
395 apply_per_platform_overrides(&mut per_platform, &default)
396 .map_err(serde::de::Error::custom)?;
397
398 Ok(PerPlatform {
399 default,
400 per_platform,
401 })
402 }
403}
404
405impl<T> FromLua for PerPlatform<T>
406where
407 T: PlatformOverridable,
408 T: PartialOverride,
409 T: DeserializeOwned,
410 T: Default,
411 T: Clone,
412{
413 fn from_lua(value: Value, lua: &Lua) -> mlua::Result<Self> {
414 match &value {
415 list @ Value::Table(tbl) => {
416 let mut per_platform = match tbl.get("platforms")? {
417 val @ Value::Table(_) => Ok(lua.from_value(val)?),
418 Value::Nil => Ok(HashMap::default()),
419 val => Err(mlua::Error::DeserializeError(format!(
420 "Expected platforms to be a table or nil, but got {}",
421 val.type_name()
422 ))),
423 }?;
424 let _ = tbl.raw_remove("platforms");
425 let default = lua.from_value(list.to_owned())?;
426 apply_per_platform_overrides(&mut per_platform, &default).map_err(
427 |err: <T as PartialOverride>::Err| {
428 mlua::Error::DeserializeError(err.to_string())
429 },
430 )?;
431 Ok(PerPlatform {
432 default,
433 per_platform,
434 })
435 }
436 Value::Nil => T::on_nil().map_err(|err| mlua::Error::DeserializeError(err.to_string())),
437 val => Err(mlua::Error::DeserializeError(format!(
438 "Expected rockspec external dependencies to be a table or nil, but got {}",
439 val.type_name()
440 ))),
441 }
442 }
443}
444
445impl<T> UserData for PerPlatform<T>
446where
447 T: IntoLuaMulti + Clone,
448{
449 fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
450 methods.add_method("get", |_, this, platform: PlatformIdentifier| {
455 Ok(this.get(&platform).clone())
456 });
457 }
458}
459
460pub struct PerPlatformWrapper<T, G> {
463 pub un_per_platform: PerPlatform<T>,
464 phantom: PhantomData<G>,
465}
466
467impl<T, G> FromLua for PerPlatformWrapper<T, G>
468where
469 T: FromPlatformOverridable<G, T, Err: ToString>,
470 G: PlatformOverridable<Err: ToString>,
471 G: DeserializeOwned,
472 G: Default,
473 G: Clone,
474{
475 fn from_lua(value: Value, lua: &Lua) -> mlua::Result<Self> {
476 let internal = PerPlatform::from_lua(value, lua)?;
477 let per_platform: HashMap<_, _> = internal
478 .per_platform
479 .into_iter()
480 .map(|(platform, internal_override)| {
481 let override_spec = T::from_platform_overridable(internal_override)
482 .map_err(|err| mlua::Error::DeserializeError(err.to_string()))?;
483
484 Ok((platform, override_spec))
485 })
486 .try_collect::<_, _, mlua::Error>()?;
487 let un_per_platform = PerPlatform {
488 default: T::from_platform_overridable(internal.default)
489 .map_err(|err| mlua::Error::DeserializeError(err.to_string()))?,
490 per_platform,
491 };
492 Ok(PerPlatformWrapper {
493 un_per_platform,
494 phantom: PhantomData,
495 })
496 }
497}
498
499impl<'de, T, G> Deserialize<'de> for PerPlatformWrapper<T, G>
500where
501 T: FromPlatformOverridable<G, T, Err: ToString>,
502 G: PlatformOverridable<Err: ToString>,
503 G: DeserializeOwned,
504 G: Default,
505 G: Clone,
506{
507 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
508 where
509 D: Deserializer<'de>,
510 {
511 let internal = PerPlatform::deserialize(deserializer)?;
512 let per_platform: HashMap<_, _> = internal
513 .per_platform
514 .into_iter()
515 .map(|(platform, internal_override)| {
516 let override_spec = T::from_platform_overridable(internal_override)
517 .map_err(serde::de::Error::custom)?;
518
519 Ok((platform, override_spec))
520 })
521 .try_collect::<_, _, D::Error>()?;
522 let un_per_platform = PerPlatform {
523 default: T::from_platform_overridable(internal.default)
524 .map_err(serde::de::Error::custom)?,
525 per_platform,
526 };
527 Ok(PerPlatformWrapper {
528 un_per_platform,
529 phantom: PhantomData,
530 })
531 }
532}
533
534fn apply_per_platform_overrides<T>(
535 per_platform: &mut HashMap<PlatformIdentifier, T>,
536 base: &T,
537) -> Result<(), T::Err>
538where
539 T: PartialOverride,
540 T: Clone,
541{
542 let per_platform_raw = per_platform.clone();
543 for (platform, overrides) in per_platform.clone() {
544 let overridden = base.apply_overrides(&overrides)?;
546 per_platform.insert(platform, overridden);
547 }
548 for (platform, overrides) in per_platform_raw {
549 for extended_platform in &platform.get_extended_platforms() {
551 if let Some(extended_overrides) = per_platform.get(extended_platform) {
552 per_platform.insert(
553 extended_platform.to_owned(),
554 extended_overrides.apply_overrides(&overrides)?,
555 );
556 }
557 }
558 }
559 Ok(())
560}
561
562#[cfg(test)]
563mod tests {
564
565 use super::*;
566 use proptest::prelude::*;
567
568 fn platform_identifier_strategy() -> impl Strategy<Value = PlatformIdentifier> {
569 prop_oneof![
570 Just(PlatformIdentifier::Unix),
571 Just(PlatformIdentifier::Windows),
572 Just(PlatformIdentifier::Win32),
573 Just(PlatformIdentifier::Cygwin),
574 Just(PlatformIdentifier::MacOSX),
575 Just(PlatformIdentifier::Linux),
576 Just(PlatformIdentifier::FreeBSD),
577 ]
578 }
579
580 #[tokio::test]
581 async fn sort_platform_identifier_more_specific_last() {
582 let mut platforms = vec![
583 PlatformIdentifier::Cygwin,
584 PlatformIdentifier::Linux,
585 PlatformIdentifier::Unix,
586 ];
587 platforms.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
588 assert_eq!(
589 platforms,
590 vec![
591 PlatformIdentifier::Unix,
592 PlatformIdentifier::Cygwin,
593 PlatformIdentifier::Linux
594 ]
595 );
596 let mut platforms = vec![PlatformIdentifier::Windows, PlatformIdentifier::Win32];
597 platforms.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
598 assert_eq!(
599 platforms,
600 vec![PlatformIdentifier::Win32, PlatformIdentifier::Windows]
601 )
602 }
603
604 #[tokio::test]
605 async fn test_is_subset_of() {
606 assert!(PlatformIdentifier::Unix.is_subset_of(&PlatformIdentifier::Linux));
607 assert!(PlatformIdentifier::Unix.is_subset_of(&PlatformIdentifier::MacOSX));
608 assert!(!PlatformIdentifier::Linux.is_subset_of(&PlatformIdentifier::Unix));
609 }
610
611 #[tokio::test]
612 async fn test_is_extension_of() {
613 assert!(PlatformIdentifier::Linux.is_extension_of(&PlatformIdentifier::Unix));
614 assert!(PlatformIdentifier::MacOSX.is_extension_of(&PlatformIdentifier::Unix));
615 assert!(!PlatformIdentifier::Unix.is_extension_of(&PlatformIdentifier::Linux));
616 }
617
618 #[tokio::test]
619 async fn per_platform() {
620 let foo = PerPlatform {
621 default: "default",
622 per_platform: vec![
623 (PlatformIdentifier::Unix, "unix"),
624 (PlatformIdentifier::FreeBSD, "freebsd"),
625 (PlatformIdentifier::Cygwin, "cygwin"),
626 (PlatformIdentifier::Linux, "linux"),
627 ]
628 .into_iter()
629 .collect(),
630 };
631 assert_eq!(*foo.get(&PlatformIdentifier::MacOSX), "unix");
632 assert_eq!(*foo.get(&PlatformIdentifier::Linux), "linux");
633 assert_eq!(*foo.get(&PlatformIdentifier::FreeBSD), "freebsd");
634 assert_eq!(*foo.get(&PlatformIdentifier::Cygwin), "cygwin");
635 assert_eq!(*foo.get(&PlatformIdentifier::Windows), "default");
636 }
637
638 #[cfg(target_os = "linux")]
639 #[tokio::test]
640 async fn test_target_identifier() {
641 run_test_target_identifier(PlatformIdentifier::Linux)
642 }
643
644 #[cfg(target_os = "macos")]
645 #[tokio::test]
646 async fn test_target_identifier() {
647 run_test_target_identifier(PlatformIdentifier::MacOSX)
648 }
649
650 #[cfg(target_env = "msvc")]
651 #[tokio::test]
652 async fn test_target_identifier() {
653 run_test_target_identifier(PlatformIdentifier::Windows)
654 }
655
656 fn run_test_target_identifier(expected: PlatformIdentifier) {
657 assert_eq!(expected, target_identifier());
658 }
659
660 proptest! {
661 #[test]
662 fn supported_platforms(identifier in platform_identifier_strategy()) {
663 let identifier_str = identifier.to_string();
664 let platforms = vec![identifier_str];
665 let platform_support = PlatformSupport::parse(&platforms).unwrap();
666 prop_assert!(platform_support.is_supported(&identifier))
667 }
668
669 #[test]
670 fn unsupported_platforms_only(unsupported in platform_identifier_strategy(), supported in platform_identifier_strategy()) {
671 if supported == unsupported
672 || unsupported.is_extension_of(&supported) {
673 return Ok(());
674 }
675 let identifier_str = format!("!{}", unsupported);
676 let platforms = vec![identifier_str];
677 let platform_support = PlatformSupport::parse(&platforms).unwrap();
678 prop_assert!(!platform_support.is_supported(&unsupported));
679 prop_assert!(platform_support.is_supported(&supported))
680 }
681
682 #[test]
683 fn supported_and_unsupported_platforms(unsupported in platform_identifier_strategy(), unspecified in platform_identifier_strategy()) {
684 if unspecified == unsupported
685 || unsupported.is_extension_of(&unspecified) {
686 return Ok(());
687 }
688 let supported_str = unspecified.to_string();
689 let unsupported_str = format!("!{}", unsupported);
690 let platforms = vec![supported_str, unsupported_str];
691 let platform_support = PlatformSupport::parse(&platforms).unwrap();
692 prop_assert!(platform_support.is_supported(&unspecified));
693 prop_assert!(!platform_support.is_supported(&unsupported));
694 }
695
696 #[test]
697 fn all_platforms_supported_if_none_are_specified(identifier in platform_identifier_strategy()) {
698 let platforms = vec![];
699 let platform_support = PlatformSupport::parse(&platforms).unwrap();
700 prop_assert!(platform_support.is_supported(&identifier))
701 }
702
703 #[test]
704 fn conflicting_platforms(identifier in platform_identifier_strategy()) {
705 let identifier_str = identifier.to_string();
706 let identifier_str_negated = format!("!{}", identifier);
707 let platforms = vec![identifier_str, identifier_str_negated];
708 let _ = PlatformSupport::parse(&platforms).unwrap_err();
709 }
710
711 #[test]
712 fn extended_platforms_supported_if_supported(identifier in platform_identifier_strategy()) {
713 let identifier_str = identifier.to_string();
714 let platforms = vec![identifier_str];
715 let platform_support = PlatformSupport::parse(&platforms).unwrap();
716 for identifier in identifier.get_extended_platforms() {
717 prop_assert!(platform_support.is_supported(&identifier))
718 }
719 }
720
721 #[test]
722 fn sub_platforms_unsupported_if_unsupported(identifier in platform_identifier_strategy()) {
723 let identifier_str = format!("!{}", identifier);
724 let platforms = vec![identifier_str];
725 let platform_support = PlatformSupport::parse(&platforms).unwrap();
726 for identifier in identifier.get_subsets() {
727 prop_assert!(!platform_support.is_supported(&identifier))
728 }
729 }
730
731 #[test]
732 fn conflicting_extended_platform_definitions(identifier in platform_identifier_strategy()) {
733 let extended_platforms = identifier.get_extended_platforms();
734 if extended_platforms.is_empty() {
735 return Ok(());
736 }
737 let supported_str = identifier.to_string();
738 let mut platforms: Vec<String> = extended_platforms.into_iter().map(|ident| format!("!{}", ident)).collect();
739 platforms.push(supported_str);
740 let _ = PlatformSupport::parse(&platforms).unwrap_err();
741 }
742 }
743}