compose_library/foundations/
range.rs

1use crate::repr::Repr;
2use crate::{FromValue, Iter, Trace};
3use compose_library::diag::{StrResult, bail};
4use compose_library::{IntoValue, IterValue, RangeIter, UntypedRef, Value, Vm};
5use compose_macros::{func, scope, ty};
6use ecow::{EcoString, eco_format};
7use std::sync::Arc;
8
9#[ty(scope, cast, name = "range")]
10#[derive(Clone, Debug, PartialEq)]
11pub struct RangeValue(Arc<Range>);
12
13#[derive(Clone, Debug, PartialEq)]
14pub enum Range {
15    Int(RangeImpl<i64>),
16    Char(RangeImpl<char>),
17}
18
19#[derive(Clone, Debug, PartialEq)]
20pub struct RangeImpl<T>
21where
22    T: PartialEq + PartialOrd,
23{
24    pub start: Option<T>,
25    pub end: Option<T>,
26    pub include_end: bool,
27}
28
29impl Trace for RangeValue {
30    fn visit_refs(&self, _f: &mut dyn FnMut(UntypedRef)) {}
31}
32
33impl Range {
34    pub fn try_new(start: Option<Value>, end: Option<Value>, include_end: bool) -> StrResult<Self> {
35        fn cast_opt<T: FromValue>(v: &Option<Value>) -> Option<Option<T>> {
36            let Some(v) = v else {
37                return Some(None);
38            };
39
40            match v.clone().cast::<T>() {
41                Ok(v) => Some(Some(v)),
42                Err(_) => None,
43            }
44        }
45
46        fn cast_opt_pair<T: FromValue>(
47            start: &Option<Value>,
48            end: &Option<Value>,
49        ) -> Option<(Option<T>, Option<T>)> {
50            let start = cast_opt(start)?;
51            let end = cast_opt(end)?;
52            Some((start, end))
53        }
54        if let Some((start, end)) = cast_opt_pair::<i64>(&start, &end) {
55            return Ok(Range::Int(RangeImpl::new(start, end, include_end)));
56        }
57
58        if let Some((start, end)) = cast_opt_pair::<char>(&start, &end) {
59            return Ok(Range::Char(RangeImpl::new(start, end, include_end)));
60        }
61
62        bail!("Cannot create range from types other than int")
63    }
64}
65
66impl Repr for RangeValue {
67    fn repr(&self, _vm: &dyn Vm) -> EcoString {
68        let (start, end, include_end) = match &self.0.as_ref() {
69            Range::Int(r) => (
70                r.start.map(|v| eco_format!("{v}")),
71                r.end.map(|e| eco_format!("{e}")),
72                r.include_end,
73            ),
74            Range::Char(r) => (
75                r.start.map(|v| eco_format!("{v}")),
76                r.end.map(|e| eco_format!("{e}")),
77                r.include_end,
78            ),
79        };
80        let op = if include_end { "..=" } else { ".." };
81        let start = start.unwrap_or_else(|| eco_format!(""));
82        let end = end.unwrap_or_else(|| eco_format!(""));
83
84        eco_format!("{start}{op}{end}")
85    }
86}
87
88impl<T> RangeImpl<T>
89where
90    T: PartialEq + PartialOrd,
91{
92    pub fn new(start: Option<T>, end: Option<T>, include_end: bool) -> Self {
93        Self {
94            start,
95            end,
96            include_end,
97        }
98    }
99}
100
101#[scope]
102impl RangeValue {
103    #[func]
104    pub fn new(start: Option<Value>, end: Option<Value>, inclusive_end: bool) -> StrResult<Self> {
105        Range::try_new(start, end, inclusive_end)
106            .map(Arc::new)
107            .map(RangeValue)
108    }
109
110    #[func]
111    pub fn start(&self) -> Option<Value> {
112        match &self.0.as_ref() {
113            Range::Int(r) => r.start.map(|v| v.into_value()),
114            Range::Char(r) => r.start.map(|v| v.into_value()),
115        }
116    }
117    #[func]
118    pub fn end(&self) -> Option<Value> {
119        match &self.0.as_ref() {
120            Range::Int(r) => r.end.map(|v| v.into_value()),
121            Range::Char(r) => r.end.map(|v| v.into_value()),
122        }
123    }
124
125    #[func]
126    pub fn inclusive_end(&self) -> bool {
127        match &self.0.as_ref() {
128            Range::Int(r) => r.include_end,
129            Range::Char(r) => r.include_end,
130        }
131    }
132
133    #[func]
134    pub fn contains(&self, value: Value) -> StrResult<bool> {
135        match &self.0.as_ref() {
136            Range::Int(r) => Ok(in_bounds(value.cast()?, r.start, r.end, r.include_end)),
137            Range::Char(r) => Ok(in_bounds(value.cast()?, r.start, r.end, r.include_end)),
138        }
139    }
140
141    #[func]
142    pub fn iter(self, vm: &mut dyn Vm) -> StrResult<IterValue> {
143        Ok(IterValue::new(
144            Iter::Range(RangeIter::new(self.inner())?),
145            vm,
146        ))
147    }
148}
149
150impl RangeValue {
151    pub fn inner(&self) -> &Range {
152        &self.0
153    }
154}
155
156fn in_bounds<T: PartialOrd>(v: T, start: Option<T>, end: Option<T>, include_end: bool) -> bool {
157    let lower_ok = start.map_or(true, |s| v >= s);
158    let upper_ok = end.map_or(true, |e| if include_end { v <= e } else { v < e });
159    lower_ok && upper_ok
160}