use std::cell::RefCell;
use std::collections::HashMap;
use std::net::{TcpListener, TcpStream};
use std::rc::Rc;
use std::thread::JoinHandle;
use bigdecimal::{BigDecimal, Zero};
use num_bigint::BigInt;
use crate::error::{ErrorData, ErrorKind};
use crate::ordered_map::OrderedMap;
use crate::pack::{BowlHandle, Packed, PackedError};
pub type Cell = Rc<RefCell<Value>>;
#[derive(Debug, Clone)]
pub enum Value {
Int(BigInt),
Float(f64),
Decimal(BigDecimal),
Str(Rc<str>),
Bytes(Rc<[u8]>),
Bool(bool),
None,
List(Rc<RefCell<Vec<Value>>>),
Dict(Rc<RefCell<OrderedMap>>),
Object(Rc<RefCell<ObjectData>>),
Function(Rc<FunctionData>),
Class(Rc<FunctionData>),
BoundMethod(Rc<BoundMethodData>),
Error(Rc<ErrorData>),
Socket(Rc<SocketData>),
Pup(Rc<PupData>),
Bowl(Rc<BowlData>),
}
#[derive(Debug)]
pub struct SocketData {
pub state: RefCell<SocketState>,
}
#[derive(Debug)]
pub enum SocketState {
Listener(TcpListener),
Conn { stream: TcpStream, buf: Vec<u8> },
Closed,
}
#[derive(Debug)]
pub struct PupData {
pub state: RefCell<PupState>,
}
#[derive(Debug)]
pub enum PupState {
Running(JoinHandle<Result<Packed, PackedError>>),
Fetched,
}
#[derive(Debug)]
pub struct BowlData {
pub handle: BowlHandle,
}
#[derive(Debug)]
pub struct BoundMethodData {
pub receiver: Value,
pub method: Rc<str>,
}
#[derive(Debug)]
pub struct FunctionData {
pub fn_id: u32,
pub name: Rc<str>,
pub captures: Vec<Cell>,
}
#[derive(Debug)]
pub struct ObjectData {
pub class_id: u32,
pub class_name: Rc<str>,
pub fields: HashMap<String, Value>,
}
impl Value {
pub fn int(n: impl Into<BigInt>) -> Value {
Value::Int(n.into())
}
pub fn int_lit(digits: &str) -> Value {
Value::Int(
digits
.parse()
.expect("compiler bug: emitted an invalid integer literal"),
)
}
pub fn decimal(d: BigDecimal) -> Value {
Value::Decimal(d)
}
pub fn str(s: impl AsRef<str>) -> Value {
Value::Str(Rc::from(s.as_ref()))
}
pub fn bytes(b: impl AsRef<[u8]>) -> Value {
Value::Bytes(Rc::from(b.as_ref()))
}
pub fn list(items: Vec<Value>) -> Value {
Value::List(Rc::new(RefCell::new(items)))
}
pub fn dict(entries: OrderedMap) -> Value {
Value::Dict(Rc::new(RefCell::new(entries)))
}
pub fn object(class_id: u32, class_name: &str) -> Value {
Value::Object(Rc::new(RefCell::new(ObjectData {
class_id,
class_name: Rc::from(class_name),
fields: HashMap::new(),
})))
}
pub fn error(kind: ErrorKind, message: &str, file: Rc<str>, line: u32) -> Value {
Value::Error(Rc::new(ErrorData {
kind,
message: Rc::from(message),
file,
line,
}))
}
pub fn function(fn_id: u32, name: &str, captures: Vec<Cell>) -> Value {
Value::Function(Rc::new(FunctionData {
fn_id,
name: Rc::from(name),
captures,
}))
}
pub fn bound_method(receiver: Value, method: &str) -> Value {
Value::BoundMethod(Rc::new(BoundMethodData {
receiver,
method: Rc::from(method),
}))
}
pub fn socket(state: SocketState) -> Value {
Value::Socket(Rc::new(SocketData {
state: RefCell::new(state),
}))
}
pub fn pup(handle: JoinHandle<Result<Packed, PackedError>>) -> Value {
Value::Pup(Rc::new(PupData {
state: RefCell::new(PupState::Running(handle)),
}))
}
pub fn bowl(handle: BowlHandle) -> Value {
Value::Bowl(Rc::new(BowlData { handle }))
}
pub fn class(fn_id: u32, name: &str) -> Value {
Value::Class(Rc::new(FunctionData {
fn_id,
name: Rc::from(name),
captures: Vec::new(),
}))
}
pub fn dict_from_pairs(pairs: Vec<(Value, Value)>) -> crate::error::DogeResult {
let mut entries = OrderedMap::new();
for (key, value) in pairs {
match key {
Value::Str(k) => {
entries.insert(k.to_string(), value);
}
other => {
return Err(crate::error::DogeError::type_error(format!(
"dict keys must be a Str, got {}",
other.describe()
)))
}
}
}
Ok(Value::dict(entries))
}
pub fn truthy(&self) -> bool {
match self {
Value::Int(n) => !n.is_zero(),
Value::Float(f) => *f != 0.0,
Value::Decimal(d) => !d.is_zero(),
Value::Str(s) => !s.is_empty(),
Value::Bytes(b) => !b.is_empty(),
Value::Bool(b) => *b,
Value::None => false,
Value::List(items) => !items.borrow().is_empty(),
Value::Dict(entries) => !entries.borrow().is_empty(),
Value::Object(_) => true,
Value::Function(_) => true,
Value::Class(_) => true,
Value::BoundMethod(_) => true,
Value::Error(_) => true,
Value::Socket(_) => true,
Value::Pup(_) => true,
Value::Bowl(_) => true,
}
}
pub fn type_name(&self) -> &'static str {
match self {
Value::Int(_) => "Int",
Value::Float(_) => "Float",
Value::Decimal(_) => "Decimal",
Value::Str(_) => "Str",
Value::Bytes(_) => "Bytes",
Value::Bool(_) => "Bool",
Value::None => "None",
Value::List(_) => "List",
Value::Dict(_) => "Dict",
Value::Object(_) => "Object",
Value::Function(_) => "Function",
Value::Class(_) => "Class",
Value::BoundMethod(_) => "Method",
Value::Error(_) => "Error",
Value::Socket(_) => "Socket",
Value::Pup(_) => "Pup",
Value::Bowl(_) => "Bowl",
}
}
pub fn describe(&self) -> String {
let name = self.type_name();
let article = match name.chars().next() {
Some('A' | 'E' | 'I' | 'O' | 'U') => "an",
_ => "a",
};
format!("{article} {name}")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn truthiness_follows_python() {
assert!(!Value::int(0).truthy());
assert!(Value::int(1).truthy());
assert!(!Value::Float(0.0).truthy());
assert!(Value::Float(0.1).truthy());
assert!(!Value::decimal(BigDecimal::from(0)).truthy());
assert!(Value::decimal(BigDecimal::from(1)).truthy());
assert!(!Value::str("").truthy());
assert!(Value::str("dog").truthy());
assert!(!Value::Bool(false).truthy());
assert!(Value::Bool(true).truthy());
assert!(!Value::None.truthy());
assert!(!Value::list(vec![]).truthy());
assert!(Value::list(vec![Value::int(1)]).truthy());
assert!(!Value::dict(OrderedMap::new()).truthy());
assert!(Value::object(0, "Shibe").truthy());
assert!(Value::function(0, "greet", vec![]).truthy());
}
#[test]
fn type_names_match_design() {
assert_eq!(Value::int(1).type_name(), "Int");
assert_eq!(Value::Float(1.0).type_name(), "Float");
assert_eq!(Value::decimal(BigDecimal::from(1)).type_name(), "Decimal");
assert_eq!(Value::str("x").type_name(), "Str");
assert_eq!(Value::Bool(true).type_name(), "Bool");
assert_eq!(Value::None.type_name(), "None");
assert_eq!(Value::list(vec![]).type_name(), "List");
assert_eq!(Value::dict(OrderedMap::new()).type_name(), "Dict");
assert_eq!(Value::object(0, "Shibe").type_name(), "Object");
assert_eq!(Value::function(0, "greet", vec![]).type_name(), "Function");
}
#[test]
fn describe_uses_the_right_article() {
assert_eq!(Value::int(1).describe(), "an Int");
assert_eq!(Value::str("x").describe(), "a Str");
assert_eq!(Value::None.describe(), "a None");
}
#[test]
fn dict_from_pairs_last_duplicate_wins() {
let d = Value::dict_from_pairs(vec![
(Value::str("k"), Value::int(1)),
(Value::str("k"), Value::int(2)),
])
.unwrap();
match d {
Value::Dict(entries) => {
let entries = entries.borrow();
assert_eq!(entries.len(), 1);
match entries.get("k") {
Some(Value::Int(n)) => assert_eq!(n, &BigInt::from(2)),
_ => panic!("expected Int 2"),
}
}
_ => panic!("expected a dict"),
}
}
#[test]
fn dict_from_pairs_rejects_non_str_key() {
let err = Value::dict_from_pairs(vec![(Value::int(1), Value::int(2))]).unwrap_err();
assert_eq!(err.kind, crate::error::ErrorKind::TypeError);
}
#[test]
fn str_constructor_shares_via_rc() {
let a = Value::str("kabosu");
let b = a.clone();
match (&a, &b) {
(Value::Str(x), Value::Str(y)) => assert!(Rc::ptr_eq(x, y)),
_ => panic!("expected two Str values"),
}
}
}