use pattern_wishcast::pattern_wishcast;
pattern_wishcast! {
#[derive(Debug, Clone, PartialEq)]
enum StuckEvaluation = {
Var { id: usize },
Application { func: Box<PartialValue>, arg: Box<PartialValue> },
UnboundVariable { name: String },
};
#[derive(Debug, Clone, PartialEq)]
enum Value is <P: PatternFields> = StuckEvaluation | {
Number { value: i32 },
Boolean { value: bool },
Text { value: String },
Tuple { elements: Vec<Self> },
Function {
param: String,
body: Box<Self>,
#[unsafe_transmute_check(iter = ".values()")]
captured_env: std::collections::HashMap<String, Self>
},
};
type CompleteValue = Value is Number { .. } | Boolean { .. } | Text { .. } | Tuple { .. } | Function { .. };
type PartialValue = Value is _;
#[derive(SubtypingRelation(upcast=to_partial, downcast=try_to_complete))]
impl CompleteValue : PartialValue;
}
impl CompleteValue {
pub fn number(n: i32) -> Self {
CompleteValue::Number { value: n }
}
pub fn boolean(b: bool) -> Self {
CompleteValue::Boolean { value: b }
}
pub fn text(s: String) -> Self {
CompleteValue::Text { value: s }
}
pub fn tuple(elements: Vec<CompleteValue>) -> Self {
let complete_elements: Vec<CompleteValue> = elements.into_iter().collect();
CompleteValue::Tuple {
elements: complete_elements,
}
}
pub fn as_number(&self) -> Option<i32> {
match self {
CompleteValue::Number { value } => Some(*value),
_ => None,
}
}
pub fn as_boolean(&self) -> Option<bool> {
match self {
CompleteValue::Boolean { value } => Some(*value),
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub struct EvalContext {
variables: std::collections::HashMap<String, PartialValue>,
}
impl EvalContext {
pub fn new() -> Self {
let mut ctx = Self {
variables: std::collections::HashMap::new(),
};
ctx.add_builtin("add".to_string());
ctx.add_builtin("sub".to_string());
ctx.add_builtin("mul".to_string());
ctx
}
fn add_builtin(&mut self, name: String) {
let builtin = PartialValue::Function {
param: format!("builtin_{name}"),
body: Box::new(PartialValue::unbound_var("builtin_implementation".to_string())),
captured_env: std::collections::HashMap::new(),
};
self.variables.insert(name, builtin);
}
pub fn bind(&mut self, name: String, value: PartialValue) {
self.variables.insert(name, value);
}
pub fn lookup(&self, name: &str) -> Option<&PartialValue> {
self.variables.get(name)
}
}
impl Default for EvalContext {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Display for EvalContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut sorted_vars: Vec<_> = self.variables.iter().collect();
sorted_vars.sort_by_key(|(name, _)| *name);
writeln!(f, "{{")?;
for (name, value) in sorted_vars {
writeln!(f, " {name} = {value}")?;
}
write!(f, "}}")
}
}
impl PartialValue {
pub fn stuck_var(id: usize) -> Self {
PartialValue::StuckEvaluation(StuckEvaluation::Var { id }, ())
}
pub fn unbound_var(name: String) -> Self {
PartialValue::StuckEvaluation(StuckEvaluation::UnboundVariable { name }, ())
}
fn try_to_complete_args(args: Vec<PartialValue>) -> Result<Vec<CompleteValue>, Vec<PartialValue>> {
let mut complete_args = Vec::new();
let mut remaining_args = args.into_iter();
for arg in remaining_args.by_ref() {
match arg.try_to_complete() {
Ok(complete) => complete_args.push(complete),
Err(partial) => {
let mut result: Vec<PartialValue> = complete_args.into_iter().map(|complete| complete.to_partial()).collect();
result.push(partial);
result.extend(remaining_args);
return Err(result);
}
}
}
Ok(complete_args)
}
fn apply_builtin(name: &str, builtin_func: PartialValue, args: Vec<PartialValue>) -> PartialValue {
match Self::try_to_complete_args(args) {
Ok(complete_args) => match name {
"add" if complete_args.len() == 2 => {
if let (Some(x), Some(y)) = (complete_args[0].as_number(), complete_args[1].as_number()) {
CompleteValue::number(x + y).to_partial()
} else {
PartialValue::unbound_var("type_error".to_string())
}
}
"sub" if complete_args.len() == 2 => {
if let (Some(x), Some(y)) = (complete_args[0].as_number(), complete_args[1].as_number()) {
CompleteValue::number(x - y).to_partial()
} else {
PartialValue::unbound_var("type_error".to_string())
}
}
"mul" if complete_args.len() == 2 => {
if let (Some(x), Some(y)) = (complete_args[0].as_number(), complete_args[1].as_number()) {
CompleteValue::number(x * y).to_partial()
} else {
PartialValue::unbound_var("type_error".to_string())
}
}
_ => PartialValue::unbound_var("unknown_builtin".to_string()),
},
Err(original_args) => {
PartialValue::StuckEvaluation(
StuckEvaluation::Application {
func: Box::new(builtin_func),
arg: Box::new(PartialValue::Tuple { elements: original_args }),
},
(),
)
}
}
}
pub fn eval(self, ctx: &EvalContext) -> PartialValue {
match self {
PartialValue::Number { .. } | PartialValue::Boolean { .. } | PartialValue::Text { .. } => self,
PartialValue::Tuple { elements } => {
let eval_elements: Vec<PartialValue> = elements.into_iter().map(|elem| elem.eval(ctx)).collect();
PartialValue::Tuple { elements: eval_elements }
}
PartialValue::StuckEvaluation(stuck, _) => {
match stuck {
StuckEvaluation::UnboundVariable { name } => {
if let Some(value) = ctx.lookup(&name) {
value.clone()
} else {
PartialValue::StuckEvaluation(StuckEvaluation::UnboundVariable { name }, ())
}
}
StuckEvaluation::Application { func, arg } => {
let eval_func = func.eval(ctx);
let eval_arg = arg.eval(ctx);
match (eval_func, eval_arg) {
(PartialValue::Function { param, body, captured_env }, PartialValue::Tuple { elements })
if param.starts_with("builtin_") =>
{
let builtin_name = param.clone();
let builtin_name = builtin_name.strip_prefix("builtin_").unwrap();
Self::apply_builtin(builtin_name, PartialValue::Function { param, body, captured_env }, elements)
}
(eval_func, eval_arg) => PartialValue::StuckEvaluation(
StuckEvaluation::Application {
func: Box::new(eval_func),
arg: Box::new(eval_arg),
},
(),
),
}
}
other => PartialValue::StuckEvaluation(other, ()),
}
}
PartialValue::Function { .. } => self,
}
}
pub fn is_complete(&self) -> bool {
self.try_to_complete_ref().is_ok()
}
}
impl<P: PatternFields> std::fmt::Display for Value<P> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Value::Number { value } => write!(f, "{value}"),
Value::Boolean { value } => write!(f, "{}", if *value { "true" } else { "false" }),
Value::Text { value } => write!(f, "\"{value}\""),
Value::Tuple { elements } => {
write!(f, "(")?;
for (i, elem) in elements.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{elem}")?;
}
write!(f, ")")
}
Value::Function { param, .. } => {
if param.starts_with("builtin_") {
let builtin_name = param.strip_prefix("builtin_").unwrap();
write!(f, "⟨builtin: {builtin_name}⟩")
} else {
write!(f, "λ{param}.⟨function⟩")
}
}
Value::StuckEvaluation(stuck, _) => match stuck {
StuckEvaluation::Var { id } => write!(f, "?{id}"),
StuckEvaluation::UnboundVariable { name } => write!(f, "?{name}"),
StuckEvaluation::Application { func, arg } => {
write!(f, "({func}{arg})")
}
},
}
}
}
fn eval_and_print(expr: PartialValue, ctx: &EvalContext) -> PartialValue {
print!("\n{expr}");
let result = expr.eval(ctx);
println!(" = {result}");
println!(" is_complete() = {}", result.is_complete());
result
}
fn main() {
println!(
"pattern-wishcast toy expression evaluator
---"
);
let mut ctx = EvalContext::new();
ctx.bind("x".to_string(), CompleteValue::number(100).to_partial());
ctx.bind("y".to_string(), CompleteValue::number(25).to_partial());
println!("\nEvaluation Context: {ctx}");
let unbound_x = PartialValue::unbound_var("x".to_string());
let number_5 = CompleteValue::number(5).to_partial();
let stuck_expr = PartialValue::StuckEvaluation(
StuckEvaluation::Application {
func: Box::new(PartialValue::unbound_var("add".to_string())),
arg: Box::new(PartialValue::Tuple {
elements: vec![unbound_x, number_5],
}),
},
(),
);
let resolved_expr = eval_and_print(stuck_expr, &ctx);
match resolved_expr.try_to_complete() {
Ok(_) => {
println!(" Downcasts to CompleteValue");
}
Err(_) => {
panic!("add result was not fully evaluated")
}
}
let var_y = PartialValue::unbound_var("y".to_string());
let mul_computation = PartialValue::StuckEvaluation(
StuckEvaluation::Application {
func: Box::new(PartialValue::unbound_var("mul".to_string())),
arg: Box::new(PartialValue::Tuple {
elements: vec![var_y, CompleteValue::number(2).to_partial()],
}),
},
(),
);
let resolved_mul = eval_and_print(mul_computation, &ctx);
match resolved_mul.try_to_complete() {
Ok(_) => {
println!(" Downcasts to CompleteValue");
}
Err(_) => {
panic!("mul result was not fully evaluated")
}
}
let var_z = PartialValue::unbound_var("z".to_string());
let mul_unbound = PartialValue::StuckEvaluation(
StuckEvaluation::Application {
func: Box::new(PartialValue::unbound_var("mul".to_string())),
arg: Box::new(PartialValue::Tuple {
elements: vec![var_z, CompleteValue::number(2).to_partial()],
}),
},
(),
);
let _resolved_unbound = eval_and_print(mul_unbound, &ctx);
}
#[test]
fn test_main() {
main()
}