compose_library/foundations/
range.rs1use 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}