use crate::*;
#[cfg(all(feature = "kind_annotation", feature = "enum"))]
use std::collections::HashSet;
use crate::*;
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum FrameState {
Running,
Suspended,
Completed,
}
#[derive(Clone)]
pub struct Frame {
plan: Plan,
ip: usize, locals: SymbolTableRef, out: Option<Value>, state: FrameState, }
#[derive(Clone)]
pub struct Stack {
frames: Vec<Frame>,
}
pub fn function_define(fxn_def: &FunctionDefine, p: &Interpreter) -> MResult<FunctionDefinition> {
let fxn_name_id = fxn_def.name.hash();
let mut new_fxn =
FunctionDefinition::new(fxn_name_id, fxn_def.name.to_string(), fxn_def.clone());
for input_arg in &fxn_def.input {
new_fxn
.input
.insert(input_arg.name.hash(), input_arg.kind.clone());
}
for output_arg in &fxn_def.output {
new_fxn
.output
.insert(output_arg.name.hash(), output_arg.kind.clone());
}
let functions = p.functions();
let mut functions_brrw = functions.borrow_mut();
functions_brrw
.user_functions
.insert(fxn_name_id, new_fxn.clone());
functions_brrw
.dictionary
.borrow_mut()
.insert(fxn_name_id, fxn_def.name.to_string());
p.state
.borrow()
.dictionary
.borrow_mut()
.insert(fxn_name_id, fxn_def.name.to_string());
Ok(new_fxn)
}
pub fn function_call(
fxn_call: &FunctionCall,
env: Option<&Environment>,
p: &Interpreter,
) -> MResult<Value> {
let functions = p.functions();
let fxn_name_id = fxn_call.name.hash();
if let Some(user_fxn) = { functions.borrow().user_functions.get(&fxn_name_id).cloned() } {
let mut input_arg_values = vec![];
for (_, arg_expr) in fxn_call.args.iter() {
input_arg_values.push(expression(arg_expr, env, p)?);
}
return execute_user_function(&user_fxn, &input_arg_values, p);
}
if { functions.borrow().functions.contains_key(&fxn_name_id) } {
todo!();
}
let fxn_compiler = {
functions
.borrow()
.function_compilers
.get(&fxn_name_id)
.copied()
};
match fxn_compiler {
Some(fxn_compiler) => {
let mut input_arg_values = vec![];
for (_, arg_expr) in fxn_call.args.iter() {
input_arg_values.push(expression(arg_expr, env, p)?);
}
trace_println!(
p,
"{}",
format_trace(
"fn",
format!(
"native {}({})",
fxn_call.name.to_string(),
format_trace_args(&input_arg_values)
),
)
);
execute_native_function_compiler(fxn_compiler, &input_arg_values, p)
}
None => Err(MechError::new(
MissingFunctionError {
function_id: fxn_name_id,
},
None,
)
.with_compiler_loc()
.with_tokens(fxn_call.name.tokens())),
}
}
pub fn execute_native_function_compiler(
fxn_compiler: &'static dyn NativeFunctionCompiler,
input_arg_values: &Vec<Value>,
p: &Interpreter,
) -> MResult<Value> {
let plan = p.plan();
match fxn_compiler.compile(input_arg_values) {
Ok(mut new_fxn) => {
trace_println!(
p,
"{}",
format_trace(
"arm",
format!(
"selected {} args=[{}]",
new_fxn
.to_string()
.lines()
.next()
.unwrap_or("<unknown-arm>"),
format_trace_args(input_arg_values)
),
)
);
let mut plan_brrw = plan.borrow_mut();
new_fxn.solve();
let result = new_fxn.out();
trace_println!(
p,
"{}",
format_trace("arm", format!("result {}", summarize_value(&result)))
);
plan_brrw.push(new_fxn);
Ok(result)
}
Err(err) => Err(err),
}
}
fn execute_user_function(
fxn_def: &FunctionDefinition,
input_arg_values: &Vec<Value>,
p: &Interpreter,
) -> MResult<Value> {
if input_arg_values.len() != fxn_def.input.len() {
return Err(MechError::new(
IncorrectNumberOfArguments {
expected: fxn_def.input.len(),
found: input_arg_values.len(),
},
None,
)
.with_compiler_loc()
.with_tokens(fxn_def.code.name.tokens()));
}
#[cfg(feature = "matrix")]
if let Some(result) = try_broadcast_user_function(fxn_def, input_arg_values, p)? {
return Ok(result);
}
trace_println!(
p,
"{}",
format_trace(
"fn",
format!(
"enter {}({})",
fxn_def.name,
format_trace_args(input_arg_values)
),
)
);
let scope = FunctionScope::enter(p);
bind_function_inputs(fxn_def, input_arg_values, p)?;
let output = if !fxn_def.code.match_arms.is_empty() {
execute_function_match_arms(fxn_def, input_arg_values, p)
} else {
for statement_node in &fxn_def.code.statements {
statement(statement_node, None, p)?;
}
collect_function_output(p, fxn_def)
};
drop(scope);
match output {
Ok(value) => {
trace_println!(
p,
"{}",
format_trace(
"fn",
format!("exit {} => {}", fxn_def.name, summarize_value(&value))
)
);
Ok(value)
}
Err(err) => {
trace_println!(
p,
"{}",
format_trace("fn", format!("fail {} => {:?}", fxn_def.name, err))
);
Err(err)
}
}
}
#[cfg(feature = "matrix")]
fn try_broadcast_user_function(
fxn_def: &FunctionDefinition,
input_arg_values: &Vec<Value>,
p: &Interpreter,
) -> MResult<Option<Value>> {
if input_arg_values.len() != 1 || fxn_def.code.output.len() != 1 || fxn_def.code.input.len() != 1 {
return Ok(None);
}
let source = detach_value(&input_arg_values[0]);
if !source.is_matrix() {
return Ok(None);
}
#[cfg(feature = "kind_annotation")]
let (input_kind, output_kind) = {
let input_kind = kind_annotation(&fxn_def.code.input[0].kind.kind, p)?
.to_value_kind(&p.state.borrow().kinds)?;
let output_kind = kind_annotation(&fxn_def.code.output[0].kind.kind, p)?
.to_value_kind(&p.state.borrow().kinds)?;
(input_kind, output_kind)
};
#[cfg(not(feature = "kind_annotation"))]
let (input_kind, output_kind) = {
return Ok(None);
};
if input_kind != output_kind || matches!(input_kind, ValueKind::Matrix(_, _)) {
return Ok(None);
}
let Some(elements) = crate::patterns::matrix_like_values(&source) else {
return Ok(None);
};
let mut outputs = Vec::with_capacity(elements.len());
for element in elements {
outputs.push(execute_user_function(fxn_def, &vec![element], p)?);
}
let shape = source.shape();
Ok(Some(build_typed_matrix_from_values(
&output_kind,
outputs,
shape[0],
shape[1],
)))
}
#[cfg(feature = "matrix")]
fn build_typed_matrix_from_values(
output_kind: &ValueKind,
outputs: Vec<Value>,
rows: usize,
cols: usize,
) -> Value {
match output_kind {
#[cfg(feature = "f64")]
ValueKind::F64 => Value::MatrixF64(f64::to_matrix(
outputs
.into_iter()
.map(|value| value.as_f64().expect("Expected f64 output").borrow().clone())
.collect::<Vec<f64>>(),
rows,
cols,
)),
_ => Value::MatrixValue(Value::to_matrix(outputs, rows, cols)),
}
}
fn execute_function_match_arms(
fxn_def: &FunctionDefinition,
input_arg_values: &Vec<Value>,
p: &Interpreter,
) -> MResult<Value> {
#[cfg(all(feature = "kind_annotation", feature = "enum"))]
{
let has_wildcard = fxn_def
.code
.match_arms
.iter()
.any(|arm| matches!(arm.pattern, Pattern::Wildcard));
if !has_wildcard && fxn_def.input.len() == 1 {
if let Some((_, kind_annotation_node)) = fxn_def.input.iter().next() {
let input_kind = kind_annotation(&kind_annotation_node.kind, p)?
.to_value_kind(&p.state.borrow().kinds)?;
if let ValueKind::Enum(enum_id, _) = input_kind {
let state_brrw = p.state.borrow();
if let Some(enum_def) = state_brrw.enums.get(&enum_id) {
let mut covered_variants: HashSet<u64> = HashSet::new();
for arm in &fxn_def.code.match_arms {
match &arm.pattern {
#[cfg(feature = "atom")]
Pattern::TupleStruct(tuple_struct) => {
covered_variants.insert(tuple_struct.name.hash());
}
Pattern::Expression(expr) => {
if let Expression::Literal(Literal::Atom(atom)) = expr {
covered_variants.insert(atom.name.hash());
}
}
_ => {}
}
}
let all_covered = enum_def
.variants
.iter()
.all(|(variant_id, _)| covered_variants.contains(variant_id));
if !all_covered {
let missing_patterns = enum_def
.variants
.iter()
.filter(|(variant_id, _)| !covered_variants.contains(variant_id))
.map(|(variant_id, payload_kind)| {
let variant_name = enum_def
.names
.borrow()
.get(variant_id)
.cloned()
.unwrap_or_else(|| variant_id.to_string());
if payload_kind.is_some() {
format!(":{}(...)", variant_name)
} else {
format!(":{}", variant_name)
}
})
.collect::<Vec<String>>();
return Err(MechError::new(
FunctionMatchNonExhaustiveError {
function_name: fxn_def.name.clone(),
missing_patterns,
},
None,
)
.with_compiler_loc()
.with_tokens(fxn_def.code.name.tokens()));
}
}
}
}
}
}
for (arm_idx, arm) in fxn_def.code.match_arms.iter().enumerate() {
let mut env = Environment::new();
let matched = crate::patterns::pattern_matches_arguments(
&arm.pattern,
input_arg_values,
&mut env,
p,
)?;
trace_println!(p, "{}", {
let args_summary = summarize_values_with_kinds(input_arg_values);
let pattern_summary = summarize_pattern(&arm.pattern);
let marker = if matched { "✓" } else { "X" };
format_trace(
"match",
format!(
"arm[{arm_idx}] test pattern={pattern_summary} args=[{args_summary}] {marker}"
),
)
});
if matched {
let out = expression(&arm.expression, Some(&env), p)?;
let coerced = coerce_function_output_kind(detach_value(&out), fxn_def, p)?;
trace_println!(
p,
"{}",
format_trace(
"match",
format!(
"arm[{arm_idx}] out value={} kind={}",
summarize_value(&coerced),
coerced.kind().to_string()
)
)
);
return Ok(coerced);
}
}
Err(MechError::new(
FunctionOutputUndefinedError {
output_id: fxn_def.id,
},
None,
)
.with_compiler_loc()
.with_tokens(fxn_def.code.name.tokens()))
}
fn format_trace(scope: &str, message: String) -> String {
format!("[trace][{scope}] {message}")
}
fn format_trace_args(values: &Vec<Value>) -> String {
values
.iter()
.map(summarize_value)
.collect::<Vec<_>>()
.join(", ")
}
fn summarize_value(value: &Value) -> String {
const MAX_TRACE_CHARS: usize = 96;
let rendered = single_line_trace_text(&summarize_value_compact(value, 0));
truncate_for_trace(&rendered, MAX_TRACE_CHARS)
}
fn summarize_value_compact(value: &Value, depth: usize) -> String {
if depth > 2 {
return format!("{}(..)", value.kind().to_string());
}
match value {
#[cfg(feature = "u64")]
Value::U64(x) => format!("u64(@{:04x}:{})", short_addr(x.addr()), *x.borrow()),
#[cfg(feature = "i64")]
Value::I64(x) => format!("i64(@{:04x}:{})", short_addr(x.addr()), *x.borrow()),
#[cfg(feature = "f64")]
Value::F64(x) => format!("f64(@{:04x}:{})", short_addr(x.addr()), *x.borrow()),
#[cfg(feature = "bool")]
Value::Bool(x) => format!("bool(@{:04x}:{})", short_addr(x.addr()), *x.borrow()),
#[cfg(feature = "string")]
Value::String(x) => format!("str(@{:04x}:\"{}\")", short_addr(x.addr()), x.borrow()),
#[cfg(feature = "atom")]
Value::Atom(x) => format!("{}(@{:04x})", x.borrow().to_string(), short_addr(x.addr())),
#[cfg(feature = "tuple")]
Value::Tuple(tuple_ref) => summarize_tuple_value(tuple_ref, depth),
_ => format!(
"{}({})",
value.kind().to_string(),
truncate_for_trace(&single_line_trace_text(&format!("{:?}", value)), 48)
),
}
}
#[cfg(feature = "tuple")]
fn summarize_tuple_value(tuple_ref: &Ref<MechTuple>, depth: usize) -> String {
let tuple = tuple_ref.borrow();
let mut parts = Vec::new();
for element in tuple.elements.iter().take(3) {
parts.push(summarize_value_compact(element, depth + 1));
}
if tuple.elements.len() > 3 {
parts.push("…".to_string());
}
format!(
"tuple(@{:04x}; len={}; [{}])",
short_addr(tuple_ref.addr()),
tuple.elements.len(),
parts.join(", ")
)
}
fn short_addr(addr: usize) -> u16 {
(addr & 0xffff) as u16
}
fn summarize_values_with_kinds(values: &Vec<Value>) -> String {
values
.iter()
.enumerate()
.map(|(idx, value)| {
format!(
"#{idx}={} :{}",
summarize_value(value),
value.kind().to_string()
)
})
.collect::<Vec<_>>()
.join(", ")
}
fn summarize_pattern(pattern: &Pattern) -> String {
match pattern {
Pattern::Wildcard => "_".to_string(),
Pattern::Expression(expr) => truncate_for_trace(&format!("{:?}", expr), 72),
Pattern::Tuple(tuple) => format!("tuple(len={})", tuple.0.len()),
Pattern::Array(array) => {
let spread = if array.spread.is_some() {
",spread"
} else {
""
};
format!(
"array(prefix={}{} ,suffix={})",
array.prefix.len(),
spread,
array.suffix.len()
)
}
Pattern::TupleStruct(tuple_struct) => {
format!(
"{}(len={})",
tuple_struct.name.to_string(),
tuple_struct.patterns.len()
)
}
}
}
fn truncate_for_trace(text: &str, max_chars: usize) -> String {
if text.chars().count() <= max_chars {
return text.to_string();
}
let mut truncated = text.chars().take(max_chars).collect::<String>();
truncated.push('…');
truncated
}
fn single_line_trace_text(text: &str) -> String {
text.split_whitespace().collect::<Vec<_>>().join(" ")
}
#[cfg(feature = "kind_annotation")]
fn coerce_function_output_kind( value: Value, fxn_def: &FunctionDefinition, p: &Interpreter) -> MResult<Value> {
if fxn_def.output.is_empty() {
return Ok(value);
}
let Some((_, output_kind_annotation)) = fxn_def.output.get_index(0) else {
return Ok(value);
};
let target_kind = kind_annotation(&output_kind_annotation.kind, p)?
.to_value_kind(&p.state.borrow().kinds)?;
return Ok(value.convert_to(&target_kind).unwrap_or(value))
}
struct FunctionScope {
state: Ref<ProgramState>,
previous_symbols: SymbolTableRef,
previous_plan: Plan,
previous_environment: Option<SymbolTableRef>,
}
impl FunctionScope {
fn enter(p: &Interpreter) -> Self {
let state = p.state.clone();
let mut state_brrw = state.borrow_mut();
let mut local_symbols = SymbolTable::new();
local_symbols.dictionary = state_brrw.dictionary.clone();
let local_symbols = Ref::new(local_symbols);
let previous_symbols = std::mem::replace(&mut state_brrw.symbol_table, local_symbols);
let previous_plan = std::mem::replace(&mut state_brrw.plan, Plan::new());
let previous_environment = state_brrw.environment.take();
drop(state_brrw);
Self {
state,
previous_symbols,
previous_plan,
previous_environment,
}
}
}
impl Drop for FunctionScope {
fn drop(&mut self) {
let mut state_brrw = self.state.borrow_mut();
state_brrw.symbol_table = self.previous_symbols.clone();
state_brrw.plan = self.previous_plan.clone();
state_brrw.environment = self.previous_environment.clone();
}
}
fn bind_function_inputs(
fxn_def: &FunctionDefinition,
input_arg_values: &Vec<Value>,
p: &Interpreter,
) -> MResult<()> {
let scoped_state = p.state.borrow();
for ((arg_id, input_kind_annotation), input_value) in fxn_def.input.iter().zip(input_arg_values.iter()) {
let arg_name = fxn_def
.code
.input
.iter()
.find(|arg| arg.name.hash() == *arg_id)
.map(|arg| arg.name.to_string())
.unwrap_or_else(|| arg_id.to_string());
let bound_value = {
#[cfg(feature = "kind_annotation")]
{
let target_kind = kind_annotation(&input_kind_annotation.kind, p)?
.to_value_kind(&p.state.borrow().kinds)?;
let detached_input = detach_value(input_value);
#[cfg(all(feature = "enum", feature = "atom"))]
if let ValueKind::Enum(enum_id, _) = &target_kind {
let state_brrw = p.state.borrow();
if enum_value_matches(detached_input.clone(), *enum_id, &state_brrw) {
detached_input.clone()
} else {
return Err(MechError::new(
FunctionInputTypeMismatchError {
function_name: fxn_def.name.clone(),
argument_name: arg_name.clone(),
expected: target_kind.clone(),
found: detached_input.kind(),
},
None,
)
.with_compiler_loc()
.with_tokens(input_kind_annotation.tokens()));
}
} else {
detached_input.clone().convert_to(&target_kind).ok_or_else(|| {
MechError::new(
FunctionInputTypeMismatchError {
function_name: fxn_def.name.clone(),
argument_name: arg_name.clone(),
expected: target_kind.clone(),
found: detached_input.kind(),
},
None,
)
.with_compiler_loc()
.with_tokens(input_kind_annotation.tokens())
})?
}
#[cfg(not(all(feature = "enum", feature = "atom")))]
detached_input.clone().convert_to(&target_kind).ok_or_else(|| {
MechError::new(
FunctionInputTypeMismatchError {
function_name: fxn_def.name.clone(),
argument_name: arg_name.clone(),
expected: target_kind.clone(),
found: detached_input.kind(),
},
None,
)
.with_compiler_loc()
.with_tokens(input_kind_annotation.tokens())
})?
}
#[cfg(not(feature = "kind_annotation"))]
{
detach_value(input_value)
}
};
scoped_state.save_symbol(*arg_id, arg_name, bound_value, false);
}
Ok(())
}
#[cfg(all(feature = "enum", feature = "atom"))]
fn enum_value_matches(value: Value, enum_id: u64, state: &ProgramState) -> bool {
let enum_def = match state.enums.get(&enum_id) {
Some(enm) => enm,
None => return false,
};
match value {
Value::Atom(atom) => {
let variant_id = atom.borrow().id();
enum_def
.variants
.iter()
.any(|(known_variant, payload_kind)| *known_variant == variant_id && payload_kind.is_none())
}
#[cfg(feature = "tuple")]
Value::Tuple(tuple_val) => {
let tuple_brrw = tuple_val.borrow();
if tuple_brrw.elements.len() != 2 {
return false;
}
let tag = match tuple_brrw.elements[0].as_ref() {
Value::Atom(atom) => atom.borrow().id(),
_ => return false,
};
let payload = tuple_brrw.elements[1].as_ref().clone();
let (_, declared_payload_kind) = match enum_def.variants.iter().find(|(known_variant, _)| *known_variant == tag) {
Some(entry) => entry,
None => return false,
};
match declared_payload_kind {
Some(Value::Kind(expected_kind)) => match expected_kind {
ValueKind::Enum(inner_enum_id, _) => enum_value_matches(payload, *inner_enum_id, state),
_ => {
payload.kind() == expected_kind.clone()
|| payload.convert_to(expected_kind).is_some()
}
},
_ => false,
}
}
_ => false,
}
}
fn collect_function_output(p: &Interpreter, fxn_def: &FunctionDefinition) -> MResult<Value> {
let symbols = p.symbols();
let symbols_brrw = symbols.borrow();
let mut outputs = vec![];
for output_arg in &fxn_def.code.output {
let output_id = output_arg.name.hash();
match symbols_brrw.get(output_id) {
Some(cell) => outputs.push(detach_value(&cell.borrow())),
None => {
return Err(
MechError::new(FunctionOutputUndefinedError { output_id }, None)
.with_compiler_loc()
.with_tokens(output_arg.tokens()),
);
}
}
}
Ok(match outputs.len() {
0 => Value::Empty,
1 => outputs.remove(0),
_ => Value::Tuple(Ref::new(MechTuple::from_vec(outputs))),
})
}
pub(crate) fn detach_value(value: &Value) -> Value {
match value {
Value::MutableReference(reference) => detach_value(&reference.borrow()),
_ => value.clone(),
}
}
#[derive(Debug, Clone)]
pub struct MissingFunctionError {
pub function_id: u64,
}
impl MechErrorKind for MissingFunctionError {
fn name(&self) -> &str {
"MissingFunction"
}
fn message(&self) -> String {
format!("Function with id {} not found", self.function_id)
}
}
#[derive(Debug, Clone)]
pub struct FunctionOutputUndefinedError {
pub output_id: u64,
}
impl MechErrorKind for FunctionOutputUndefinedError {
fn name(&self) -> &str {
"FunctionOutputUndefined"
}
fn message(&self) -> String {
format!(
"Function output {} was declared but never defined",
self.output_id
)
}
}
#[derive(Debug, Clone)]
pub struct FunctionInputTypeMismatchError {
pub function_name: String,
pub argument_name: String,
pub expected: ValueKind,
pub found: ValueKind,
}
#[derive(Debug, Clone)]
pub struct FunctionMatchNonExhaustiveError {
pub function_name: String,
pub missing_patterns: Vec<String>,
}
impl MechErrorKind for FunctionMatchNonExhaustiveError {
fn name(&self) -> &str {
"FunctionMatchNonExhaustive"
}
fn message(&self) -> String {
format!(
"Function '{}' has non-exhaustive match arms. Missing patterns: {}. Add the missing patterns or add a wildcard (`*`) arm.",
self.function_name,
self.missing_patterns.join(", ")
)
}
}
impl MechErrorKind for FunctionInputTypeMismatchError {
fn name(&self) -> &str {
"FunctionInputTypeMismatch"
}
fn message(&self) -> String {
format!(
"Function '{}' argument '{}' expected {}, found {}",
self.function_name, self.argument_name, self.expected, self.found
)
}
}