use crate::{ComputationGraph, JitError, JitResult, Node, NodeId};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
pub struct ConstantEvaluator {
config: ConstEvalConfig,
constant_cache: Arc<RwLock<HashMap<NodeId, ConstantValue>>>,
evaluation_context: EvaluationContext,
}
#[derive(Debug, Clone)]
pub struct ConstEvalConfig {
pub enable_constant_folding: bool,
pub enable_dead_code_elimination: bool,
pub enable_branch_elimination: bool,
pub enable_loop_unrolling: bool,
pub max_evaluation_steps: usize,
pub max_memory_usage: usize,
pub enable_aggressive_propagation: bool,
pub max_recursion_depth: usize,
pub cache_size: usize,
}
impl Default for ConstEvalConfig {
fn default() -> Self {
Self {
enable_constant_folding: true,
enable_dead_code_elimination: true,
enable_branch_elimination: true,
enable_loop_unrolling: true,
max_evaluation_steps: 10000,
max_memory_usage: 64 * 1024 * 1024, enable_aggressive_propagation: false,
max_recursion_depth: 100,
cache_size: 1000,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ConstantValue {
Bool(bool),
Int(i64),
UInt(u64),
Float(f64),
String(String),
Array(Vec<ConstantValue>),
Tensor {
shape: Vec<usize>,
data: Vec<f64>,
dtype: String,
},
Complex { real: f64, imag: f64 },
None,
Undefined,
}
#[derive(Debug, Clone)]
pub struct EvaluationContext {
variables: HashMap<String, ConstantValue>,
functions: HashMap<String, FunctionDefinition>,
depth: usize,
steps: usize,
memory_usage: usize,
}
#[derive(Debug, Clone)]
pub struct FunctionDefinition {
pub name: String,
pub parameters: Vec<String>,
pub body: Vec<Instruction>,
pub return_type: Option<String>,
}
#[derive(Debug, Clone)]
pub enum Instruction {
LoadConstant(ConstantValue),
LoadVariable(String),
Store(String),
BinaryOp {
op: BinaryOperator,
left: Box<Instruction>,
right: Box<Instruction>,
},
UnaryOp {
op: UnaryOperator,
operand: Box<Instruction>,
},
Call {
function: String,
args: Vec<Instruction>,
},
Conditional {
condition: Box<Instruction>,
then_branch: Box<Instruction>,
else_branch: Box<Instruction>,
},
Loop {
condition: Box<Instruction>,
body: Vec<Instruction>,
max_iterations: Option<usize>,
},
Index {
array: Box<Instruction>,
index: Box<Instruction>,
},
Array(Vec<Instruction>),
Tensor {
shape: Vec<usize>,
data: Vec<Instruction>,
},
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum BinaryOperator {
Add,
Sub,
Mul,
Div,
Mod,
Pow,
And,
Or,
Xor,
Lt,
Le,
Gt,
Ge,
Eq,
Ne,
BitAnd,
BitOr,
BitXor,
Shl,
Shr,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum UnaryOperator {
Neg,
Not,
BitNot,
Abs,
Sin,
Cos,
Tan,
Log,
Exp,
Sqrt,
Floor,
Ceil,
Round,
}
#[derive(Debug, Clone)]
pub struct EvaluationResult {
pub constants_found: Vec<(NodeId, ConstantValue)>,
pub dead_code_nodes: Vec<NodeId>,
pub eliminable_branches: Vec<NodeId>,
pub unrollable_loops: Vec<(NodeId, usize)>,
pub propagation_opportunities: Vec<PropagationOpportunity>,
}
#[derive(Debug, Clone)]
pub struct PropagationOpportunity {
pub from_node: NodeId,
pub to_nodes: Vec<NodeId>,
pub constant_value: ConstantValue,
pub estimated_benefit: f64,
}
impl ConstantEvaluator {
pub fn new(config: ConstEvalConfig) -> Self {
Self {
config,
constant_cache: Arc::new(RwLock::new(HashMap::new())),
evaluation_context: EvaluationContext::new(),
}
}
pub fn evaluate_constants(&mut self, graph: &ComputationGraph) -> JitResult<EvaluationResult> {
let mut result = EvaluationResult {
constants_found: Vec::new(),
dead_code_nodes: Vec::new(),
eliminable_branches: Vec::new(),
unrollable_loops: Vec::new(),
propagation_opportunities: Vec::new(),
};
self.evaluation_context.reset();
let sorted_nodes = graph
.topological_sort()
.map_err(|e| JitError::CompilationError(format!("{:?}", e)))?;
for node_id in sorted_nodes {
if let Some(node) = graph.node(node_id) {
if let Some(constant_value) = self.try_evaluate_node(node, node_id)? {
result
.constants_found
.push((node_id, constant_value.clone()));
if let Ok(mut cache) = self.constant_cache.write() {
cache.insert(node_id, constant_value.clone());
}
result.propagation_opportunities.extend(
self.analyze_propagation_opportunities(graph, node_id, &constant_value)?,
);
}
if self.is_dead_code(node)? {
result.dead_code_nodes.push(node_id);
}
if self.is_eliminable_branch(node)? {
result.eliminable_branches.push(node_id);
}
if let Some(iterations) = self.get_unroll_count(node)? {
result.unrollable_loops.push((node_id, iterations));
}
}
}
Ok(result)
}
pub fn apply_optimizations(
&self,
graph: &mut ComputationGraph,
result: &EvaluationResult,
) -> JitResult<usize> {
let mut optimizations_applied = 0;
if self.config.enable_constant_folding {
optimizations_applied += self.apply_constant_folding(graph, &result.constants_found)?;
}
if self.config.enable_dead_code_elimination {
optimizations_applied +=
self.apply_dead_code_elimination(graph, &result.dead_code_nodes)?;
}
if self.config.enable_branch_elimination {
optimizations_applied +=
self.apply_branch_elimination(graph, &result.eliminable_branches)?;
}
if self.config.enable_loop_unrolling {
optimizations_applied += self.apply_loop_unrolling(graph, &result.unrollable_loops)?;
}
if self.config.enable_aggressive_propagation {
optimizations_applied +=
self.apply_constant_propagation(graph, &result.propagation_opportunities)?;
}
Ok(optimizations_applied)
}
fn try_evaluate_node(
&mut self,
node: &Node,
node_id: NodeId,
) -> JitResult<Option<ConstantValue>> {
if let Ok(cache) = self.constant_cache.read() {
if let Some(cached_value) = cache.get(&node_id) {
return Ok(Some(cached_value.clone()));
}
}
if self.evaluation_context.steps >= self.config.max_evaluation_steps {
return Ok(None);
}
if self.evaluation_context.depth >= self.config.max_recursion_depth {
return Ok(None);
}
if self.evaluation_context.memory_usage >= self.config.max_memory_usage {
return Ok(None);
}
self.evaluation_context.steps += 1;
self.evaluation_context.depth += 1;
let result = match node.operation_type() {
"constant" => self.evaluate_constant_node(node),
"add" => self.evaluate_binary_op(node, BinaryOperator::Add),
"sub" => self.evaluate_binary_op(node, BinaryOperator::Sub),
"mul" => self.evaluate_binary_op(node, BinaryOperator::Mul),
"div" => self.evaluate_binary_op(node, BinaryOperator::Div),
"pow" => self.evaluate_binary_op(node, BinaryOperator::Pow),
"neg" => self.evaluate_unary_op(node, UnaryOperator::Neg),
"abs" => self.evaluate_unary_op(node, UnaryOperator::Abs),
"sin" => self.evaluate_unary_op(node, UnaryOperator::Sin),
"cos" => self.evaluate_unary_op(node, UnaryOperator::Cos),
"exp" => self.evaluate_unary_op(node, UnaryOperator::Exp),
"log" => self.evaluate_unary_op(node, UnaryOperator::Log),
"sqrt" => self.evaluate_unary_op(node, UnaryOperator::Sqrt),
_ => Ok(None), };
self.evaluation_context.depth -= 1;
result
}
fn evaluate_constant_node(&self, node: &Node) -> JitResult<Option<ConstantValue>> {
if let Some(value_attr) = node.get_attribute("value") {
let value_str = match value_attr {
crate::graph::Attribute::String(s) => s,
crate::graph::Attribute::Int(i) => return Ok(Some(ConstantValue::Int(*i))),
crate::graph::Attribute::Float(f) => return Ok(Some(ConstantValue::Float(*f))),
crate::graph::Attribute::Bool(b) => return Ok(Some(ConstantValue::Bool(*b))),
_ => return Ok(None),
};
if value_str == "true" {
Ok(Some(ConstantValue::Bool(true)))
} else if value_str == "false" {
Ok(Some(ConstantValue::Bool(false)))
} else if let Ok(int_val) = value_str.parse::<i64>() {
Ok(Some(ConstantValue::Int(int_val)))
} else if let Ok(float_val) = value_str.parse::<f64>() {
Ok(Some(ConstantValue::Float(float_val)))
} else {
Ok(Some(ConstantValue::String(value_str.clone())))
}
} else {
Ok(None)
}
}
fn evaluate_binary_op(
&mut self,
_node: &Node,
_op: BinaryOperator,
) -> JitResult<Option<ConstantValue>> {
Ok(None)
}
fn evaluate_unary_op(
&mut self,
_node: &Node,
_op: UnaryOperator,
) -> JitResult<Option<ConstantValue>> {
Ok(None)
}
fn apply_binary_operation(
&self,
op: BinaryOperator,
left: &ConstantValue,
right: &ConstantValue,
) -> JitResult<Option<ConstantValue>> {
match (left, right) {
(ConstantValue::Int(a), ConstantValue::Int(b)) => {
let result = match op {
BinaryOperator::Add => ConstantValue::Int(a + b),
BinaryOperator::Sub => ConstantValue::Int(a - b),
BinaryOperator::Mul => ConstantValue::Int(a * b),
BinaryOperator::Div => {
if *b != 0 {
ConstantValue::Int(a / b)
} else {
return Ok(None); }
}
BinaryOperator::Mod => {
if *b != 0 {
ConstantValue::Int(a % b)
} else {
return Ok(None); }
}
BinaryOperator::Pow => ConstantValue::Float((*a as f64).powf(*b as f64)),
BinaryOperator::Lt => ConstantValue::Bool(a < b),
BinaryOperator::Le => ConstantValue::Bool(a <= b),
BinaryOperator::Gt => ConstantValue::Bool(a > b),
BinaryOperator::Ge => ConstantValue::Bool(a >= b),
BinaryOperator::Eq => ConstantValue::Bool(a == b),
BinaryOperator::Ne => ConstantValue::Bool(a != b),
BinaryOperator::BitAnd => ConstantValue::Int(a & b),
BinaryOperator::BitOr => ConstantValue::Int(a | b),
BinaryOperator::BitXor => ConstantValue::Int(a ^ b),
_ => return Ok(None),
};
Ok(Some(result))
}
(ConstantValue::Float(a), ConstantValue::Float(b)) => {
let result = match op {
BinaryOperator::Add => ConstantValue::Float(a + b),
BinaryOperator::Sub => ConstantValue::Float(a - b),
BinaryOperator::Mul => ConstantValue::Float(a * b),
BinaryOperator::Div => {
if *b != 0.0 {
ConstantValue::Float(a / b)
} else {
return Ok(None); }
}
BinaryOperator::Pow => ConstantValue::Float(a.powf(*b)),
BinaryOperator::Lt => ConstantValue::Bool(a < b),
BinaryOperator::Le => ConstantValue::Bool(a <= b),
BinaryOperator::Gt => ConstantValue::Bool(a > b),
BinaryOperator::Ge => ConstantValue::Bool(a >= b),
BinaryOperator::Eq => ConstantValue::Bool((a - b).abs() < f64::EPSILON),
BinaryOperator::Ne => ConstantValue::Bool((a - b).abs() >= f64::EPSILON),
_ => return Ok(None),
};
Ok(Some(result))
}
(ConstantValue::Bool(a), ConstantValue::Bool(b)) => {
let result = match op {
BinaryOperator::And => ConstantValue::Bool(*a && *b),
BinaryOperator::Or => ConstantValue::Bool(*a || *b),
BinaryOperator::Xor => ConstantValue::Bool(*a ^ *b),
BinaryOperator::Eq => ConstantValue::Bool(a == b),
BinaryOperator::Ne => ConstantValue::Bool(a != b),
_ => return Ok(None),
};
Ok(Some(result))
}
(ConstantValue::Int(a), ConstantValue::Float(_b)) => {
self.apply_binary_operation(op, &ConstantValue::Float(*a as f64), right)
}
(ConstantValue::Float(_a), ConstantValue::Int(b)) => {
self.apply_binary_operation(op, left, &ConstantValue::Float(*b as f64))
}
_ => Ok(None), }
}
fn apply_unary_operation(
&self,
op: UnaryOperator,
value: &ConstantValue,
) -> JitResult<Option<ConstantValue>> {
match value {
ConstantValue::Int(a) => {
let result = match op {
UnaryOperator::Neg => ConstantValue::Int(-a),
UnaryOperator::Abs => ConstantValue::Int(a.abs()),
UnaryOperator::BitNot => ConstantValue::Int(!a),
_ => return Ok(None),
};
Ok(Some(result))
}
ConstantValue::Float(a) => {
let result = match op {
UnaryOperator::Neg => ConstantValue::Float(-a),
UnaryOperator::Abs => ConstantValue::Float(a.abs()),
UnaryOperator::Sin => ConstantValue::Float(a.sin()),
UnaryOperator::Cos => ConstantValue::Float(a.cos()),
UnaryOperator::Tan => ConstantValue::Float(a.tan()),
UnaryOperator::Log => {
if *a > 0.0 {
ConstantValue::Float(a.ln())
} else {
return Ok(None); }
}
UnaryOperator::Exp => ConstantValue::Float(a.exp()),
UnaryOperator::Sqrt => {
if *a >= 0.0 {
ConstantValue::Float(a.sqrt())
} else {
return Ok(None); }
}
UnaryOperator::Floor => ConstantValue::Float(a.floor()),
UnaryOperator::Ceil => ConstantValue::Float(a.ceil()),
UnaryOperator::Round => ConstantValue::Float(a.round()),
_ => return Ok(None),
};
Ok(Some(result))
}
ConstantValue::Bool(a) => {
let result = match op {
UnaryOperator::Not => ConstantValue::Bool(!a),
_ => return Ok(None),
};
Ok(Some(result))
}
_ => Ok(None),
}
}
fn is_dead_code(&self, node: &Node) -> JitResult<bool> {
if node.has_side_effects() {
return Ok(false);
}
Ok(false)
}
fn is_eliminable_branch(&self, node: &Node) -> JitResult<bool> {
if node.operation_type() != "branch" && node.operation_type() != "if" {
return Ok(false);
}
Ok(false)
}
fn get_unroll_count(&self, node: &Node) -> JitResult<Option<usize>> {
if node.operation_type() != "loop" && node.operation_type() != "for" {
return Ok(None);
}
if let Some(iterations_attr) = node.get_attribute("iterations") {
let iterations = match iterations_attr {
crate::graph::Attribute::Int(i) => *i as usize,
crate::graph::Attribute::String(s) => {
if let Ok(val) = s.parse::<usize>() {
val
} else {
return Ok(None);
}
}
_ => return Ok(None),
};
if iterations <= 16 {
return Ok(Some(iterations));
}
}
Ok(None)
}
fn analyze_propagation_opportunities(
&self,
graph: &ComputationGraph,
constant_node_id: NodeId,
constant_value: &ConstantValue,
) -> JitResult<Vec<PropagationOpportunity>> {
let mut opportunities = Vec::new();
if let Some(_constant_node) = graph.get_node(constant_node_id) {
let outputs = graph.get_node_outputs(constant_node_id);
let mut to_nodes = Vec::new();
for output_id in outputs {
if let Some(output_node) = graph.get_node(output_id) {
if self.can_benefit_from_constant(output_node, constant_value) {
to_nodes.push(output_id);
}
}
}
if !to_nodes.is_empty() {
let estimated_benefit =
self.estimate_propagation_benefit(&to_nodes, constant_value);
opportunities.push(PropagationOpportunity {
from_node: constant_node_id,
to_nodes,
constant_value: constant_value.clone(),
estimated_benefit,
});
}
}
Ok(opportunities)
}
fn can_benefit_from_constant(&self, node: &Node, _constant_value: &ConstantValue) -> bool {
match node.operation_type() {
"add" | "sub" | "mul" | "div" | "pow" => true,
"branch" | "if" => true,
"loop" | "for" => true,
_ => false,
}
}
fn estimate_propagation_benefit(
&self,
_to_nodes: &[NodeId],
_constant_value: &ConstantValue,
) -> f64 {
0.1 * _to_nodes.len() as f64
}
fn apply_constant_folding(
&self,
graph: &mut ComputationGraph,
constants: &[(NodeId, ConstantValue)],
) -> JitResult<usize> {
let mut applied = 0;
for (node_id, constant_value) in constants {
if let Some(node) = graph.get_node_mut(*node_id) {
let graph_constant_value = match constant_value {
ConstantValue::Int(i) => crate::graph::ConstantValue::IntScalar(*i),
ConstantValue::Float(f) => crate::graph::ConstantValue::Scalar(*f),
_ => crate::graph::ConstantValue::Scalar(0.0), };
node.op = crate::graph::Operation::Constant(crate::graph::ConstantInfo {
value: graph_constant_value,
});
node.set_attribute(
"value".to_string(),
match constant_value {
ConstantValue::Bool(b) => crate::graph::Attribute::Bool(*b),
ConstantValue::Int(i) => crate::graph::Attribute::Int(*i),
ConstantValue::Float(f) => crate::graph::Attribute::Float(*f),
ConstantValue::String(s) => crate::graph::Attribute::String(s.clone()),
_ => crate::graph::Attribute::String(constant_value.to_string()),
},
);
applied += 1;
}
}
Ok(applied)
}
fn apply_dead_code_elimination(
&self,
graph: &mut ComputationGraph,
dead_nodes: &[NodeId],
) -> JitResult<usize> {
let mut applied = 0;
for &node_id in dead_nodes {
if graph.remove_node(node_id).is_some() {
applied += 1;
}
}
Ok(applied)
}
fn apply_branch_elimination(
&self,
graph: &mut ComputationGraph,
eliminable_branches: &[NodeId],
) -> JitResult<usize> {
let mut applied = 0;
for &node_id in eliminable_branches {
if let Some(_node) = graph.node(node_id) {
let inputs = graph.get_node_inputs(node_id);
if !inputs.is_empty() {
if let Ok(cache) = self.constant_cache.read() {
if let Some(ConstantValue::Bool(condition)) = cache.get(&inputs[0]) {
let branch_index = if *condition { 1 } else { 2 };
if inputs.len() > branch_index {
match graph.replace_node_with_input(node_id, inputs[branch_index]) {
Ok(_) => {
log::debug!(
"Successfully eliminated branch by replacing with {}",
if *condition { "true" } else { "false" }
);
applied += 1;
}
Err(e) => {
log::warn!("Failed to eliminate branch: {}", e);
}
}
}
}
}
}
}
}
Ok(applied)
}
fn apply_loop_unrolling(
&self,
graph: &mut ComputationGraph,
unrollable_loops: &[(NodeId, usize)],
) -> JitResult<usize> {
let mut applied = 0;
for &(node_id, iterations) in unrollable_loops {
if let Some(loop_node) = graph.get_node(node_id) {
if let Some(loop_body) = loop_node.get_attribute("body") {
let body_str = match loop_body {
crate::graph::Attribute::String(s) => s,
_ => continue,
};
let unrolled_body = self.create_unrolled_body(body_str, iterations)?;
match graph.replace_node_with_sequence(node_id, &unrolled_body) {
Ok(_) => {
log::debug!(
"Successfully unrolled loop with {} iterations",
iterations
);
applied += 1;
}
Err(e) => {
log::warn!("Failed to unroll loop: {}", e);
}
}
}
}
}
Ok(applied)
}
fn apply_constant_propagation(
&self,
_graph: &mut ComputationGraph,
_opportunities: &[PropagationOpportunity],
) -> JitResult<usize> {
Ok(0)
}
fn create_unrolled_body(
&self,
_loop_body: &str,
iterations: usize,
) -> JitResult<Vec<crate::Node>> {
use crate::graph::{Node, Operation};
use torsh_core::{DType, DeviceType, Shape};
let mut unrolled_nodes = Vec::new();
for i in 0..iterations {
let node = Node::new(Operation::Input, format!("unrolled_iter_{}", i))
.with_output_shapes(vec![Some(Shape::new(vec![1]))])
.with_dtypes(vec![DType::F32])
.with_device(DeviceType::Cpu);
unrolled_nodes.push(node);
}
Ok(unrolled_nodes)
}
}
impl EvaluationContext {
fn new() -> Self {
Self {
variables: HashMap::new(),
functions: HashMap::new(),
depth: 0,
steps: 0,
memory_usage: 0,
}
}
fn reset(&mut self) {
self.variables.clear();
self.depth = 0;
self.steps = 0;
self.memory_usage = 0;
}
}
impl ConstantValue {
pub fn to_string(&self) -> String {
match self {
ConstantValue::Bool(b) => b.to_string(),
ConstantValue::Int(i) => i.to_string(),
ConstantValue::UInt(u) => u.to_string(),
ConstantValue::Float(f) => f.to_string(),
ConstantValue::String(s) => s.clone(),
ConstantValue::Array(arr) => {
format!(
"[{}]",
arr.iter()
.map(|v| v.to_string())
.collect::<Vec<_>>()
.join(", ")
)
}
ConstantValue::Tensor { shape, data, dtype } => {
format!("Tensor({:?}, {:?}, {})", shape, data, dtype)
}
ConstantValue::Complex { real, imag } => {
format!("{}+{}i", real, imag)
}
ConstantValue::None => "None".to_string(),
ConstantValue::Undefined => "Undefined".to_string(),
}
}
pub fn is_truthy(&self) -> bool {
match self {
ConstantValue::Bool(b) => *b,
ConstantValue::Int(i) => *i != 0,
ConstantValue::UInt(u) => *u != 0,
ConstantValue::Float(f) => *f != 0.0,
ConstantValue::String(s) => !s.is_empty(),
ConstantValue::Array(arr) => !arr.is_empty(),
ConstantValue::Tensor { data, .. } => !data.is_empty(),
ConstantValue::Complex { real, imag } => *real != 0.0 || *imag != 0.0,
ConstantValue::None => false,
ConstantValue::Undefined => false,
}
}
pub fn type_name(&self) -> &'static str {
match self {
ConstantValue::Bool(_) => "bool",
ConstantValue::Int(_) => "int",
ConstantValue::UInt(_) => "uint",
ConstantValue::Float(_) => "float",
ConstantValue::String(_) => "string",
ConstantValue::Array(_) => "array",
ConstantValue::Tensor { .. } => "tensor",
ConstantValue::Complex { .. } => "complex",
ConstantValue::None => "none",
ConstantValue::Undefined => "undefined",
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_constant_evaluator_creation() {
let config = ConstEvalConfig::default();
let evaluator = ConstantEvaluator::new(config);
assert!(evaluator.config.enable_constant_folding);
}
#[test]
fn test_binary_operations() {
let evaluator = ConstantEvaluator::new(ConstEvalConfig::default());
let left = ConstantValue::Int(5);
let right = ConstantValue::Int(3);
let result = evaluator
.apply_binary_operation(BinaryOperator::Add, &left, &right)
.unwrap()
.unwrap();
assert_eq!(result, ConstantValue::Int(8));
let result = evaluator
.apply_binary_operation(BinaryOperator::Mul, &left, &right)
.unwrap()
.unwrap();
assert_eq!(result, ConstantValue::Int(15));
}
#[test]
fn test_unary_operations() {
let evaluator = ConstantEvaluator::new(ConstEvalConfig::default());
let value = ConstantValue::Float(4.0);
let result = evaluator
.apply_unary_operation(UnaryOperator::Sqrt, &value)
.unwrap()
.unwrap();
assert_eq!(result, ConstantValue::Float(2.0));
let value = ConstantValue::Int(-5);
let result = evaluator
.apply_unary_operation(UnaryOperator::Abs, &value)
.unwrap()
.unwrap();
assert_eq!(result, ConstantValue::Int(5));
}
#[test]
fn test_constant_value_operations() {
let bool_val = ConstantValue::Bool(true);
assert!(bool_val.is_truthy());
assert_eq!(bool_val.type_name(), "bool");
let int_val = ConstantValue::Int(42);
assert_eq!(int_val.to_string(), "42");
let float_val = ConstantValue::Float(3.14);
assert_eq!(float_val.type_name(), "float");
}
#[test]
fn test_evaluation_context() {
let mut context = EvaluationContext::new();
assert_eq!(context.depth, 0);
assert_eq!(context.steps, 0);
context.depth = 5;
context.steps = 100;
context.reset();
assert_eq!(context.depth, 0);
assert_eq!(context.steps, 0);
}
}