#![allow(missing_docs)]
use super::{Effect, EffectContext, MonadicValue, IOAction, StateAction, ErrorAction};
use crate::diagnostics::{Error as DiagnosticError, Result};
use crate::eval::value::{Value, ThreadSafeEnvironment};
use std::collections::HashMap;
use std::fmt;
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct EffectLifter {
lifting_rules: HashMap<String, LiftingRule>,
context: EffectContext,
enabled: bool,
lift_cache: HashMap<String, MonadicValue>,
}
#[derive(Debug, Clone)]
pub struct LiftingRule {
target_effect: Effect,
transformation: LiftingTransformation,
conditions: Vec<LiftingCondition>,
priority: u32,
}
#[derive(Debug, Clone)]
pub enum LiftingTransformation {
Direct,
TransformArgs,
Custom,
MapTo(String),
}
#[derive(Debug, Clone)]
pub enum LiftingCondition {
Always,
OperationName(String),
OperationPattern(String),
HasEffect(Vec<Effect>),
HasAllEffects(Vec<Effect>),
ArgumentCount(usize),
ArgumentRange(usize, usize),
FirstArgType(ValueType),
Custom,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ValueType {
Number,
String,
Symbol,
Boolean,
Pair,
Nil,
Procedure,
Port,
Any,
}
pub struct BuiltinLiftingRules;
#[derive(Debug, Clone)]
#[derive(Default)]
pub struct LiftingSystemConfig {
pub enable_auto_lifting: bool,
pub enable_caching: bool,
pub max_cache_size: usize,
pub warn_on_lift: bool,
pub custom_rules: HashMap<String, LiftingRule>,
}
impl EffectLifter {
pub fn new() -> Self {
let mut lifter = Self {
lifting_rules: HashMap::new(),
context: EffectContext::pure(),
enabled: true,
lift_cache: HashMap::new(),
};
lifter.register_builtin_rules();
lifter
}
pub fn with_config(config: LiftingSystemConfig) -> Self {
let mut lifter = Self::new();
lifter.enabled = config.enable_auto_lifting;
for (name, rule) in config.custom_rules {
lifter.add_rule(name, rule);
}
lifter
}
pub fn add_rule(&mut self, operation: String, rule: LiftingRule) {
self.lifting_rules.insert(operation, rule);
}
pub fn remove_rule(&mut self, operation: &str) -> Option<LiftingRule> {
self.lifting_rules.remove(operation)
}
pub fn set_context(&mut self, context: EffectContext) {
self.context = context;
}
pub fn lift_operation(
&mut self,
operation: &str,
args: &[Value]
) -> Option<MonadicValue> {
if !self.enabled {
return None;
}
let cache_key = format!("{operation}:{}", args.len());
if let Some(cached) = self.lift_cache.get(&cache_key) {
return Some(cached.clone());
}
let mut applicable_rules = Vec::new();
for (name, rule) in &self.lifting_rules {
if self.rule_applies(name, rule, operation, args) {
applicable_rules.push((name.clone(), rule.clone()));
}
}
applicable_rules.sort_by(|a, b| b.1.priority.cmp(&a.1.priority));
if let Some((_, rule)) = applicable_rules.first() {
match self.apply_lifting_rule(rule, operation, args) {
Ok(lifted) => {
self.lift_cache.insert(cache_key, lifted.clone());
Some(lifted)
},
Err(_) => None,
}
} else {
None
}
}
fn rule_applies(
&self,
rule_name: &str,
rule: &LiftingRule,
operation: &str,
args: &[Value]
) -> bool {
if rule_name == operation || rule.conditions.is_empty() {
return rule.conditions.iter().all(|cond| {
self.condition_matches(cond, operation, args)
});
}
rule.conditions.iter().any(|cond| {
self.condition_matches(cond, operation, args)
})
}
fn condition_matches(
&self,
condition: &LiftingCondition,
operation: &str,
args: &[Value]
) -> bool {
match condition {
LiftingCondition::Always => true,
LiftingCondition::OperationName(name) => operation == name,
LiftingCondition::OperationPattern(pattern) => {
operation.contains(pattern)
},
LiftingCondition::HasEffect(effects) => {
effects.iter().any(|e| self.context.has_effect(e))
},
LiftingCondition::HasAllEffects(effects) => {
effects.iter().all(|e| self.context.has_effect(e))
},
LiftingCondition::ArgumentCount(count) => args.len() == *count,
LiftingCondition::ArgumentRange(min, max) => {
args.len() >= *min && args.len() <= *max
},
LiftingCondition::FirstArgType(value_type) => {
args.first().map(|v| self.value_matches_type(v, value_type))
.unwrap_or(false)
},
LiftingCondition::Custom => false, }
}
fn value_matches_type(&self, value: &Value, value_type: &ValueType) -> bool {
match value_type {
ValueType::Number => value.is_number(),
ValueType::String => value.is_string(),
ValueType::Symbol => value.is_symbol(),
ValueType::Boolean => matches!(value, Value::Literal(crate::ast::Literal::Boolean(_))),
ValueType::Pair => value.is_pair(),
ValueType::Nil => value.is_nil(),
ValueType::Procedure => value.is_procedure(),
ValueType::Port => value.is_port(),
ValueType::Any => true,
}
}
fn apply_lifting_rule(
&self,
rule: &LiftingRule,
operation: &str,
args: &[Value]
) -> Result<MonadicValue> {
match &rule.transformation {
LiftingTransformation::Direct => {
match &rule.target_effect {
Effect::Pure => Ok(MonadicValue::pure(Value::Unspecified)),
Effect::IO => {
let io_action = self.create_io_action(operation, args)?;
Ok(MonadicValue::io(io_action))
},
Effect::State => {
let state_action = self.create_state_action(operation, args)?;
let env = Arc::new(ThreadSafeEnvironment::new(None, 0));
Ok(MonadicValue::state(state_action, env))
},
Effect::Error => {
let error_action = self.create_error_action(operation, args)?;
Ok(MonadicValue::error(error_action))
},
Effect::Custom(name) => {
Ok(MonadicValue::pure(Value::string(format!("Custom effect: {name}"))))
}
}
},
LiftingTransformation::TransformArgs => {
self.apply_lifting_rule(
&LiftingRule {
target_effect: rule.target_effect.clone(),
transformation: LiftingTransformation::Direct,
conditions: rule.conditions.clone(),
priority: rule.priority,
},
operation,
args
)
},
LiftingTransformation::Custom => {
Ok(MonadicValue::pure(Value::string("Custom transformation".to_string())))
},
LiftingTransformation::MapTo(new_operation) => {
self.apply_lifting_rule(
&LiftingRule {
target_effect: rule.target_effect.clone(),
transformation: LiftingTransformation::Direct,
conditions: rule.conditions.clone(),
priority: rule.priority,
},
new_operation,
args
)
}
}
}
fn create_io_action(&self, operation: &str, args: &[Value]) -> Result<IOAction> {
match operation {
"display" => {
if let Some(value) = args.first() {
Ok(IOAction::Print(value.clone()))
} else {
Err(Box::new(DiagnosticError::runtime_error(
"display requires at least one argument".to_string(),
None,
)))
}
},
"write" => {
if let Some(value) = args.first() {
Ok(IOAction::WriteValue(value.clone()))
} else {
Err(Box::new(DiagnosticError::runtime_error(
"write requires at least one argument".to_string(),
None,
)))
}
},
"newline" => Ok(IOAction::Newline),
"read" => Ok(IOAction::Read(super::IOSource::Stdin)),
_ => Ok(IOAction::Custom(operation.to_string(), args.to_vec())),
}
}
fn create_state_action(&self, operation: &str, args: &[Value]) -> Result<StateAction> {
match operation {
"set!" => {
if args.len() >= 2 {
if let Some(var_name) = args[0].as_string() {
Ok(StateAction::SetVar(var_name.to_string(), args[1].clone()))
} else {
Err(Box::new(DiagnosticError::runtime_error(
"set! requires a variable name".to_string(),
None,
)))
}
} else {
Err(Box::new(DiagnosticError::runtime_error(
"set! requires two arguments".to_string(),
None,
)))
}
},
_ => Ok(StateAction::Custom(operation.to_string(), args.to_vec())),
}
}
fn create_error_action(&self, operation: &str, args: &[Value]) -> Result<ErrorAction> {
match operation {
"error" | "raise" => {
let error_msg = if let Some(msg) = args.first() {
format!("Error: {msg}")
} else {
"Unspecified error".to_string()
};
let error = DiagnosticError::runtime_error(error_msg, None);
Ok(ErrorAction::Throw(error))
},
_ => Ok(ErrorAction::Custom(operation.to_string(), args.to_vec())),
}
}
fn register_builtin_rules(&mut self) {
let io_ops = vec!["display", "write", "newline", "read", "print", "write-char"];
for op in io_ops {
self.add_rule(op.to_string(), LiftingRule {
target_effect: Effect::IO,
transformation: LiftingTransformation::Direct,
conditions: vec![LiftingCondition::OperationName(op.to_string())],
priority: 100,
});
}
let state_ops = vec!["set!", "vector-set!", "string-set!", "hashtable-set!"];
for op in state_ops {
self.add_rule(op.to_string(), LiftingRule {
target_effect: Effect::State,
transformation: LiftingTransformation::Direct,
conditions: vec![LiftingCondition::OperationName(op.to_string())],
priority: 100,
});
}
let error_ops = vec!["error", "raise", "throw", "assert"];
for op in error_ops {
self.add_rule(op.to_string(), LiftingRule {
target_effect: Effect::Error,
transformation: LiftingTransformation::Direct,
conditions: vec![LiftingCondition::OperationName(op.to_string())],
priority: 100,
});
}
}
pub fn clear_cache(&mut self) {
self.lift_cache.clear();
}
pub fn cache_stats(&self) -> (usize, usize) {
(self.lift_cache.len(), self.lift_cache.capacity())
}
pub fn set_enabled(&mut self, enabled: bool) {
self.enabled = enabled;
}
pub fn is_enabled(&self) -> bool {
self.enabled
}
}
impl BuiltinLiftingRules {
pub fn create_standard_rules() -> HashMap<String, LiftingRule> {
let mut rules = HashMap::new();
rules.insert("display".to_string(), LiftingRule {
target_effect: Effect::IO,
transformation: LiftingTransformation::Direct,
conditions: vec![
LiftingCondition::OperationName("display".to_string()),
LiftingCondition::ArgumentRange(1, 2),
],
priority: 100,
});
rules.insert("newline".to_string(), LiftingRule {
target_effect: Effect::IO,
transformation: LiftingTransformation::Direct,
conditions: vec![
LiftingCondition::OperationName("newline".to_string()),
LiftingCondition::ArgumentRange(0, 1),
],
priority: 100,
});
rules.insert("set!".to_string(), LiftingRule {
target_effect: Effect::State,
transformation: LiftingTransformation::Direct,
conditions: vec![
LiftingCondition::OperationName("set!".to_string()),
LiftingCondition::ArgumentCount(2),
],
priority: 100,
});
rules.insert("error".to_string(), LiftingRule {
target_effect: Effect::Error,
transformation: LiftingTransformation::Direct,
conditions: vec![
LiftingCondition::OperationName("error".to_string()),
LiftingCondition::ArgumentRange(0, 2),
],
priority: 100,
});
rules
}
pub fn create_file_rules() -> HashMap<String, LiftingRule> {
let mut rules = HashMap::new();
let file_ops = vec![
"open-input-file", "open-output-file", "close-port",
"read-char", "write-char", "read-line", "write-line"
];
for op in file_ops {
rules.insert(op.to_string(), LiftingRule {
target_effect: Effect::IO,
transformation: LiftingTransformation::Direct,
conditions: vec![LiftingCondition::OperationName(op.to_string())],
priority: 90,
});
}
rules
}
pub fn create_vector_rules() -> HashMap<String, LiftingRule> {
let mut rules = HashMap::new();
rules.insert("vector-set!".to_string(), LiftingRule {
target_effect: Effect::State,
transformation: LiftingTransformation::Direct,
conditions: vec![
LiftingCondition::OperationName("vector-set!".to_string()),
LiftingCondition::ArgumentCount(3),
],
priority: 90,
});
rules.insert("vector-fill!".to_string(), LiftingRule {
target_effect: Effect::State,
transformation: LiftingTransformation::Direct,
conditions: vec![
LiftingCondition::OperationName("vector-fill!".to_string()),
LiftingCondition::ArgumentRange(2, 4),
],
priority: 90,
});
rules
}
}
impl LiftingSystemConfig {
pub fn no_lifting() -> Self {
Self {
enable_auto_lifting: false,
enable_caching: false,
max_cache_size: 0,
warn_on_lift: false,
custom_rules: HashMap::new(),
}
}
pub fn debug() -> Self {
Self {
enable_auto_lifting: true,
enable_caching: false, max_cache_size: 0,
warn_on_lift: true, custom_rules: HashMap::new(),
}
}
}
impl PartialEq for LiftingTransformation {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(LiftingTransformation::Direct, LiftingTransformation::Direct) => true,
(LiftingTransformation::MapTo(a), LiftingTransformation::MapTo(b)) => a == b,
_ => false, }
}
}
impl PartialEq for LiftingCondition {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(LiftingCondition::Always, LiftingCondition::Always) => true,
(LiftingCondition::OperationName(a), LiftingCondition::OperationName(b)) => a == b,
(LiftingCondition::OperationPattern(a), LiftingCondition::OperationPattern(b)) => a == b,
(LiftingCondition::HasEffect(a), LiftingCondition::HasEffect(b)) => a == b,
(LiftingCondition::HasAllEffects(a), LiftingCondition::HasAllEffects(b)) => a == b,
(LiftingCondition::ArgumentCount(a), LiftingCondition::ArgumentCount(b)) => a == b,
(LiftingCondition::ArgumentRange(a1, a2), LiftingCondition::ArgumentRange(b1, b2)) =>
a1 == b1 && a2 == b2,
(LiftingCondition::FirstArgType(a), LiftingCondition::FirstArgType(b)) => a == b,
_ => false, }
}
}
impl Default for EffectLifter {
fn default() -> Self {
Self::new()
}
}
impl fmt::Display for LiftingRule {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "LiftingRule(effect={}, priority={}, conditions={})",
self.target_effect,
self.priority,
self.conditions.len())
}
}
impl fmt::Display for ValueType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ValueType::Number => write!(f, "Number"),
ValueType::String => write!(f, "String"),
ValueType::Symbol => write!(f, "Symbol"),
ValueType::Boolean => write!(f, "Boolean"),
ValueType::Pair => write!(f, "Pair"),
ValueType::Nil => write!(f, "Nil"),
ValueType::Procedure => write!(f, "Procedure"),
ValueType::Port => write!(f, "Port"),
ValueType::Any => write!(f, "Any"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_lifting_rule_creation() {
let rule = LiftingRule {
target_effect: Effect::IO,
transformation: LiftingTransformation::Direct,
conditions: vec![LiftingCondition::OperationName("display".to_string())],
priority: 100,
};
assert_eq!(rule.target_effect, Effect::IO);
assert_eq!(rule.priority, 100);
}
#[test]
fn test_effect_lifter() {
let mut lifter = EffectLifter::new();
let args = vec![Value::string("Hello, world!".to_string())];
let lifted = lifter.lift_operation("display", &args);
assert!(lifted.is_some());
let monadic_val = lifted.unwrap();
assert!(monadic_val.effects().contains(&Effect::IO));
}
#[test]
fn test_builtin_rules() {
let rules = BuiltinLiftingRules::create_standard_rules();
assert!(rules.contains_key("display"));
assert!(rules.contains_key("newline"));
assert!(rules.contains_key("set!"));
assert!(rules.contains_key("error"));
}
}