polydat_core/iteration/comprehension/source_values.rs
1// Copyright 2024-2026 Jonathan Shook
2// SPDX-License-Identifier: Apache-2.0
3
4//! The runtime's reading of a value as a comprehension source: which
5//! values peel into elements, and how a string source strips into
6//! typed tokens. The grammar's separator rule
7//! (`source::split_string_comprehension`) is the one both sides use.
8
9use super::source::split_string_comprehension;
10
11/// The canonical "is this value peelable, and into what?" predicate
12/// (comprehension_forms.md §3.1.2). Returns `Some(interior)` when `v` has an iteration
13/// interior — peeling it one level yields these elements — and
14/// `None` when `v` is an iteration scalar (relaxed wraps it; an
15/// explicit `[v…]` destructure errors).
16///
17/// This is the single place the peel/wrap decision is made,
18/// replacing the scattered per-type special cases (the
19/// `as_partition_list()`-peel / `Ok(other)`-wrap arms) that used
20/// to live in `eval::evaluate_spec_internal`.
21///
22/// `Value::Str` is iterable — its interior is its
23/// **string-comprehension tokens** ([`strip_string_tokens`]). The
24/// single-quoted *atomic* form is resolved earlier, at the
25/// source-text layer (the parser produces a one-element literal),
26/// so by the time a bare `Value::Str` reaches this predicate the
27/// intent is "iterate it." A whole-string binding is reached via
28/// the no-peel form `x in [s]`.
29pub fn iteration_interior(v: &crate::ast::Value) -> Option<Vec<crate::ast::Value>> {
30 use crate::ast::Value;
31 match v {
32 Value::VecF32(s) => Some(s.as_slice().iter().map(|x| Value::F64(*x as f64)).collect()),
33 Value::VecF64(s) => Some(s.as_slice().iter().map(|x| Value::F64(*x)).collect()),
34 Value::VecF16(s) => Some(
35 s.as_slice()
36 .iter()
37 .map(|x| Value::F64(x.to_f64()))
38 .collect(),
39 ),
40 // Signed lanes peel to the honest signed carrier — a
41 // VecI64 holding -5 iterates as I64(-5), not the unsigned
42 // bit-reinterpretation (type_system_alignment.md §5).
43 Value::VecI32(s) => Some(s.as_slice().iter().map(|x| Value::I64(*x as i64)).collect()),
44 Value::VecI64(s) => Some(s.as_slice().iter().map(|x| Value::I64(*x)).collect()),
45 Value::VecI16(s) => Some(s.as_slice().iter().map(|x| Value::I64(*x as i64)).collect()),
46 Value::VecI8(s) => Some(s.as_slice().iter().map(|x| Value::I64(*x as i64)).collect()),
47 // A JSON array peels to its elements (each carried as a
48 // Json value); a non-array JSON is an iteration scalar.
49 Value::Json(j) => j.as_array().map(|arr| {
50 arr.iter()
51 .map(|e| Value::Json(std::sync::Arc::new(e.clone())))
52 .collect()
53 }),
54 // Ext carrying a PartitionList peels into its partitions
55 // (cursor_partitions.md §7.1). Other Ext values are opaque
56 // scalars.
57 Value::Ext(_) => v.as_partition_list().map(|list| {
58 list.as_slice()
59 .iter()
60 .map(|p| Value::from_partition(*p))
61 .collect()
62 }),
63 // Lane-typed register views peel like the Vec* family
64 // (a reg_f32x4 is a fixed-width typed vector); the Raw
65 // view is an opaque buffer-state word — an iteration
66 // scalar.
67 Value::Reg128(b, view) => {
68 use crate::ast::RegLanes;
69 match view {
70 RegLanes::Raw => None,
71 RegLanes::I8x16 => {
72 Some(b.lanes_i8().iter().map(|x| Value::I64(*x as i64)).collect())
73 }
74 RegLanes::I16x8 => Some(
75 b.lanes_i16()
76 .iter()
77 .map(|x| Value::I64(*x as i64))
78 .collect(),
79 ),
80 RegLanes::I32x4 => Some(
81 b.lanes_i32()
82 .iter()
83 .map(|x| Value::I64(*x as i64))
84 .collect(),
85 ),
86 RegLanes::I64x2 => Some(b.lanes_i64().iter().map(|x| Value::I64(*x)).collect()),
87 RegLanes::F16x8 => Some(
88 b.lanes_f16()
89 .iter()
90 .map(|x| Value::F64(x.to_f64()))
91 .collect(),
92 ),
93 RegLanes::F32x4 => Some(
94 b.lanes_f32()
95 .iter()
96 .map(|x| Value::F64(*x as f64))
97 .collect(),
98 ),
99 RegLanes::F64x2 => Some(b.lanes_f64().iter().map(|x| Value::F64(*x)).collect()),
100 }
101 }
102 // A string's interior is its comprehension tokens.
103 Value::Str(s) => Some(strip_string_tokens(s)),
104 // Iteration scalars — relaxed wraps, `[v…]` errors.
105 Value::U64(_)
106 | Value::I64(_)
107 | Value::U128(_)
108 | Value::I128(_)
109 | Value::F64(_)
110 | Value::Bool(_)
111 | Value::Bytes(_)
112 | Value::Handle(_)
113 | Value::None => None,
114 }
115}
116
117/// Split a string-comprehension source into its token values
118/// (comprehension_forms.md §3.1.3). Separators are runs of comma, semicolon, and
119/// ASCII whitespace; every other character — notably `:` (k:v /
120/// `a:b:c` tuples), `.` (floats), `-` (negatives / hyphenated
121/// labels), `/` — stays inside the token. Each token is typed
122/// like a literal-list element (u64 / f64 / bool / else Str), so
123/// `"1, 2, 3"` yields numeric values and `"a, b"` yields strings.
124///
125/// A single-token string (`"OTHER"`) yields a one-element vec, so
126/// the single-value case degenerates to the no-peel binding for
127/// free.
128pub fn strip_string_tokens(s: &str) -> Vec<crate::ast::Value> {
129 use crate::ast::Value;
130 split_string_comprehension(s)
131 .into_iter()
132 .map(|t| {
133 if let Ok(n) = t.parse::<u64>() {
134 Value::U64(n)
135 } else if let Ok(f) = t.parse::<f64>() {
136 Value::F64(f)
137 } else if t == "true" {
138 Value::Bool(true)
139 } else if t == "false" {
140 Value::Bool(false)
141 } else {
142 Value::Str(t.to_string().into())
143 }
144 })
145 .collect()
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151 use crate::ast::Value;
152
153 #[test]
154 fn string_strips_on_comma_semicolon_whitespace_retaining_colons() {
155 // comprehension_forms.md §3.1.3: separators are comma /
156 // semicolon / ws;
157 // colons (and dots, dashes) stay in-token.
158 let got = strip_string_tokens("a:1, b:2; c:3 d:4");
159 assert_eq!(
160 got,
161 vec![
162 Value::Str("a:1".into()),
163 Value::Str("b:2".into()),
164 Value::Str("c:3".into()),
165 Value::Str("d:4".into()),
166 ]
167 );
168 }
169
170 #[test]
171 fn string_tokens_are_typed_like_literals() {
172 // Floats survive (dot retained), ints type as U64.
173 assert_eq!(
174 strip_string_tokens("1, 2, 3"),
175 vec![Value::U64(1), Value::U64(2), Value::U64(3)]
176 );
177 assert_eq!(
178 strip_string_tokens("1.5, 2.5"),
179 vec![Value::F64(1.5), Value::F64(2.5)]
180 );
181 }
182
183 #[test]
184 fn single_token_string_degenerates_to_singleton() {
185 assert_eq!(
186 strip_string_tokens("OTHER"),
187 vec![Value::Str("OTHER".into())]
188 );
189 }
190
191 #[test]
192 fn iteration_interior_string_is_its_tokens() {
193 let v = Value::Str("x, y, z".into());
194 assert_eq!(
195 iteration_interior(&v),
196 Some(vec![
197 Value::Str("x".into()),
198 Value::Str("y".into()),
199 Value::Str("z".into())
200 ])
201 );
202 }
203
204 #[test]
205 fn iteration_interior_vector_peels_to_elements() {
206 let v = Value::VecI32(crate::ast::SliceArc::from_vec(vec![10, 20, 30]));
207 assert_eq!(
208 iteration_interior(&v),
209 Some(vec![Value::I64(10), Value::I64(20), Value::I64(30)])
210 );
211 }
212
213 #[test]
214 fn iteration_interior_signed_vector_peels_signed() {
215 // The honest-I64 alignment fix: negative lane elements
216 // iterate as their signed values, not the unsigned
217 // bit-reinterpretation (type_system_alignment.md §5).
218 let v = Value::VecI64(crate::ast::SliceArc::from_vec(vec![-5_i64, 7]));
219 assert_eq!(
220 iteration_interior(&v),
221 Some(vec![Value::I64(-5), Value::I64(7)])
222 );
223 }
224
225 #[test]
226 fn iteration_interior_scalars_are_none() {
227 assert_eq!(iteration_interior(&Value::U64(5)), None);
228 assert_eq!(iteration_interior(&Value::F64(1.5)), None);
229 assert_eq!(iteration_interior(&Value::Bool(true)), None);
230 }
231}