spath/spec/selector/
slice.rs

1// Copyright 2024 tison <wander4096@gmail.com>
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Slice selectors for selecting array slices in SPath.
16
17use std::cmp::max;
18use std::cmp::min;
19use std::cmp::Ordering;
20
21use num_traits::ToPrimitive;
22
23use crate::spec::function::FunctionRegistry;
24use crate::spec::query::Queryable;
25use crate::ConcreteVariantArray;
26use crate::LocatedNode;
27use crate::NormalizedPath;
28use crate::VariantValue;
29
30/// §2.3.4 Array Slice Selector.
31///
32/// Default Array Slice start and end Values:
33///
34/// | Condition | start     | end     |
35/// |-----------|-----------|---------|
36/// | step >= 0 | 0         | len     |
37/// | step < 0  | len - 1   | -len - 1|
38#[derive(Debug, PartialEq, Eq, Default, Clone, Copy)]
39pub struct Slice {
40    /// The start of the slice, inclusive.
41    ///
42    /// This can be negative to start the slice from a position relative to the end of the array
43    /// being sliced.
44    start: Option<i64>,
45    /// The end of the slice, exclusive.
46    ///
47    /// This can be negative to end the slice at a position relative to the end of the array being
48    /// sliced.
49    end: Option<i64>,
50    /// The step slice for the slice. Default to 1.
51    ///
52    /// This can be negative to step in reverse order.
53    step: Option<i64>,
54}
55
56impl std::fmt::Display for Slice {
57    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58        if let Some(start) = self.start {
59            write!(f, "{start}")?;
60        }
61        write!(f, ":")?;
62        if let Some(end) = self.end {
63            write!(f, "{end}")?;
64        }
65        write!(f, ":")?;
66        if let Some(step) = self.step {
67            write!(f, "{step}")?;
68        }
69        Ok(())
70    }
71}
72
73// §2.3.4.2.2. (Array Slice Selector) Normative Semantics
74fn bounds(start: i64, end: i64, step: i64, len: i64) -> (i64, i64) {
75    fn normalize(i: i64, len: i64) -> i64 {
76        if i < 0 {
77            len + i
78        } else {
79            i
80        }
81    }
82
83    let start = normalize(start, len);
84    let end = normalize(end, len);
85
86    if step >= 0 {
87        let lower = min(max(start, 0), len);
88        let upper = min(max(end, 0), len);
89        (lower, upper)
90    } else {
91        let upper = min(max(start, -1), len - 1);
92        let lower = min(max(end, -1), len - 1);
93        (lower, upper)
94    }
95}
96
97impl Slice {
98    /// Create a new slice selector.
99    pub fn new(start: Option<i64>, end: Option<i64>, step: Option<i64>) -> Self {
100        Self { start, end, step }
101    }
102
103    fn select<'b, T, N, F>(&self, current: &'b T, make_node: F) -> Vec<N>
104    where
105        T: VariantValue,
106        N: 'b,
107        F: Fn(usize, &'b T) -> N,
108    {
109        let vec = match current.as_array() {
110            Some(vec) => vec,
111            None => return vec![],
112        };
113
114        let (start, end, step) = (self.start, self.end, self.step.unwrap_or(1));
115        if step == 0 {
116            // §2.3.4.2.2. Normative Semantics
117            // When step = 0, no elements are selected, and the result array is empty.
118            return vec![];
119        }
120
121        let len = vec.len().to_i64().unwrap_or(i64::MAX);
122        let (start, end) = if step >= 0 {
123            match (start, end) {
124                (Some(start), Some(end)) => (start, end),
125                (Some(start), None) => (start, len),
126                (None, Some(end)) => (0, end),
127                (None, None) => (0, len),
128            }
129        } else {
130            match (start, end) {
131                (Some(start), Some(end)) => (start, end),
132                (Some(start), None) => (start, -len - 1),
133                (None, Some(end)) => (len - 1, end),
134                (None, None) => (len - 1, -len - 1),
135            }
136        };
137
138        let (lower, upper) = bounds(start, end, step, len);
139        let mut selected = vec![];
140        match step.cmp(&0) {
141            Ordering::Greater => {
142                // step > 0
143                let mut i = lower;
144                while i < upper {
145                    let node = vec.get(i as usize).unwrap();
146                    selected.push(make_node(i as usize, node));
147                    i += step;
148                }
149            }
150            Ordering::Less => {
151                // step < 0
152                let mut i = upper;
153                while lower < i {
154                    let node = vec.get(i as usize).unwrap();
155                    selected.push(make_node(i as usize, node));
156                    i += step;
157                }
158            }
159            Ordering::Equal => unreachable!("step is guaranteed not zero here"),
160        }
161        selected
162    }
163}
164
165impl Queryable for Slice {
166    fn query<'b, T: VariantValue, Registry: FunctionRegistry<Value = T>>(
167        &self,
168        current: &'b T,
169        _root: &'b T,
170        _registry: &Registry,
171    ) -> Vec<&'b T> {
172        self.select(current, |_, node| node)
173    }
174
175    fn query_located<'b, T: VariantValue, Registry: FunctionRegistry<Value = T>>(
176        &self,
177        current: &'b T,
178        _root: &'b T,
179        _registry: &Registry,
180        parent: NormalizedPath<'b>,
181    ) -> Vec<LocatedNode<'b, T>> {
182        self.select(current, |i, node| {
183            LocatedNode::new(parent.clone_and_push(i), node)
184        })
185    }
186}