use std::io::Write;
use bigdecimal::{BigDecimal, FromPrimitive, RoundingMode, ToPrimitive};
use num_bigint::BigInt;
use crate::error::{DogeError, DogeResult};
use crate::value::Value;
pub fn bark(v: &Value) -> Value {
println!("{v}");
Value::None
}
pub fn gib(prompt: Option<&Value>) -> DogeResult {
if let Some(p) = prompt {
let text = match p {
Value::Str(s) => s,
_ => {
return Err(DogeError::type_error(format!(
"gib needs a Str prompt, got {}",
p.describe()
)))
}
};
print!("{text}");
let _ = std::io::stdout().flush();
}
let mut line = String::new();
match std::io::stdin().read_line(&mut line) {
Ok(0) => Ok(Value::None),
Ok(_) => {
let line = line.strip_suffix('\n').unwrap_or(&line);
let line = line.strip_suffix('\r').unwrap_or(line);
Ok(Value::str(line))
}
Err(err) => Err(DogeError::io_error(format!("could not read input: {err}"))),
}
}
pub fn len(v: &Value) -> DogeResult {
match v {
Value::Str(s) => Ok(Value::int(s.chars().count())),
Value::Bytes(b) => Ok(Value::int(b.len())),
Value::List(items) => Ok(Value::int(items.borrow().len())),
Value::Dict(entries) => Ok(Value::int(entries.borrow().len())),
_ => Err(crate::error::DogeError::type_error(format!(
"cannot take the len of {}",
v.describe()
))),
}
}
pub fn to_str(v: &Value) -> Value {
Value::str(v.to_string())
}
pub fn interp(parts: &[Value]) -> Value {
let mut out = String::new();
for part in parts {
out.push_str(&part.to_string());
}
Value::str(out)
}
pub fn to_int(v: &Value) -> DogeResult {
match v {
Value::Int(n) => Ok(Value::Int(n.clone())),
Value::Float(f) => BigInt::from_f64(f.trunc())
.map(Value::Int)
.ok_or_else(|| DogeError::value_error(format!("cannot turn {f} into an Int"))),
Value::Decimal(d) => {
let (digits, _) = d
.with_scale_round(0, RoundingMode::Down)
.into_bigint_and_exponent();
Ok(Value::Int(digits))
}
Value::Bool(b) => Ok(Value::int(i64::from(*b))),
Value::Str(s) => s.trim().parse::<BigInt>().map(Value::Int).map_err(|_| {
crate::error::DogeError::value_error(format!("cannot turn {s:?} into an Int"))
}),
_ => Err(crate::error::DogeError::type_error(format!(
"cannot turn {} into an Int",
v.describe()
))),
}
}
pub fn to_float(v: &Value) -> DogeResult {
match v {
Value::Int(n) => n
.to_f64()
.map(Value::Float)
.ok_or_else(|| DogeError::value_error(format!("{n} is too large for a Float"))),
Value::Float(f) => Ok(Value::Float(*f)),
Value::Decimal(d) => d
.to_f64()
.map(Value::Float)
.ok_or_else(|| DogeError::value_error(format!("{d} is too large for a Float"))),
Value::Bool(b) => Ok(Value::Float(if *b { 1.0 } else { 0.0 })),
Value::Str(s) => s.trim().parse::<f64>().map(Value::Float).map_err(|_| {
crate::error::DogeError::value_error(format!("cannot turn {s:?} into a Float"))
}),
_ => Err(crate::error::DogeError::type_error(format!(
"cannot turn {} into a Float",
v.describe()
))),
}
}
pub fn to_decimal(v: &Value) -> DogeResult {
match v {
Value::Decimal(_) => Ok(v.clone()),
Value::Int(n) => Ok(Value::decimal(BigDecimal::from(n.clone()))),
Value::Bool(b) => Ok(Value::decimal(BigDecimal::from(i64::from(*b)))),
Value::Float(f) => format!("{f}")
.parse::<BigDecimal>()
.map(Value::decimal)
.map_err(|_| DogeError::value_error(format!("cannot turn {f} into a Decimal"))),
Value::Str(s) => s
.trim()
.parse::<BigDecimal>()
.map(Value::decimal)
.map_err(|_| DogeError::value_error(format!("cannot turn {s:?} into a Decimal"))),
_ => Err(DogeError::type_error(format!(
"cannot turn {} into a Decimal",
v.describe()
))),
}
}
pub fn to_bytes(v: &Value) -> DogeResult {
match v {
Value::Bytes(_) => Ok(v.clone()),
Value::Str(s) => Ok(Value::bytes(s.as_bytes())),
Value::List(items) => {
let items = items.borrow();
let mut out = Vec::with_capacity(items.len());
for item in items.iter() {
match item {
Value::Int(n) => {
let byte = n.to_u8().ok_or_else(|| {
crate::error::DogeError::value_error(format!(
"bytes needs each Int in 0..=255, got {n}"
))
})?;
out.push(byte);
}
other => {
return Err(crate::error::DogeError::type_error(format!(
"bytes needs a List of Ints, got {} in the List",
other.describe()
)))
}
}
}
Ok(Value::bytes(out))
}
_ => Err(crate::error::DogeError::type_error(format!(
"cannot turn {} into Bytes",
v.describe()
))),
}
}
pub fn range(start: &Value, end: &Value) -> DogeResult {
match (start, end) {
(Value::Int(a), Value::Int(b)) => {
let a = a.to_i64().ok_or_else(|| range_bound_too_large(a))?;
let b = b.to_i64().ok_or_else(|| range_bound_too_large(b))?;
Ok(Value::list((a..b).map(Value::int).collect()))
}
(Value::Int(_), other) | (other, _) => Err(crate::error::DogeError::type_error(format!(
"range needs Int bounds, got {}",
other.describe()
))),
}
}
fn range_bound_too_large(n: &BigInt) -> DogeError {
DogeError::value_error(format!("range bound {n} is too large"))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::ErrorKind;
use crate::ops::values_equal;
#[test]
fn bark_returns_none() {
assert!(matches!(bark(&Value::str("much hello")), Value::None));
}
#[test]
fn len_counts_characters_and_elements() {
assert!(values_equal(
&len(&Value::str("héllo")).unwrap(),
&Value::int(5)
));
assert!(values_equal(
&len(&Value::list(vec![Value::int(1), Value::int(2)])).unwrap(),
&Value::int(2)
));
assert_eq!(len(&Value::int(3)).unwrap_err().kind, ErrorKind::TypeError);
}
#[test]
fn interp_joins_display_forms() {
let parts = [
Value::str("age "),
Value::int(7),
Value::str(", "),
Value::None,
];
assert!(matches!(interp(&parts), Value::Str(s) if &*s == "age 7, none"));
let nested = [Value::str("["), Value::str("hi"), Value::str("]")];
assert!(matches!(interp(&nested), Value::Str(s) if &*s == "[hi]"));
assert!(matches!(interp(&[]), Value::Str(s) if s.is_empty()));
}
#[test]
fn conversions_round_trip() {
assert!(matches!(to_str(&Value::int(7)), Value::Str(s) if &*s == "7"));
assert!(values_equal(
&to_int(&Value::Float(3.9)).unwrap(),
&Value::int(3)
));
assert!(values_equal(
&to_int(&Value::str(" 42 ")).unwrap(),
&Value::int(42)
));
assert!(matches!(to_float(&Value::int(4)).unwrap(), Value::Float(f) if f == 4.0));
}
#[test]
fn int_parses_arbitrary_precision_strings() {
let big = "123456789012345678901234567890";
let parsed = to_int(&Value::str(big)).unwrap();
assert_eq!(parsed.to_string(), big);
}
#[test]
fn dec_is_exact_from_str_int_and_float() {
assert_eq!(to_decimal(&Value::str("0.10")).unwrap().to_string(), "0.10");
assert!(values_equal(
&to_decimal(&Value::int(3)).unwrap(),
&to_decimal(&Value::str("3")).unwrap()
));
assert_eq!(to_decimal(&Value::Float(0.1)).unwrap().to_string(), "0.1");
assert_eq!(
to_decimal(&Value::str("nope")).unwrap_err().kind,
ErrorKind::ValueError
);
}
#[test]
fn int_truncates_a_decimal_toward_zero() {
assert!(values_equal(
&to_int(&to_decimal(&Value::str("3.9")).unwrap()).unwrap(),
&Value::int(3)
));
assert!(values_equal(
&to_int(&to_decimal(&Value::str("-3.9")).unwrap()).unwrap(),
&Value::int(-3)
));
}
#[test]
fn bad_conversions_are_catchable_value_errors() {
assert_eq!(
to_int(&Value::str("dog")).unwrap_err().kind,
ErrorKind::ValueError
);
assert_eq!(
to_float(&Value::str("woof")).unwrap_err().kind,
ErrorKind::ValueError
);
}
#[test]
fn to_bytes_from_str_list_and_bytes() {
assert!(matches!(
to_bytes(&Value::str("é")).unwrap(),
Value::Bytes(b) if b[..] == [0xc3, 0xa9]
));
let from_list = to_bytes(&Value::list(vec![Value::int(104), Value::int(105)])).unwrap();
assert!(matches!(from_list, Value::Bytes(b) if b[..] == [104, 105]));
assert!(matches!(
to_bytes(&Value::bytes([1, 2])).unwrap(),
Value::Bytes(b) if b[..] == [1, 2]
));
}
#[test]
fn to_bytes_rejects_out_of_range_and_wrong_types() {
assert_eq!(
to_bytes(&Value::list(vec![Value::int(256)]))
.unwrap_err()
.kind,
ErrorKind::ValueError
);
assert_eq!(
to_bytes(&Value::list(vec![Value::str("x")]))
.unwrap_err()
.kind,
ErrorKind::TypeError
);
assert_eq!(
to_bytes(&Value::int(1)).unwrap_err().kind,
ErrorKind::TypeError
);
}
#[test]
fn len_counts_bytes_for_bytes() {
assert!(values_equal(
&len(&Value::bytes("héllo")).unwrap(),
&Value::int(6)
));
}
#[test]
fn range_two_args() {
let xs = range(&Value::int(2), &Value::int(5)).unwrap();
match xs {
Value::List(items) => {
let items = items.borrow();
assert_eq!(items.len(), 3);
assert!(values_equal(&items[0], &Value::int(2)));
assert!(values_equal(&items[2], &Value::int(4)));
}
_ => panic!("expected a list"),
}
}
#[test]
fn range_empty_when_end_not_after_start() {
let xs = range(&Value::int(5), &Value::int(5)).unwrap();
assert!(values_equal(&len(&xs).unwrap(), &Value::int(0)));
let ys = range(&Value::int(5), &Value::int(2)).unwrap();
assert!(values_equal(&len(&ys).unwrap(), &Value::int(0)));
}
#[test]
fn range_rejects_float() {
assert_eq!(
range(&Value::int(0), &Value::Float(3.0)).unwrap_err().kind,
ErrorKind::TypeError
);
assert_eq!(
range(&Value::Float(0.0), &Value::int(3)).unwrap_err().kind,
ErrorKind::TypeError
);
}
}