use core::fmt;
use std::rc::Rc;
use crate::{
ast::AstNode,
binop::{BinOp, Comparison},
builtins::BuiltinFn,
scope::ScopePtr,
thunk::Thunk,
Error, Result,
};
#[derive(Debug, Clone)]
pub enum Value {
Object(ScopePtr),
Patch(Thunk),
Array(Vec<Thunk>),
Number(f64),
String(String),
Boolean(bool),
FunctionCall {
f: Thunk,
args: Vec<Thunk>,
},
BuiltinFn(BuiltinFn),
Lambda {
scope: ScopePtr,
arg_names: Vec<String>,
expr: Rc<AstNode>,
},
BinOp {
kind: BinOp,
left: Thunk,
right: Thunk,
},
Name(ScopePtr, String),
Attribute {
value: Thunk,
attr: String,
},
Null,
}
impl Value {
pub fn is_primitive(&self) -> bool {
matches!(
self,
Value::Object(..)
| Value::Array(..)
| Value::Patch(..)
| Value::Number(..)
| Value::String(..)
| Value::Boolean(..)
| Value::Null
| Value::Lambda { .. }
| Value::BuiltinFn(..)
)
}
pub fn at(&self, index: usize) -> Result<Rc<Value>> {
match self {
Value::Array(values) => match values.get(index) {
Some(thunk) => thunk.evaluate(),
None => Err(Error::IndexError(index, self.clone().into())),
},
_ => Err(Error::TypeError(
"Can't index into non-array".into(),
self.clone().into(),
)),
}
}
pub fn attr(&self, attr: &str) -> Result<Rc<Value>> {
match self {
Value::Object(scope) => match scope.get(&attr.into()) {
Some(thunk) => thunk.evaluate(),
None => Err(Error::NoSuchAttribute(scope.clone(), attr.into())),
},
_ => Err(Error::TypeError(
"Can't call attr on non-object".into(),
self.clone().into(),
)),
}
}
pub fn str(&self) -> Result<&str> {
match self {
Value::String(s) => Ok(s.as_str()),
_ => Err(Error::TypeError(
"Can't convert non-string value to str".into(),
self.clone().into(),
)),
}
}
pub fn number(&self) -> Result<f64> {
match self {
Value::Number(s) => Ok(*s),
_ => Err(Error::TypeError(
"Can't convert non-number value to number".into(),
self.clone().into(),
)),
}
}
pub fn bool(&self) -> Result<bool> {
match self {
Value::Boolean(b) => Ok(*b),
_ => Err(Error::TypeError(
"Can't convert non-bool value to bool".into(),
self.clone().into(),
)),
}
}
pub fn is_null(&self) -> bool {
matches!(self, Value::Null)
}
fn pretty_format_items(
&self,
items: Vec<String>,
indent: usize,
max_width: usize,
f: &mut fmt::Formatter<'_>,
) -> fmt::Result {
let total_length: usize = items.iter().map(|s| s.len()).sum();
let wraps = items.iter().any(|s| s.contains('\n'));
let single_line_length = indent + total_length + 2 * items.len() + 1;
if !wraps && single_line_length < max_width {
write!(f, "{}", items.join(", "))?;
} else {
let indent = " ".repeat(indent);
for item in items {
for line in item.split('\n') {
write!(f, "\n{indent} {line}")?;
}
write!(f, ",")?;
}
write!(f, "\n{indent}")?;
}
Ok(())
}
pub fn pretty_indented(
&self,
indent: usize,
_color: bool,
f: &mut fmt::Formatter<'_>,
) -> fmt::Result {
let max_width = f.width().unwrap_or(80);
match self {
Value::Number(n) => write!(f, "{n}"),
Value::Boolean(b) => write!(f, "{b}"),
Value::Null => write!(f, "null"),
Value::String(s) => write!(f, "\"{s}\""),
Value::Lambda { .. } => write!(f, "<lambda>"),
Value::BuiltinFn(BuiltinFn(name, ..)) => write!(f, "<builtin: {name}>"),
Value::Patch(thunk) => {
write!(f, "&")?;
match thunk.evaluate() {
Ok(v) => write!(f, "{}", v),
Err(e) => write!(f, "Error<{e:?}>"),
}
}
Value::Object(scope) => {
let values = scope.values();
let mut keys = values.keys().collect::<Vec<_>>();
keys.sort();
let pair_strs = keys
.iter()
.map(|k| {
format!("{k}: {}", scope.get(k).unwrap())
})
.collect::<Vec<_>>();
write!(f, "{{")?;
self.pretty_format_items(pair_strs, indent, max_width, f)?;
write!(f, "}}")
}
Value::Array(values) => {
let strs = values
.iter()
.map(|thunk| format!("{thunk}"))
.collect::<Vec<_>>();
write!(f, "[")?;
self.pretty_format_items(strs, indent, max_width, f)?;
write!(f, "]")
}
_ => write!(f, "<pending: {:?}>", self),
}
}
pub fn pretty(&self, color: bool, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.pretty_indented(0, color, f)
}
}
impl fmt::Display for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.pretty(false, f)
}
}
impl std::cmp::PartialEq<Value> for Value {
fn eq(&self, other: &Value) -> bool {
Comparison::Equal
.compare_values(self, other)
.unwrap_or(false)
}
}