use std::{ cmp::Ordering, collections::HashMap, rc::Rc, fmt::{ self, Debug, Formatter } };
use crate::{ runtime::process::Terminal,
language::{ data::{ closure::ClosureValue, callable::CallableValue, map_key::MapKey,
range::Range,
methods::BoundMethod,
types::{ EnumValue, StructValue, NamedValue,
TypeDefinition, TypeKind } },
native::NativeFunction,
bytecode::FunctionRef } };
#[derive(Clone, Debug, PartialEq)]
pub enum ExecResult
{
Value(u8),
Signaled
}
#[derive(Clone)]
pub enum Executable
{
Yes,
No,
Function(FunctionRef),
Native(Rc<NativeFunction>),
Method(Rc<BoundMethod>)
}
impl Debug for Executable
{
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result
{
match self
{
Self::Yes => write!(f, "Yes"),
Self::No => write!(f, "No"),
Self::Function(function) => write!(f, "Function({:p})", Rc::as_ptr(function)),
Self::Native(function) => write!(f, "Native({})", function.name),
Self::Method(method) => write!(f, "Method({:p})", Rc::as_ptr(method))
}
}
}
impl PartialEq for Executable
{
fn eq(&self, other: &Self) -> bool
{
match (self, other)
{
(Self::Yes, Self::Yes) | (Self::No, Self::No) => true,
(Self::Function(left), Self::Function(right)) => Rc::ptr_eq(left, right),
(Self::Native(left), Self::Native(right)) => Rc::ptr_eq(left, right),
(Self::Method(left), Self::Method(right)) => Rc::ptr_eq(left, right),
_ => false
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum Value
{
None,
Terminal(Rc<Terminal>),
Enum(Rc<EnumValue>),
Struct(Rc<StructValue>),
Named(Rc<NamedValue>),
Callable(Rc<CallableValue>),
Closure(Rc<ClosureValue>),
Type(Rc<TypeDefinition>),
ExecResult(ExecResult),
Integer(i64),
Float(f64, Option<String>),
Boolean(bool),
String(String, Executable),
Array(Rc<Vec<Value>>),
HashMap(Rc<HashMap<MapKey, Value>>),
Range(Range),
ArgumentExpansion(Rc<Vec<Value>>)
}
impl Value
{
pub fn underlying(&self) -> &Value
{
match self
{
Value::Named(item) => item.value.underlying(),
value => value,
}
}
pub fn is_callable(&self) -> bool
{
matches!(self.underlying(), Value::Closure(_) | Value::Callable(_) | Value::String(_,
Executable::Function(_) | Executable::Native(_) | Executable::Method(_)))
}
pub fn is_executable(&self) -> bool
{
self.is_callable() || matches!(self, Value::String(_, Executable::Yes))
}
pub fn type_name(&self) -> String
{
match self
{
Value::None => "None",
Value::Terminal(_) => "Terminal",
Value::Integer(_) => "Integer",
Value::Float(_, _) => "Float",
Value::Boolean(_) => "Boolean",
Value::String(_, _) => "String",
Value::Array(_) => "Array",
Value::HashMap(_) => "HashMap",
Value::Range(_) => "Range",
Value::ArgumentExpansion(_) => "ArgumentExpansion",
Value::ExecResult(_) => "ExecResult",
Value::Enum(item) => return item.definition.name.clone(),
Value::Struct(item) => return item.definition.name.clone(),
Value::Named(item) => return item.definition.name.clone(),
Value::Callable(item) => return item.prototype.name.clone(),
Value::Closure(_) => "Function",
Value::Type(_) => "Type",
}.to_string()
}
pub fn from_hash_map(values: HashMap<MapKey, Value>) -> Value
{
Value::HashMap(Rc::new(values))
}
pub fn from_array(values: Vec<Value>) -> Value
{
Value::Array(Rc::new(values))
}
pub fn from_argument_expansion(values: Vec<Value>) -> Value
{
Value::ArgumentExpansion(Rc::new(values))
}
pub fn from_string(s: String) -> Value
{
Value::String(s, Executable::No)
}
pub fn from_executable_string(s: String) -> Value
{
Value::String(s, Executable::Yes)
}
pub fn from_status_code(code: Option<i32>) -> Value
{
match code
{
Some(code) => Value::ExecResult(ExecResult::Value(code as u8)),
None => Value::ExecResult(ExecResult::Signaled),
}
}
pub fn as_text(&self) -> String
{
match self
{
Value::Named(item) => item.value.as_text(),
Value::Callable(item) => item.target.as_text(),
Value::Closure(_) => "<anonymous>".into(),
Value::Type(item) => item.name.clone(),
Value::None => "()".to_string(),
Value::Terminal(_) => "Terminal".to_string(),
Value::Enum(value) => value.to_string(),
Value::Struct(value) =>
{
let TypeKind::Struct(fields) = &value.definition.kind else { unreachable!(); };
format!(
"{}({})",
value.definition.name,
fields
.iter()
.zip(&value.fields)
.map(|(field, value)| format!(
"{}: {}",
field.name,
value.collection_text()
))
.collect::<Vec<_>>()
.join(", ")
)
},
Value::Range(range) => range.to_string(),
Value::ExecResult(code) => format!("ExecResult({})", match code
{
ExecResult::Value(v) => v.to_string(),
ExecResult::Signaled => "Signaled".to_string(),
}),
Value::Integer(i) => i.to_string(),
Value::Float(_, Some(s)) => s.clone(),
Value::Float(f, None) => format!("{f:?}"),
Value::Boolean(b) => b.to_string(),
Value::String(s, _) => s.clone(),
Value::Array(arr) => arr.iter().map(|v| v.as_text()).collect::<Vec<String>>().join(":"),
Value::ArgumentExpansion(args) => args
.iter()
.map(|v| v.as_text())
.collect::<Vec<String>>()
.join(":"),
Value::HashMap(values) =>
{
if values.is_empty() { return "[:]".to_string(); }
let mut entries: Vec<_> = values.iter().collect();
entries.sort_by(|(left, _), (right, _)| left.cmp(right));
format!(
"[{}]",
entries
.into_iter()
.map(|(key, value)| format!(
"{}: {}",
key.to_value().collection_text(),
value.collection_text()
))
.collect::<Vec<_>>()
.join(", ")
)
},
}
}
fn collection_text(&self) -> String
{
match self
{
Value::Named(item) => item.value.collection_text(),
Value::String(text, _) => format!("{:?}", text),
Value::Array(values) | Value::ArgumentExpansion(values) => format!("[{}]", values.iter()
.map(Self::collection_text).collect::<Vec<_>>().join(", ")),
value => value.as_text()
}
}
pub fn as_integer(&self) -> i64
{
match self
{
Value::Named(item) => item.value.as_integer(),
Value::Callable(_) | Value::Closure(_) | Value::Type(_) => 0,
Value::None | Value::Terminal(_) => 0,
Value::HashMap(_) | Value::Range(_) | Value::Enum(_) | Value::Struct(_) => 0,
Value::ExecResult(code) => match code
{
ExecResult::Value(v) => *v as i64,
ExecResult::Signaled => 0,
},
Value::Integer(i) => *i,
Value::Float(f, _) => *f as i64,
Value::Boolean(b) => if *b { 1 } else { 0 },
Value::String(s, _) => s.parse::<i64>().unwrap_or(0),
Value::Array(arr) => arr.iter().map(|v| v.as_integer()).sum(),
Value::ArgumentExpansion(args) => args.iter().map(|v| v.as_integer()).sum()
}
}
pub fn as_bool(&self) -> bool
{
match self
{
Value::Named(item) => item.value.as_bool(),
Value::Callable(_) | Value::Closure(_) | Value::Type(_) => true,
Value::None => false,
Value::Enum(_) | Value::Struct(_) | Value::Terminal(_) => true,
Value::Range(range) => !range.is_empty(),
Value::ExecResult(ExecResult::Value(code)) => *code == 0,
Value::ExecResult(ExecResult::Signaled) => false,
Value::Integer(value) => *value != 0,
Value::Float(value, _) => *value != 0.0,
Value::Boolean(value) => *value,
Value::String(value, _) => !value.is_empty(),
Value::Array(values) | Value::ArgumentExpansion(values) => !values.is_empty(),
Value::HashMap(values) => !values.is_empty()
}
}
pub fn equals(&self, other: &Value) -> bool
{
match (self, other)
{
(Value::Callable(left), Value::Callable(right)) => left == right,
(Value::Closure(left), Value::Closure(right)) => left == right,
(Value::Type(left), Value::Type(right)) => left == right,
(Value::Named(left), Value::Named(right)) =>
left.definition.id == right.definition.id && left.value.equals(&right.value),
(Value::None, Value::None) => true,
(Value::Terminal(left), Value::Terminal(right)) => left == right,
(Value::Enum(left), Value::Enum(right)) => left == right,
(Value::Struct(left), Value::Struct(right)) => left.definition.id == right.definition.id
&& left.fields.len() == right.fields.len()
&& left.fields.iter().zip(&right.fields).all(|(left, right)| left.equals(right)),
(Value::Range(left), Value::Range(right)) => left == right,
(Value::ExecResult(left), Value::ExecResult(right)) => left == right,
(Value::Integer(left), Value::Integer(right)) => left == right,
(Value::Float(left, _), Value::Float(right, _)) => left == right,
(Value::Integer(integer), Value::Float(float, _))
| (Value::Float(float, _), Value::Integer(integer)) =>
{
float.fract() == 0.0 && *float >= i64::MIN as f64
&& *float < -(i64::MIN as f64) && *float as i64 == *integer
},
(Value::Boolean(left), Value::Boolean(right)) => left == right,
(Value::String(left, _), Value::String(right, _)) => left == right,
(Value::HashMap(left), Value::HashMap(right)) => left.len() == right.len()
&& left.iter()
.all(|(key, value)| right.get(key).is_some_and(|other| value.equals(other))),
(Value::Array(left), Value::Array(right))
| (Value::ArgumentExpansion(left), Value::ArgumentExpansion(right)) =>
left.len() == right.len()
&& left.iter().zip(right.iter()).all(|(left, right)| left.equals(right)),
_ => false
}
}
pub fn arithmetic_error(&self) -> Option<&'static str>
{
match self
{
Value::Named(item) => item.value.arithmetic_error(),
Value::Callable(_) | Value::Closure(_) =>
Some("Functions cannot be used in arithmetic"),
Value::Type(_) => Some("Types cannot be used in arithmetic"),
Value::Integer(_) | Value::Float(_, _) => None,
Value::Terminal(_) => Some("Terminals cannot be used in arithmetic"),
Value::Enum(_) => Some("Enums cannot be used in arithmetic"),
Value::Struct(_) => Some("Structs cannot be used in arithmetic"),
Value::String(_, _) => Some("Strings cannot be used in arithmetic"),
Value::Boolean(_) => Some("Booleans cannot be used in arithmetic"),
Value::Array(_) | Value::ArgumentExpansion(_) =>
Some("Arrays cannot be used in arithmetic"),
Value::HashMap(_) => Some("Maps cannot be used in arithmetic"),
Value::Range(_) => Some("Ranges cannot be used in arithmetic"),
Value::ExecResult(_) => Some("Command statuses cannot be used in arithmetic"),
Value::None => Some("None cannot be used in arithmetic"),
}
}
pub fn numeric_order(&self, other: &Value) -> Result<Ordering, &'static str>
{
if matches!(self, Value::Named(_)) || matches!(other, Value::Named(_))
{
let (Value::Named(left), Value::Named(right)) = (self, other) else
{ return Err("Named comparisons require matching types; convert explicitly"); };
if left.definition.id != right.definition.id
{ return Err("Named comparisons require matching types; convert explicitly"); }
return left.value.numeric_order(&right.value);
}
if let (Value::Integer(left), Value::Integer(right)) = (self, other)
{ return Ok(left.cmp(right)); }
if !matches!(self, Value::Integer(_) | Value::Float(_, _))
|| !matches!(other, Value::Integer(_) | Value::Float(_, _))
{ return Err("Ordering comparisons require numbers"); }
let left = self.arithmetic_float().ok_or("Non-finite floating-point operand")?;
let right = other.arithmetic_float().ok_or("Non-finite floating-point operand")?;
Ok(left.partial_cmp(&right).unwrap())
}
pub fn arithmetic_float(&self) -> Option<f64>
{
let number = match self
{
Value::Integer(number) => *number as f64,
Value::Float(number, _) => *number,
_ => return None,
};
number.is_finite().then_some(number)
}
}