1use std::{
5 borrow::Cow,
6 error::Error,
7 fmt::{self, Write},
8 hash::{Hash, Hasher},
9 mem, slice,
10 vec::IntoIter,
11};
12
13use chrono::{NaiveDate, NaiveDateTime, NaiveTime, TimeDelta as ChronoTimeDelta};
14use num_bigint::BigInt;
15use num_traits::{ToPrimitive, Zero};
16
17use crate::{
18 builtins::BuiltinsFunctions,
19 exceptions::ExcType,
20 file_mode::FileMode,
21 format::{FormatFloat, StringRepr, bytes_repr_fmt, format_offset_timedelta_repr, string_repr_fmt},
22 resource::ResourceError,
23};
24
25#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
54pub enum MontyObject {
55 Ellipsis,
57 NotImplemented,
59 None,
61 Bool(bool),
63 Int(i64),
65 BigInt(BigInt),
67 Float(f64),
69 String(String),
71 Bytes(Vec<u8>),
73 List(Vec<Self>),
75 Tuple(Vec<Self>),
77 NamedTuple {
83 type_name: String,
85 field_names: Vec<String>,
87 values: Vec<Self>,
89 },
90 Dict(DictPairs),
92 Set(Vec<Self>),
94 FrozenSet(Vec<Self>),
96 Date(MontyDate),
98 DateTime(MontyDateTime),
100 TimeDelta(MontyTimeDelta),
102 TimeZone(MontyTimeZone),
104 Exception {
106 exc_type: ExcType,
108 arg: Option<String>,
110 },
111 Type(MontyType),
115 BuiltinFunction(BuiltinsFunctions),
116 Path(String),
120 FileHandle(MontyFileHandle),
122 Dataclass {
128 name: String,
130 type_id: u64,
132 field_names: Vec<String>,
134 attrs: DictPairs,
136 frozen: bool,
138 },
139 Function {
144 name: String,
146 docstring: Option<String>,
148 },
149 Repr(String),
155 Cycle(usize, String),
166}
167
168impl fmt::Display for MontyObject {
169 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
170 match self {
171 Self::String(s) => f.write_str(s),
172 Self::Cycle(_, placeholder) => f.write_str(placeholder),
173 Self::Type(t) => write!(f, "<class '{t}'>"),
174 Self::Function { name, .. } => write!(f, "<function '{name}' external>"),
175 _ => self.repr_fmt(f),
176 }
177 }
178}
179
180impl MontyObject {
181 pub fn dict(dict: impl Into<DictPairs>) -> Self {
183 Self::Dict(dict.into())
184 }
185
186 #[must_use]
193 pub fn builtin_function_from_name(name: &str) -> Option<Self> {
194 name.parse::<BuiltinsFunctions>().ok().map(Self::BuiltinFunction)
195 }
196
197 pub fn host_size(&self) -> usize {
204 const BASE: usize = size_of::<MontyObject>();
207 const STR_OVERHEAD: usize = size_of::<String>();
209
210 let names_len = |names: &[String]| -> usize { names.iter().map(|s| STR_OVERHEAD + s.len()).sum() };
211
212 let payload = match self {
213 Self::String(s) | Self::Path(s) | Self::Repr(s) => s.len(),
214 Self::Cycle(_, placeholder) => placeholder.len(),
215 Self::Bytes(b) => b.len(),
216 Self::BigInt(bi) => usize::try_from(bi.bits().div_ceil(8)).unwrap_or(usize::MAX),
219 Self::Exception { arg, .. } => arg.as_ref().map_or(0, String::len),
220 Self::FileHandle(fh) => fh.path.len(),
221 Self::Function { name, docstring } => name.len() + docstring.as_ref().map_or(0, String::len),
222 Self::NamedTuple {
223 type_name, field_names, ..
224 } => type_name.len() + names_len(field_names),
225 Self::Dataclass { name, field_names, .. } => name.len() + names_len(field_names),
226 Self::Type(MontyType::Instance(name)) => name.len(),
230 _ => 0,
231 };
232 BASE + payload
233 }
234
235 #[must_use]
240 pub fn py_repr(&self) -> String {
241 let mut s = String::new();
242 self.repr_fmt(&mut s).expect("Unable to format repr display value");
243 s
244 }
245
246 fn repr_fmt(&self, f: &mut impl Write) -> fmt::Result {
247 match self {
248 Self::Ellipsis => f.write_str("Ellipsis"),
249 Self::NotImplemented => f.write_str("NotImplemented"),
250 Self::None => f.write_str("None"),
251 Self::Bool(true) => f.write_str("True"),
252 Self::Bool(false) => f.write_str("False"),
253 Self::Int(v) => write!(f, "{v}"),
254 Self::BigInt(v) => write!(f, "{v}"),
255 Self::Float(v) => write!(f, "{}", FormatFloat(*v)),
256 Self::String(s) => string_repr_fmt(s, f),
257 Self::Bytes(b) => bytes_repr_fmt(b, f),
258 Self::List(l) => {
259 f.write_char('[')?;
260 let mut iter = l.iter();
261 if let Some(first) = iter.next() {
262 first.repr_fmt(f)?;
263 for item in iter {
264 f.write_str(", ")?;
265 item.repr_fmt(f)?;
266 }
267 }
268 f.write_char(']')
269 }
270 Self::Tuple(t) => {
271 f.write_char('(')?;
272 let mut iter = t.iter();
273 if let Some(first) = iter.next() {
274 first.repr_fmt(f)?;
275 for item in iter {
276 f.write_str(", ")?;
277 item.repr_fmt(f)?;
278 }
279 }
280 f.write_char(')')
281 }
282 Self::NamedTuple {
283 type_name,
284 field_names,
285 values,
286 } => {
287 f.write_str(type_name)?;
289 f.write_char('(')?;
290 let mut first = true;
291 for (name, value) in field_names.iter().zip(values) {
292 if !first {
293 f.write_str(", ")?;
294 }
295 first = false;
296 f.write_str(name)?;
297 f.write_char('=')?;
298 value.repr_fmt(f)?;
299 }
300 f.write_char(')')
301 }
302 Self::Dict(d) => {
303 f.write_char('{')?;
304 let mut iter = d.iter();
305 if let Some((k, v)) = iter.next() {
306 k.repr_fmt(f)?;
307 f.write_str(": ")?;
308 v.repr_fmt(f)?;
309 for (k, v) in iter {
310 f.write_str(", ")?;
311 k.repr_fmt(f)?;
312 f.write_str(": ")?;
313 v.repr_fmt(f)?;
314 }
315 }
316 f.write_char('}')
317 }
318 Self::Set(s) => {
319 if s.is_empty() {
320 f.write_str("set()")
321 } else {
322 f.write_char('{')?;
323 let mut iter = s.iter();
324 if let Some(first) = iter.next() {
325 first.repr_fmt(f)?;
326 for item in iter {
327 f.write_str(", ")?;
328 item.repr_fmt(f)?;
329 }
330 }
331 f.write_char('}')
332 }
333 }
334 Self::FrozenSet(fs) => {
335 f.write_str("frozenset(")?;
336 if !fs.is_empty() {
337 f.write_char('{')?;
338 let mut iter = fs.iter();
339 if let Some(first) = iter.next() {
340 first.repr_fmt(f)?;
341 for item in iter {
342 f.write_str(", ")?;
343 item.repr_fmt(f)?;
344 }
345 }
346 f.write_char('}')?;
347 }
348 f.write_char(')')
349 }
350 Self::Date(date) => write!(f, "datetime.date({}, {}, {})", date.year, date.month, date.day),
351 Self::DateTime(datetime) => {
352 write!(
353 f,
354 "datetime.datetime({}, {}, {}, {}, {}",
355 datetime.year, datetime.month, datetime.day, datetime.hour, datetime.minute
356 )?;
357 if datetime.second != 0 || datetime.microsecond != 0 {
358 write!(f, ", {}", datetime.second)?;
359 }
360 if datetime.microsecond != 0 {
361 write!(f, ", {}", datetime.microsecond)?;
362 }
363 if let Some(offset) = datetime.offset_seconds {
364 if offset == 0 && datetime.timezone_name.is_none() {
365 f.write_str(", tzinfo=datetime.timezone.utc")?;
366 } else {
367 let timedelta_repr = format_offset_timedelta_repr(offset);
368 write!(f, ", tzinfo=datetime.timezone({timedelta_repr}")?;
369 if let Some(name) = &datetime.timezone_name {
370 write!(f, ", {}", StringRepr(name))?;
371 }
372 f.write_char(')')?;
373 }
374 }
375 f.write_char(')')
376 }
377 Self::TimeDelta(delta) => {
378 if delta.days == 0 && delta.seconds == 0 && delta.microseconds == 0 {
379 return f.write_str("datetime.timedelta(0)");
380 }
381 f.write_str("datetime.timedelta(")?;
382 let mut first = true;
383 if delta.days != 0 {
384 write!(f, "days={}", delta.days)?;
385 first = false;
386 }
387 if delta.seconds != 0 {
388 if !first {
389 f.write_str(", ")?;
390 }
391 write!(f, "seconds={}", delta.seconds)?;
392 first = false;
393 }
394 if delta.microseconds != 0 {
395 if !first {
396 f.write_str(", ")?;
397 }
398 write!(f, "microseconds={}", delta.microseconds)?;
399 }
400 f.write_char(')')
401 }
402 Self::TimeZone(tz) => {
403 if tz.offset_seconds == 0 && tz.name.is_none() {
404 return f.write_str("datetime.timezone.utc");
405 }
406 let timedelta_repr = format_offset_timedelta_repr(tz.offset_seconds);
407 write!(f, "datetime.timezone({timedelta_repr}")?;
408 if let Some(name) = &tz.name {
409 write!(f, ", {}", StringRepr(name))?;
410 }
411 f.write_char(')')
412 }
413 Self::Exception { exc_type, arg } => {
414 let type_str: &'static str = exc_type.into();
415 write!(f, "{type_str}(")?;
416
417 if let Some(arg) = &arg {
418 string_repr_fmt(arg, f)?;
419 }
420 f.write_char(')')
421 }
422 Self::Dataclass {
423 name,
424 field_names,
425 attrs,
426 ..
427 } => {
428 f.write_str(name)?;
431 f.write_char('(')?;
432 let mut first = true;
433 for field_name in field_names {
434 if !first {
435 f.write_str(", ")?;
436 }
437 first = false;
438 f.write_str(field_name)?;
439 f.write_char('=')?;
440 let key = Self::String(field_name.clone());
442 if let Some(value) = attrs.iter().find(|(k, _)| k == &key).map(|(_, v)| v) {
443 value.repr_fmt(f)?;
444 } else {
445 f.write_str("<?>")?;
446 }
447 }
448 f.write_char(')')
449 }
450 Self::Path(p) => write!(f, "PosixPath('{p}')"),
451 Self::FileHandle(handle) => write!(f, "{handle}"),
452 Self::Type(t) => write!(f, "<class '{t}'>"),
453 Self::BuiltinFunction(func) => write!(f, "<built-in function {func}>"),
454 Self::Function { name, .. } => write!(f, "<function '{name}' external>"),
455 Self::Repr(s) => write!(f, "Repr({})", StringRepr(s)),
456 Self::Cycle(_, placeholder) => f.write_str(placeholder),
457 }
458 }
459
460 #[must_use]
470 pub fn is_truthy(&self) -> bool {
471 match self {
472 Self::None => false,
473 Self::Ellipsis | Self::NotImplemented => true,
474 Self::Bool(b) => *b,
475 Self::Int(i) => *i != 0,
476 Self::BigInt(bi) => !bi.is_zero(),
477 Self::Float(f) => *f != 0.0,
478 Self::String(s) => !s.is_empty(),
479 Self::Bytes(b) => !b.is_empty(),
480 Self::List(l) => !l.is_empty(),
481 Self::Tuple(t) => !t.is_empty(),
482 Self::NamedTuple { values, .. } => !values.is_empty(),
483 Self::Dict(d) => !d.is_empty(),
484 Self::Set(s) => !s.is_empty(),
485 Self::FrozenSet(fs) => !fs.is_empty(),
486 Self::Date(_) => true,
487 Self::DateTime(_) => true,
488 Self::TimeDelta(delta) => delta.days != 0 || delta.seconds != 0 || delta.microseconds != 0,
489 Self::TimeZone(_) => true,
490 Self::Exception { .. } => true,
491 Self::Path(_) => true, Self::FileHandle { .. } => true, Self::Dataclass { .. } => true, Self::Type(_) | Self::BuiltinFunction(_) | Self::Function { .. } | Self::Repr(_) | Self::Cycle(_, _) => {
495 true
496 }
497 }
498 }
499
500 #[must_use]
504 pub fn type_name(&self) -> &'static str {
505 match self {
506 Self::None => "NoneType",
507 Self::Ellipsis => "ellipsis",
508 Self::NotImplemented => "NotImplementedType",
509 Self::Bool(_) => "bool",
510 Self::Int(_) | Self::BigInt(_) => "int",
511 Self::Float(_) => "float",
512 Self::String(_) => "str",
513 Self::Bytes(_) => "bytes",
514 Self::List(_) => "list",
515 Self::Tuple(_) => "tuple",
516 Self::NamedTuple { .. } => "namedtuple",
517 Self::Dict(_) => "dict",
518 Self::Set(_) => "set",
519 Self::FrozenSet(_) => "frozenset",
520 Self::Date(_) => "date",
521 Self::DateTime(_) => "datetime",
522 Self::TimeDelta(_) => "timedelta",
523 Self::TimeZone(_) => "timezone",
524 Self::Exception { .. } => "Exception",
525 Self::Path(_) => "PosixPath",
526 Self::FileHandle(handle) => handle.mode.type_name(),
527 Self::Dataclass { .. } => "dataclass",
528 Self::Type(_) => "type",
529 Self::BuiltinFunction(_) => "builtin_function_or_method",
530 Self::Function { .. } => "function",
531 Self::Repr(_) => "repr",
532 Self::Cycle(_, _) => "cycle",
533 }
534 }
535}
536
537impl Hash for MontyObject {
538 fn hash<H: Hasher>(&self, state: &mut H) {
539 match self {
541 Self::Int(_) | Self::BigInt(_) => {
542 mem::discriminant(&Self::Int(0)).hash(state);
544 }
545 _ => mem::discriminant(self).hash(state),
546 }
547
548 match self {
549 Self::Ellipsis | Self::NotImplemented | Self::None => {}
550 Self::Bool(bool) => bool.hash(state),
551 Self::Int(i) => i.hash(state),
552 Self::BigInt(bi) => {
553 if let Ok(i) = i64::try_from(bi) {
555 i.hash(state);
556 } else {
557 bi.to_signed_bytes_le().hash(state);
559 }
560 }
561 Self::Float(f) => f.to_bits().hash(state),
562 Self::String(string) => string.hash(state),
563 Self::Bytes(bytes) => bytes.hash(state),
564 Self::Date(date) => date.hash(state),
565 Self::DateTime(datetime) => datetime.hash(state),
566 Self::TimeDelta(delta) => delta.hash(state),
567 Self::TimeZone(timezone) => timezone.hash(state),
568 Self::Path(path) => path.hash(state),
569 Self::FileHandle(MontyFileHandle { path, mode, position }) => {
570 path.hash(state);
571 mode.as_str().hash(state);
572 position.hash(state);
573 }
574 Self::Type(t) => t.name().hash(state),
575 Self::Cycle(_, _) => panic!("cycle values are not hashable"),
576 _ => panic!("{} python values are not hashable", self.type_name()),
577 }
578 }
579}
580
581impl PartialEq for MontyObject {
582 fn eq(&self, other: &Self) -> bool {
583 match (self, other) {
584 (Self::Ellipsis, Self::Ellipsis) => true,
585 (Self::NotImplemented, Self::NotImplemented) => true,
586 (Self::None, Self::None) => true,
587 (Self::Bool(a), Self::Bool(b)) => a == b,
588 (Self::Int(a), Self::Int(b)) => a == b,
589 (Self::BigInt(a), Self::BigInt(b)) => a == b,
590 (Self::Int(a), Self::BigInt(b)) | (Self::BigInt(b), Self::Int(a)) => b.to_i64() == Some(*a),
592 (Self::Float(a), Self::Float(b)) => a.to_bits() == b.to_bits(),
594 (Self::String(a), Self::String(b)) => a == b,
595 (Self::Bytes(a), Self::Bytes(b)) => a == b,
596 (Self::List(a), Self::List(b)) => a == b,
597 (Self::Tuple(a), Self::Tuple(b)) => a == b,
598 (Self::Date(a), Self::Date(b)) => a == b,
599 (Self::DateTime(a), Self::DateTime(b)) => a == b,
600 (Self::TimeDelta(a), Self::TimeDelta(b)) => a == b,
601 (Self::TimeZone(a), Self::TimeZone(b)) => a == b,
602 (
603 Self::NamedTuple {
604 type_name: a_type,
605 field_names: a_fields,
606 values: a_values,
607 },
608 Self::NamedTuple {
609 type_name: b_type,
610 field_names: b_fields,
611 values: b_values,
612 },
613 ) => a_type == b_type && a_fields == b_fields && a_values == b_values,
614 (Self::NamedTuple { values, .. }, Self::Tuple(t)) | (Self::Tuple(t), Self::NamedTuple { values, .. }) => {
616 values == t
617 }
618 (Self::Dict(a), Self::Dict(b)) => a == b,
619 (Self::Set(a), Self::Set(b)) => a == b,
620 (Self::FrozenSet(a), Self::FrozenSet(b)) => a == b,
621 (
622 Self::Exception {
623 exc_type: a_type,
624 arg: a_arg,
625 },
626 Self::Exception {
627 exc_type: b_type,
628 arg: b_arg,
629 },
630 ) => a_type == b_type && a_arg == b_arg,
631 (
632 Self::Dataclass {
633 name: a_name,
634 type_id: a_type_id,
635 field_names: a_field_names,
636 attrs: a_attrs,
637 frozen: a_frozen,
638 },
639 Self::Dataclass {
640 name: b_name,
641 type_id: b_type_id,
642 field_names: b_field_names,
643 attrs: b_attrs,
644 frozen: b_frozen,
645 },
646 ) => {
647 a_name == b_name
648 && a_type_id == b_type_id
649 && a_field_names == b_field_names
650 && a_attrs == b_attrs
651 && a_frozen == b_frozen
652 }
653 (Self::Path(a), Self::Path(b)) => a == b,
654 (
655 Self::FileHandle(MontyFileHandle {
656 path: a_path,
657 mode: a_mode,
658 position: a_pos,
659 }),
660 Self::FileHandle(MontyFileHandle {
661 path: b_path,
662 mode: b_mode,
663 position: b_pos,
664 }),
665 ) => a_path == b_path && a_mode == b_mode && a_pos == b_pos,
666 (
667 Self::Function {
668 name: a_name,
669 docstring: a_doc,
670 },
671 Self::Function {
672 name: b_name,
673 docstring: b_doc,
674 },
675 ) => a_name == b_name && a_doc == b_doc,
676 (Self::Repr(a), Self::Repr(b)) => a == b,
677 (Self::Cycle(a, _), Self::Cycle(b, _)) => a == b,
678 (Self::Type(a), Self::Type(b)) => a == b,
679 (Self::BuiltinFunction(a), Self::BuiltinFunction(b)) => a == b,
681 _ => false,
682 }
683 }
684}
685
686impl Eq for MontyObject {}
687
688impl AsRef<Self> for MontyObject {
689 fn as_ref(&self) -> &Self {
690 self
691 }
692}
693
694#[derive(
706 Debug,
707 Clone,
708 PartialEq,
709 Eq,
710 serde::Serialize,
711 serde::Deserialize,
712 strum::EnumIter,
713 strum::EnumString,
714 strum::IntoStaticStr,
715)]
716#[strum(serialize_all = "lowercase")]
717pub enum MontyType {
718 Ellipsis,
719 Type,
720 #[strum(serialize = "NoneType")]
721 NoneType,
722 Bool,
723 Int,
724 Float,
725 Range,
726 Slice,
727 Date,
728 #[strum(serialize = "datetime.datetime")]
729 DateTime,
730 TimeDelta,
731 TimeZone,
732 Str,
733 Bytes,
734 List,
735 #[strum(serialize = "collections.deque")]
739 Deque,
740 #[strum(serialize = "list_iterator")]
741 ListIterator,
742 #[strum(serialize = "callable_iterator")]
743 CallableIterator,
744 Tuple,
745 NamedTuple,
746 Dict,
747 #[strum(serialize = "dict_keys")]
748 DictKeys,
749 #[strum(serialize = "dict_items")]
750 DictItems,
751 #[strum(serialize = "dict_values")]
752 DictValues,
753 Set,
754 FrozenSet,
755 Dataclass,
756 #[strum(disabled)]
762 Instance(String),
763 #[strum(disabled)]
769 Exception(ExcType),
770 Function,
771 #[strum(serialize = "builtin_function_or_method")]
772 BuiltinFunction,
773 Cell,
774 Iterator,
775 Coroutine,
776 Module,
777 #[strum(serialize = "_io.TextIOWrapper")]
778 TextIOWrapper,
779 #[strum(serialize = "_io.BufferedReader")]
780 BufferedReader,
781 #[strum(serialize = "_io.BufferedWriter")]
782 BufferedWriter,
783 #[strum(serialize = "_io.BufferedRandom")]
784 BufferedRandom,
785 #[strum(serialize = "typing._SpecialForm")]
786 SpecialForm,
787 #[strum(serialize = "PosixPath")]
788 Path,
789 Property,
790 #[strum(serialize = "re.Pattern")]
791 RePattern,
792 #[strum(serialize = "re.Match")]
793 ReMatch,
794 #[strum(serialize = "tuple_iterator")]
796 TupleIterator,
797 #[strum(serialize = "str_ascii_iterator")]
798 StrAsciiIterator,
799 #[strum(serialize = "str_iterator")]
800 StrIterator,
801 #[strum(serialize = "bytes_iterator")]
802 BytesIterator,
803 #[strum(serialize = "range_iterator")]
804 RangeIterator,
805 #[strum(serialize = "dict_keyiterator")]
806 DictKeyIterator,
807 #[strum(serialize = "dict_itemiterator")]
808 DictItemIterator,
809 #[strum(serialize = "dict_valueiterator")]
810 DictValueIterator,
811 #[strum(serialize = "set_iterator")]
812 SetIterator,
813 #[strum(serialize = "itertools.count")]
814 ItertoolsCount,
815 #[strum(serialize = "itertools.repeat")]
816 ItertoolsRepeat,
817 #[strum(serialize = "Field")]
820 Field,
821 #[strum(serialize = "itertools.pairwise")]
822 ItertoolsPairwise,
823 #[strum(serialize = "itertools.compress")]
824 ItertoolsCompress,
825 #[strum(serialize = "itertools.islice")]
826 ItertoolsIslice,
827 #[strum(serialize = "itertools.chain")]
828 ItertoolsChain,
829 #[strum(serialize = "itertools.cycle")]
830 ItertoolsCycle,
831 #[strum(serialize = "NotImplementedType")]
832 NotImplementedType,
833}
834
835impl fmt::Display for MontyType {
836 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
837 f.write_str(self.name())
838 }
839}
840
841impl MontyType {
842 #[must_use]
845 pub fn name(&self) -> &str {
846 match self {
847 Self::Instance(name) => name,
848 Self::Exception(exc_type) => (*exc_type).into(),
849 other => other.into(),
852 }
853 }
854
855 #[must_use]
867 pub fn from_type_name(name: &str) -> Option<Self> {
868 name.parse::<Self>()
869 .ok()
870 .or_else(|| name.parse::<ExcType>().ok().map(Self::Exception))
871 }
872}
873
874#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
876pub struct MontyDate {
877 pub year: i32,
879 pub month: u8,
881 pub day: u8,
883}
884
885#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
887pub struct MontyDateTime {
888 pub year: i32,
890 pub month: u8,
892 pub day: u8,
894 pub hour: u8,
896 pub minute: u8,
898 pub second: u8,
900 pub microsecond: u32,
902 pub offset_seconds: Option<i32>,
904 pub timezone_name: Option<String>,
908}
909
910#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
912pub struct MontyTimeDelta {
913 pub days: i32,
915 pub seconds: i32,
917 pub microseconds: i32,
919}
920
921#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
923pub struct MontyTimeZone {
924 pub offset_seconds: i32,
926 pub name: Option<String>,
928}
929
930impl PartialEq for MontyDateTime {
931 fn eq(&self, other: &Self) -> bool {
932 let self_aware = self.offset_seconds.is_some();
933 let other_aware = other.offset_seconds.is_some();
934 if self_aware != other_aware {
935 return false;
936 }
937
938 if self_aware {
939 return monty_datetime_utc_micros(self)
940 .zip(monty_datetime_utc_micros(other))
941 .is_some_and(|(lhs, rhs)| lhs == rhs)
942 || monty_datetime_raw_eq(self, other);
943 }
944
945 monty_datetime_local_micros(self)
946 .zip(monty_datetime_local_micros(other))
947 .is_some_and(|(lhs, rhs)| lhs == rhs)
948 || monty_datetime_raw_eq(self, other)
949 }
950}
951
952impl Eq for MontyDateTime {}
953
954impl Hash for MontyDateTime {
955 fn hash<H: Hasher>(&self, state: &mut H) {
956 if self.offset_seconds.is_some()
957 && let Some(utc_micros) = monty_datetime_utc_micros(self)
958 {
959 utc_micros.hash(state);
960 return;
961 }
962 if let Some(local_micros) = monty_datetime_local_micros(self) {
963 local_micros.hash(state);
964 return;
965 }
966
967 self.year.hash(state);
969 self.month.hash(state);
970 self.day.hash(state);
971 self.hour.hash(state);
972 self.minute.hash(state);
973 self.second.hash(state);
974 self.microsecond.hash(state);
975 self.offset_seconds.hash(state);
976 self.timezone_name.hash(state);
977 }
978}
979
980impl PartialEq for MontyTimeZone {
981 fn eq(&self, other: &Self) -> bool {
982 self.offset_seconds == other.offset_seconds
983 }
984}
985
986impl Eq for MontyTimeZone {}
987
988impl Hash for MontyTimeZone {
989 fn hash<H: Hasher>(&self, state: &mut H) {
990 self.offset_seconds.hash(state);
991 }
992}
993
994#[derive(Debug)]
999pub struct ConversionError {
1000 pub expected: &'static str,
1002 pub actual: &'static str,
1004}
1005
1006impl ConversionError {
1007 #[must_use]
1009 pub fn new(expected: &'static str, actual: &'static str) -> Self {
1010 Self { expected, actual }
1011 }
1012}
1013
1014impl fmt::Display for ConversionError {
1015 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1016 write!(f, "expected {}, got {}", self.expected, self.actual)
1017 }
1018}
1019
1020impl Error for ConversionError {}
1021
1022#[derive(Debug, Clone)]
1028pub enum InvalidInputError {
1029 InvalidType(Cow<'static, str>),
1032 Resource(ResourceError),
1034}
1035
1036impl InvalidInputError {
1037 #[must_use]
1039 pub fn invalid_type(msg: impl Into<Cow<'static, str>>) -> Self {
1040 Self::InvalidType(msg.into())
1041 }
1042}
1043
1044impl fmt::Display for InvalidInputError {
1045 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1046 match self {
1047 Self::InvalidType(msg) => write!(f, "{msg}"),
1048 Self::Resource(e) => write!(f, "{e}"),
1049 }
1050 }
1051}
1052
1053impl Error for InvalidInputError {}
1054
1055impl From<ResourceError> for InvalidInputError {
1056 fn from(err: ResourceError) -> Self {
1057 Self::Resource(err)
1058 }
1059}
1060
1061impl TryFrom<&MontyObject> for i64 {
1064 type Error = ConversionError;
1065
1066 fn try_from(value: &MontyObject) -> Result<Self, Self::Error> {
1067 match value {
1068 MontyObject::Int(i) => Ok(*i),
1069 _ => Err(ConversionError::new("int", value.type_name())),
1070 }
1071 }
1072}
1073
1074impl TryFrom<&MontyObject> for f64 {
1078 type Error = ConversionError;
1079
1080 fn try_from(value: &MontyObject) -> Result<Self, Self::Error> {
1081 match value {
1082 MontyObject::Float(f) => Ok(*f),
1083 MontyObject::Int(i) => Ok(*i as Self),
1084 _ => Err(ConversionError::new("float", value.type_name())),
1085 }
1086 }
1087}
1088
1089impl TryFrom<&MontyObject> for String {
1092 type Error = ConversionError;
1093
1094 fn try_from(value: &MontyObject) -> Result<Self, Self::Error> {
1095 if let MontyObject::String(s) = value {
1096 Ok(s.clone())
1097 } else {
1098 Err(ConversionError::new("str", value.type_name()))
1099 }
1100 }
1101}
1102
1103impl TryFrom<&MontyObject> for bool {
1107 type Error = ConversionError;
1108
1109 fn try_from(value: &MontyObject) -> Result<Self, Self::Error> {
1110 match value {
1111 MontyObject::Bool(b) => Ok(*b),
1112 _ => Err(ConversionError::new("bool", value.type_name())),
1113 }
1114 }
1115}
1116
1117#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
1122pub struct DictPairs(Vec<(MontyObject, MontyObject)>);
1123
1124impl From<Vec<(MontyObject, MontyObject)>> for DictPairs {
1125 fn from(pairs: Vec<(MontyObject, MontyObject)>) -> Self {
1126 Self(pairs)
1127 }
1128}
1129
1130impl IntoIterator for DictPairs {
1131 type Item = (MontyObject, MontyObject);
1132 type IntoIter = IntoIter<Self::Item>;
1133
1134 fn into_iter(self) -> Self::IntoIter {
1135 self.0.into_iter()
1136 }
1137}
1138impl<'a> IntoIterator for &'a DictPairs {
1139 type Item = &'a (MontyObject, MontyObject);
1140 type IntoIter = slice::Iter<'a, (MontyObject, MontyObject)>;
1141
1142 fn into_iter(self) -> Self::IntoIter {
1143 self.0.iter()
1144 }
1145}
1146
1147impl FromIterator<(MontyObject, MontyObject)> for DictPairs {
1148 fn from_iter<T: IntoIterator<Item = (MontyObject, MontyObject)>>(iter: T) -> Self {
1149 Self(iter.into_iter().collect())
1150 }
1151}
1152
1153impl DictPairs {
1154 #[must_use]
1156 pub fn len(&self) -> usize {
1157 self.0.len()
1158 }
1159
1160 #[must_use]
1162 pub fn is_empty(&self) -> bool {
1163 self.0.is_empty()
1164 }
1165
1166 fn iter(&self) -> impl Iterator<Item = &(MontyObject, MontyObject)> {
1167 self.0.iter()
1168 }
1169}
1170
1171#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1184pub struct MontyFileHandle {
1185 pub path: String,
1187 pub mode: FileMode,
1189 pub position: u64,
1192}
1193
1194impl fmt::Display for MontyFileHandle {
1195 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1196 write!(
1197 f,
1198 "<{} name={} mode={}>",
1199 self.mode.file_type_name(),
1200 StringRepr(&self.path),
1201 StringRepr(self.mode.as_str())
1202 )
1203 }
1204}
1205
1206fn monty_datetime_local_micros(datetime: &MontyDateTime) -> Option<i64> {
1207 monty_datetime_naive(datetime).map(|naive| naive.and_utc().timestamp_micros())
1208}
1209
1210fn monty_datetime_raw_eq(a: &MontyDateTime, b: &MontyDateTime) -> bool {
1211 a.year == b.year
1212 && a.month == b.month
1213 && a.day == b.day
1214 && a.hour == b.hour
1215 && a.minute == b.minute
1216 && a.second == b.second
1217 && a.microsecond == b.microsecond
1218 && a.offset_seconds == b.offset_seconds
1219 && a.timezone_name == b.timezone_name
1220}
1221
1222fn monty_datetime_utc_micros(datetime: &MontyDateTime) -> Option<i64> {
1223 let offset_seconds = datetime.offset_seconds?;
1224 let offset_delta = ChronoTimeDelta::try_seconds(i64::from(offset_seconds))?;
1225 let utc = monty_datetime_naive(datetime)?.checked_sub_signed(offset_delta)?;
1226 Some(utc.and_utc().timestamp_micros())
1227}
1228
1229fn monty_datetime_naive(datetime: &MontyDateTime) -> Option<NaiveDateTime> {
1230 let date = NaiveDate::from_ymd_opt(datetime.year, u32::from(datetime.month), u32::from(datetime.day))?;
1231 let time = NaiveTime::from_hms_micro_opt(
1232 u32::from(datetime.hour),
1233 u32::from(datetime.minute),
1234 u32::from(datetime.second),
1235 datetime.microsecond,
1236 )?;
1237 Some(date.and_time(time))
1238}