1use std::cell::RefCell;
4use std::rc::Rc;
5
6use crate::interpreter::RuntimeValue;
7
8use super::compare::values_equal;
9
10fn ascii_char_text(b: u8) -> Rc<String> {
17 debug_assert!(b < 128, "caller guarantees an ASCII byte");
18 thread_local! {
19 static CACHE: RefCell<[Option<Rc<String>>; 128]> =
20 RefCell::new(std::array::from_fn(|_| None));
21 }
22 CACHE.with(|c| {
23 c.borrow_mut()[b as usize]
24 .get_or_insert_with(|| Rc::new((b as char).to_string()))
25 .clone()
26 })
27}
28
29fn text_metrics(s: &Rc<String>) -> (bool, usize) {
38 thread_local! {
39 static CACHE: RefCell<Vec<(Rc<String>, usize, bool, usize)>> =
40 const { RefCell::new(Vec::new()) };
41 }
42 CACHE.with(|c| {
43 let mut c = c.borrow_mut();
44 let len = s.len();
45 if let Some(e) = c.iter().find(|(rc, l, _, _)| *l == len && Rc::ptr_eq(rc, s)) {
46 return (e.2, e.3);
47 }
48 let ascii = s.is_ascii();
49 let char_len = if ascii { len } else { s.chars().count() };
50 if c.len() >= 8 {
51 c.remove(0);
52 }
53 c.push((s.clone(), len, ascii, char_len));
54 (ascii, char_len)
55 })
56}
57
58pub fn text_is_ascii(s: &Rc<String>) -> bool {
66 text_metrics(s).0
67}
68
69fn resolve_index(i: i64, len: usize) -> Result<usize, String> {
73 if i >= 1 {
74 let idx = (i - 1) as usize;
75 if idx >= len {
76 return Err(format!("Index {} out of bounds", i));
77 }
78 Ok(idx)
79 } else if i <= -1 {
80 let back = i.unsigned_abs() as usize;
81 if back > len {
82 return Err(format!("Index {} out of bounds", i));
83 }
84 Ok(len - back)
85 } else {
86 Err("Index 0 out of bounds".to_string())
87 }
88}
89
90pub fn index_get(coll: &RuntimeValue, idx: &RuntimeValue) -> Result<RuntimeValue, String> {
93 match (coll, idx) {
94 (RuntimeValue::List(items), RuntimeValue::Int(i)) => {
95 let items = items.borrow();
96 let idx = resolve_index(*i, items.len())?;
97 Ok(items.get(idx).expect("bounds checked above"))
98 }
99 (RuntimeValue::Tuple(items), RuntimeValue::Int(i)) => {
100 let idx = resolve_index(*i, items.len())?;
101 Ok(items[idx].clone())
102 }
103 (RuntimeValue::Text(s), RuntimeValue::Int(i)) => {
104 let i = *i;
105 let bytes = s.as_bytes();
110 let (ascii, char_len) = text_metrics(s);
114 if i >= 1 && (i as usize) <= bytes.len() && ascii {
115 return Ok(RuntimeValue::Text(ascii_char_text(bytes[i as usize - 1])));
116 }
117 let idx = resolve_index(i, char_len)?;
118 Ok(RuntimeValue::Text(Rc::new(
119 s.chars().nth(idx).unwrap().to_string(),
120 )))
121 }
122 (RuntimeValue::Map(map), key) => {
123 let map = map.borrow();
124 match map.get(key) {
125 Some(val) => Ok(val.clone()),
126 None => Err(format!("Key '{}' not found in map", key.to_display_string())),
127 }
128 }
129 (RuntimeValue::Struct(s), RuntimeValue::Text(field)) => {
131 match s.fields.get(field.as_str()) {
132 Some(val) => Ok(val.clone()),
133 None => Err(format!("Struct has no field '{}'", field)),
134 }
135 }
136 _ => Err(format!(
137 "Cannot index {} with {}",
138 coll.type_name(),
139 idx.type_name()
140 )),
141 }
142}
143
144pub fn index_set(coll: &RuntimeValue, idx: &RuntimeValue, value: RuntimeValue) -> Result<(), String> {
147 match (coll, idx) {
148 (RuntimeValue::List(items), RuntimeValue::Int(n)) => {
149 let mut items = items.borrow_mut();
150 let idx = resolve_index(*n, items.len())?;
151 items.set(idx, value);
152 Ok(())
153 }
154 (RuntimeValue::Map(map), key) => {
155 assert_hashable_key(key)?;
156 map.borrow_mut().insert(key.clone(), value);
157 Ok(())
158 }
159 (RuntimeValue::List(_), _) => Err("List index must be an integer".to_string()),
160 _ => Err(format!("Cannot index into {}", coll.type_name())),
161 }
162}
163
164pub fn assert_hashable_key(key: &RuntimeValue) -> Result<(), String> {
169 match key {
170 RuntimeValue::List(_) | RuntimeValue::Set(_) | RuntimeValue::Map(_) => Err(format!(
171 "a {} cannot be a Map key — it is mutable, and mutating a live key \
172 would corrupt the map (use a Tuple of its values instead)",
173 key.type_name()
174 )),
175 RuntimeValue::Tuple(items) => {
176 for item in items.iter() {
177 assert_hashable_key(item)?;
178 }
179 Ok(())
180 }
181 RuntimeValue::Struct(s) => {
182 for value in s.fields.values() {
183 assert_hashable_key(value)?;
184 }
185 Ok(())
186 }
187 _ => Ok(()),
188 }
189}
190
191pub fn slice(
193 coll: &RuntimeValue,
194 start: &RuntimeValue,
195 end: &RuntimeValue,
196) -> Result<RuntimeValue, String> {
197 match (coll, start, end) {
198 (RuntimeValue::List(items), RuntimeValue::Int(s), RuntimeValue::Int(e)) => {
199 let items = items.borrow();
200 let start = (*s as usize).saturating_sub(1);
201 let end = *e as usize;
202 let payload = if start < end && end <= items.len() {
204 items.slice(start, end - 1)
205 } else {
206 crate::interpreter::ListRepr::Boxed(Vec::new())
207 };
208 Ok(RuntimeValue::List(Rc::new(RefCell::new(payload))))
209 }
210 _ => Err("Slice requires List and Int indices".to_string()),
211 }
212}
213
214pub fn length_of(coll: &RuntimeValue) -> Result<RuntimeValue, String> {
217 match coll {
218 RuntimeValue::List(items) => Ok(RuntimeValue::Int(items.borrow().len() as i64)),
219 RuntimeValue::Tuple(items) => Ok(RuntimeValue::Int(items.len() as i64)),
220 RuntimeValue::Set(items) => Ok(RuntimeValue::Int(items.borrow().len() as i64)),
221 RuntimeValue::Text(s) => Ok(RuntimeValue::Int(s.len() as i64)),
222 RuntimeValue::Map(map) => Ok(RuntimeValue::Int(map.borrow().len() as i64)),
223 RuntimeValue::Crdt(c) => Ok(RuntimeValue::Int(c.borrow().len() as i64)),
224 _ => Err(format!("Cannot get length of {}", coll.type_name())),
225 }
226}
227
228pub fn contains(coll: &RuntimeValue, val: &RuntimeValue) -> Result<RuntimeValue, String> {
231 match coll {
232 RuntimeValue::List(items) => {
233 Ok(RuntimeValue::Bool(items.borrow().contains(val)))
234 }
235 RuntimeValue::Set(items) => {
236 let items = items.borrow();
237 let found = items.iter().any(|item| values_equal(item, val));
238 Ok(RuntimeValue::Bool(found))
239 }
240 RuntimeValue::Map(entries) => Ok(RuntimeValue::Bool(entries.borrow().contains_key(val))),
241 RuntimeValue::Text(s) => {
242 if let RuntimeValue::Text(needle) = val {
243 Ok(RuntimeValue::Bool(s.contains(needle.as_str())))
244 } else if let RuntimeValue::Char(c) = val {
245 Ok(RuntimeValue::Bool(s.contains(*c)))
246 } else {
247 Err(format!("Cannot check if Text contains {}", val.type_name()))
248 }
249 }
250 RuntimeValue::Crdt(c) => Ok(RuntimeValue::Bool(c.borrow().contains(val)?)),
251 _ => Err(format!("Cannot check contains on {}", coll.type_name())),
252 }
253}
254
255pub fn union(left: &RuntimeValue, right: &RuntimeValue) -> Result<RuntimeValue, String> {
257 match (left, right) {
258 (RuntimeValue::Set(a), RuntimeValue::Set(b)) => {
259 let a = a.borrow();
260 let b = b.borrow();
261 let mut result = a.clone();
262 for item in b.iter() {
263 if !result.iter().any(|x| values_equal(x, item)) {
264 result.push(item.clone());
265 }
266 }
267 Ok(RuntimeValue::Set(Rc::new(RefCell::new(result))))
268 }
269 _ => Err(format!(
270 "Cannot union {} and {}",
271 left.type_name(),
272 right.type_name()
273 )),
274 }
275}
276
277pub fn intersection(left: &RuntimeValue, right: &RuntimeValue) -> Result<RuntimeValue, String> {
279 match (left, right) {
280 (RuntimeValue::Set(a), RuntimeValue::Set(b)) => {
281 let a = a.borrow();
282 let b = b.borrow();
283 let result: Vec<RuntimeValue> = a
284 .iter()
285 .filter(|item| b.iter().any(|x| values_equal(x, item)))
286 .cloned()
287 .collect();
288 Ok(RuntimeValue::Set(Rc::new(RefCell::new(result))))
289 }
290 _ => Err(format!(
291 "Cannot intersect {} and {}",
292 left.type_name(),
293 right.type_name()
294 )),
295 }
296}
297
298pub fn range(start: &RuntimeValue, end: &RuntimeValue) -> Result<RuntimeValue, String> {
300 match (start, end) {
301 (RuntimeValue::Int(s), RuntimeValue::Int(e)) => {
302 let range: Vec<i64> = (*s..=*e).collect();
303 Ok(RuntimeValue::List(Rc::new(RefCell::new(
304 crate::interpreter::ListRepr::Ints(range),
305 ))))
306 }
307 _ => Err("Range requires Int bounds".to_string()),
308 }
309}
310
311pub fn iteration_snapshot(v: &RuntimeValue) -> Result<Vec<RuntimeValue>, String> {
316 match v {
317 RuntimeValue::List(list) => Ok(list.borrow().to_values()),
318 RuntimeValue::Set(set) => Ok(set.borrow().clone()),
319 RuntimeValue::Text(s) => Ok(s
320 .chars()
321 .map(|c| RuntimeValue::Text(Rc::new(c.to_string())))
322 .collect()),
323 RuntimeValue::Map(map) => Ok(map
324 .borrow()
325 .iter()
326 .map(|(k, v)| RuntimeValue::Tuple(Rc::new(vec![k.clone(), v.clone()])))
327 .collect()),
328 _ => Err(format!("Cannot iterate over {}", v.type_name())),
329 }
330}
331
332pub fn push_to_struct_field(
335 obj: &RuntimeValue,
336 field_name: &str,
337 val: RuntimeValue,
338) -> Result<(), String> {
339 if let RuntimeValue::Struct(s) = obj {
340 if let Some(RuntimeValue::List(items)) = s.fields.get(field_name) {
341 items.borrow_mut().push(val);
342 Ok(())
343 } else {
344 Err(format!("Field '{}' is not a List", field_name))
345 }
346 } else {
347 Err("Cannot push to field of non-struct".to_string())
348 }
349}
350
351thread_local! {
352 static FORCE_REFERENCE: std::cell::Cell<u32> = const { std::cell::Cell::new(0) };
362}
363
364pub struct ReferenceScope;
367
368impl ReferenceScope {
369 pub fn enter() -> Self {
370 FORCE_REFERENCE.with(|c| c.set(c.get() + 1));
371 ReferenceScope
372 }
373}
374
375impl Drop for ReferenceScope {
376 fn drop(&mut self) {
377 FORCE_REFERENCE.with(|c| c.set(c.get().saturating_sub(1)));
378 }
379}
380
381pub fn with_reference_semantics<T>(f: impl FnOnce() -> T) -> T {
383 let _g = ReferenceScope::enter();
384 f()
385}
386
387pub fn reference_scope_active() -> bool {
391 FORCE_REFERENCE.with(|c| c.get() > 0)
392}
393
394pub fn value_semantics_enabled() -> bool {
402 use std::sync::OnceLock;
403 static ON: OnceLock<bool> = OnceLock::new();
404 if reference_scope_active() {
405 return false;
406 }
407 *ON.get_or_init(|| std::env::var("LOGOS_VALUE_SEMANTICS").as_deref() != Ok("0"))
408}
409
410pub fn list_push(coll: &RuntimeValue, value: RuntimeValue) -> Result<(), String> {
412 match coll {
413 RuntimeValue::List(items) => {
414 items.borrow_mut().push(value);
415 Ok(())
416 }
417 _ => Err("Can only push to a List".to_string()),
418 }
419}
420
421pub fn list_pop(coll: &RuntimeValue) -> Result<RuntimeValue, String> {
424 match coll {
425 RuntimeValue::List(items) => {
426 Ok(items.borrow_mut().pop().unwrap_or(RuntimeValue::Nothing))
427 }
428 _ => Err("Can only pop from a List".to_string()),
429 }
430}
431
432pub fn set_add(coll: &RuntimeValue, value: RuntimeValue) -> Result<(), String> {
434 match coll {
435 RuntimeValue::Set(items) => {
436 let already_present = items.borrow().iter().any(|x| values_equal(x, &value));
437 if !already_present {
438 items.borrow_mut().push(value);
439 }
440 Ok(())
441 }
442 RuntimeValue::Crdt(c) => c.borrow_mut().insert(&value),
443 _ => Err("Can only add to a Set".to_string()),
444 }
445}
446
447pub fn remove_from(coll: &RuntimeValue, value: &RuntimeValue) -> Result<(), String> {
449 match coll {
450 RuntimeValue::Set(items) => {
451 items.borrow_mut().retain(|x| !values_equal(x, value));
452 Ok(())
453 }
454 RuntimeValue::Map(map) => {
455 map.borrow_mut().shift_remove(value);
456 Ok(())
457 }
458 RuntimeValue::Crdt(c) => c.borrow_mut().remove(value),
459 _ => Err("Can only remove from a Set or Map".to_string()),
460 }
461}
462
463#[cfg(test)]
464mod tests {
465 use super::*;
466
467 fn list(items: Vec<RuntimeValue>) -> RuntimeValue {
468 RuntimeValue::List(Rc::new(RefCell::new(crate::interpreter::ListRepr::from_values(
469 items,
470 ))))
471 }
472
473 #[test]
474 fn index_is_one_based_with_end_relative_negatives() {
475 let xs = list(vec![RuntimeValue::Int(5), RuntimeValue::Int(6)]);
476 assert!(matches!(index_get(&xs, &RuntimeValue::Int(1)).unwrap(), RuntimeValue::Int(5)));
477 assert_eq!(index_get(&xs, &RuntimeValue::Int(0)).unwrap_err(), "Index 0 out of bounds");
478 assert_eq!(index_get(&xs, &RuntimeValue::Int(3)).unwrap_err(), "Index 3 out of bounds");
479 assert!(matches!(index_get(&xs, &RuntimeValue::Int(-1)).unwrap(), RuntimeValue::Int(6)));
481 assert!(matches!(index_get(&xs, &RuntimeValue::Int(-2)).unwrap(), RuntimeValue::Int(5)));
482 assert_eq!(index_get(&xs, &RuntimeValue::Int(-3)).unwrap_err(), "Index -3 out of bounds");
484 }
485
486 #[test]
487 fn text_indexing_is_chars_but_length_is_bytes() {
488 let s = RuntimeValue::Text(Rc::new("héllo".to_string()));
489 let c = index_get(&s, &RuntimeValue::Int(2)).unwrap();
491 assert!(matches!(&c, RuntimeValue::Text(t) if **t == "é"));
492 assert!(matches!(length_of(&s).unwrap(), RuntimeValue::Int(6)));
493 }
494
495 #[test]
496 fn slice_is_one_indexed_inclusive_and_oob_is_empty() {
497 let xs = list((1..=5).map(RuntimeValue::Int).collect());
498 let s = slice(&xs, &RuntimeValue::Int(2), &RuntimeValue::Int(4)).unwrap();
499 if let RuntimeValue::List(items) = &s {
500 let v: Vec<i64> = items
501 .borrow()
502 .to_values()
503 .iter()
504 .map(|x| if let RuntimeValue::Int(n) = x { *n } else { panic!() })
505 .collect();
506 assert_eq!(v, vec![2, 3, 4]);
507 } else {
508 panic!("slice did not return a list");
509 }
510 let s = slice(&xs, &RuntimeValue::Int(4), &RuntimeValue::Int(99)).unwrap();
511 if let RuntimeValue::List(items) = &s {
512 assert!(items.borrow().is_empty());
513 }
514 }
515
516 #[test]
517 fn pop_of_empty_list_is_nothing_not_error() {
518 let xs = list(vec![]);
519 assert!(matches!(list_pop(&xs).unwrap(), RuntimeValue::Nothing));
520 }
521
522 #[test]
523 fn set_add_dedups_with_ieee_equality() {
524 let s = RuntimeValue::Set(Rc::new(RefCell::new(vec![RuntimeValue::Float(0.3)])));
527 set_add(&s, RuntimeValue::Float(0.1 + 0.2)).unwrap();
528 if let RuntimeValue::Set(items) = &s {
529 assert_eq!(items.borrow().len(), 2, "IEEE-distinct floats stay distinct");
530 }
531 set_add(&s, RuntimeValue::Float(0.3)).unwrap();
533 if let RuntimeValue::Set(items) = &s {
534 assert_eq!(items.borrow().len(), 2, "bit-equal float must dedup");
535 }
536 }
537
538 #[test]
539 fn range_requires_int_bounds() {
540 assert_eq!(
541 range(&RuntimeValue::Int(1), &RuntimeValue::Float(2.5)).unwrap_err(),
542 "Range requires Int bounds"
543 );
544 }
545}