1use std::io::Write;
2
3use crate::error::{DogeError, DogeResult};
4use crate::value::Value;
5
6pub fn bark(v: &Value) -> Value {
9 println!("{v}");
10 Value::None
11}
12
13pub fn gib(prompt: Option<&Value>) -> DogeResult {
18 if let Some(p) = prompt {
19 let text = match p {
20 Value::Str(s) => s,
21 _ => {
22 return Err(DogeError::type_error(format!(
23 "gib needs a Str prompt, got {}",
24 p.describe()
25 )))
26 }
27 };
28 print!("{text}");
29 let _ = std::io::stdout().flush();
30 }
31 let mut line = String::new();
32 match std::io::stdin().read_line(&mut line) {
33 Ok(0) => Ok(Value::None),
34 Ok(_) => {
35 let line = line.strip_suffix('\n').unwrap_or(&line);
36 let line = line.strip_suffix('\r').unwrap_or(line);
37 Ok(Value::str(line))
38 }
39 Err(err) => Err(DogeError::io_error(format!("could not read input: {err}"))),
40 }
41}
42
43pub fn len(v: &Value) -> DogeResult {
46 match v {
47 Value::Str(s) => Ok(Value::Int(s.chars().count() as i64)),
48 Value::List(items) => Ok(Value::Int(items.borrow().len() as i64)),
49 Value::Dict(entries) => Ok(Value::Int(entries.borrow().len() as i64)),
50 _ => Err(crate::error::DogeError::type_error(format!(
51 "cannot take the len of {}",
52 v.describe()
53 ))),
54 }
55}
56
57pub fn to_str(v: &Value) -> Value {
59 Value::str(v.to_string())
60}
61
62pub fn interp(parts: &[Value]) -> Value {
65 let mut out = String::new();
66 for part in parts {
67 out.push_str(&part.to_string());
68 }
69 Value::str(out)
70}
71
72pub fn to_int(v: &Value) -> DogeResult {
76 match v {
77 Value::Int(n) => Ok(Value::Int(*n)),
78 Value::Float(f) => Ok(Value::Int(*f as i64)),
79 Value::Bool(b) => Ok(Value::Int(i64::from(*b))),
80 Value::Str(s) => s.trim().parse::<i64>().map(Value::Int).map_err(|_| {
81 crate::error::DogeError::value_error(format!("cannot turn {s:?} into an Int"))
82 }),
83 _ => Err(crate::error::DogeError::type_error(format!(
84 "cannot turn {} into an Int",
85 v.describe()
86 ))),
87 }
88}
89
90pub fn to_float(v: &Value) -> DogeResult {
94 match v {
95 Value::Int(n) => Ok(Value::Float(*n as f64)),
96 Value::Float(f) => Ok(Value::Float(*f)),
97 Value::Bool(b) => Ok(Value::Float(if *b { 1.0 } else { 0.0 })),
98 Value::Str(s) => s.trim().parse::<f64>().map(Value::Float).map_err(|_| {
99 crate::error::DogeError::value_error(format!("cannot turn {s:?} into a Float"))
100 }),
101 _ => Err(crate::error::DogeError::type_error(format!(
102 "cannot turn {} into a Float",
103 v.describe()
104 ))),
105 }
106}
107
108pub fn range(start: &Value, end: &Value) -> DogeResult {
113 match (start, end) {
114 (Value::Int(a), Value::Int(b)) => Ok(Value::list((*a..*b).map(Value::Int).collect())),
115 (Value::Int(_), other) | (other, _) => Err(crate::error::DogeError::type_error(format!(
116 "range needs Int bounds, got {}",
117 other.describe()
118 ))),
119 }
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125 use crate::error::ErrorKind;
126
127 #[test]
128 fn bark_returns_none() {
129 assert!(matches!(bark(&Value::str("much hello")), Value::None));
130 }
131
132 #[test]
133 fn len_counts_characters_and_elements() {
134 assert!(matches!(len(&Value::str("héllo")).unwrap(), Value::Int(5)));
136 assert!(matches!(
137 len(&Value::list(vec![Value::Int(1), Value::Int(2)])).unwrap(),
138 Value::Int(2)
139 ));
140 assert_eq!(len(&Value::Int(3)).unwrap_err().kind, ErrorKind::TypeError);
141 }
142
143 #[test]
144 fn interp_joins_display_forms() {
145 let parts = [
146 Value::str("age "),
147 Value::Int(7),
148 Value::str(", "),
149 Value::None,
150 ];
151 assert!(matches!(interp(&parts), Value::Str(s) if &*s == "age 7, none"));
152 let nested = [Value::str("["), Value::str("hi"), Value::str("]")];
154 assert!(matches!(interp(&nested), Value::Str(s) if &*s == "[hi]"));
155 assert!(matches!(interp(&[]), Value::Str(s) if s.is_empty()));
156 }
157
158 #[test]
159 fn conversions_round_trip() {
160 assert!(matches!(to_str(&Value::Int(7)), Value::Str(s) if &*s == "7"));
161 assert!(matches!(to_int(&Value::Float(3.9)).unwrap(), Value::Int(3)));
162 assert!(matches!(
163 to_int(&Value::str(" 42 ")).unwrap(),
164 Value::Int(42)
165 ));
166 assert!(matches!(to_float(&Value::Int(4)).unwrap(), Value::Float(f) if f == 4.0));
167 }
168
169 #[test]
170 fn bad_conversions_are_catchable_value_errors() {
171 assert_eq!(
172 to_int(&Value::str("dog")).unwrap_err().kind,
173 ErrorKind::ValueError
174 );
175 assert_eq!(
176 to_float(&Value::str("woof")).unwrap_err().kind,
177 ErrorKind::ValueError
178 );
179 }
180
181 #[test]
182 fn range_two_args() {
183 let xs = range(&Value::Int(2), &Value::Int(5)).unwrap();
184 match xs {
185 Value::List(items) => {
186 let items = items.borrow();
187 assert_eq!(items.len(), 3);
188 assert!(matches!(items[0], Value::Int(2)));
189 assert!(matches!(items[2], Value::Int(4)));
190 }
191 _ => panic!("expected a list"),
192 }
193 }
194
195 #[test]
196 fn range_empty_when_end_not_after_start() {
197 let xs = range(&Value::Int(5), &Value::Int(5)).unwrap();
198 assert!(matches!(len(&xs).unwrap(), Value::Int(0)));
199 let ys = range(&Value::Int(5), &Value::Int(2)).unwrap();
200 assert!(matches!(len(&ys).unwrap(), Value::Int(0)));
201 }
202
203 #[test]
204 fn range_rejects_float() {
205 assert_eq!(
206 range(&Value::Int(0), &Value::Float(3.0)).unwrap_err().kind,
207 ErrorKind::TypeError
208 );
209 assert_eq!(
210 range(&Value::Float(0.0), &Value::Int(3)).unwrap_err().kind,
211 ErrorKind::TypeError
212 );
213 }
214}