yulang-runtime 0.1.1

Runtime IR, validation, monomorphization, and VM support for Yulang
Documentation
use super::*;

pub(super) fn bind_pattern_with_defaults(
    pattern: &Pattern,
    value: VmValue,
    env: &mut Env,
    eval_default: &mut impl FnMut(&Expr, &Env) -> Result<VmValue, VmError>,
) -> Result<(), VmError> {
    match pattern {
        Pattern::Wildcard { .. } => Ok(()),
        Pattern::Bind { name, .. } => {
            env.insert(typed_ir::Path::from_name(name.clone()), value);
            Ok(())
        }
        Pattern::Lit { lit, .. } if value == value_from_lit(lit) => Ok(()),
        Pattern::Lit { .. } => Err(VmError::PatternMismatch),
        Pattern::Tuple { items, .. } => {
            let VmValue::Tuple(values) = value else {
                return Err(VmError::PatternMismatch);
            };
            if items.len() != values.len() {
                return Err(VmError::PatternMismatch);
            }
            for (item, value) in items.iter().zip(values) {
                bind_pattern_with_defaults(item, value, env, eval_default)?;
            }
            Ok(())
        }
        Pattern::Variant {
            tag,
            value: pattern_value,
            ..
        } => {
            let VmValue::Variant {
                tag: actual,
                value: actual_value,
            } = value
            else {
                return Err(VmError::PatternMismatch);
            };
            if tag != &actual {
                return Err(VmError::PatternMismatch);
            }
            match (pattern_value, actual_value) {
                (Some(pattern), Some(value)) => {
                    bind_pattern_with_defaults(pattern, *value, env, eval_default)
                }
                (None, None) => Ok(()),
                _ => Err(VmError::PatternMismatch),
            }
        }
        Pattern::Or { left, right, .. } => {
            let snapshot = env.clone();
            if bind_pattern_with_defaults(left, value.clone(), env, eval_default).is_ok() {
                return Ok(());
            }
            *env = snapshot;
            bind_pattern_with_defaults(right, value, env, eval_default)
        }
        Pattern::As { pattern, name, .. } => {
            bind_pattern_with_defaults(pattern, value.clone(), env, eval_default)?;
            env.insert(typed_ir::Path::from_name(name.clone()), value);
            Ok(())
        }
        Pattern::Record { fields, spread, .. } => {
            let VmValue::Record(values) = value else {
                return Err(VmError::PatternMismatch);
            };
            let mut rest = values.clone();
            for field in fields {
                rest.remove(&field.name);
                let Some(value) = values.get(&field.name).cloned() else {
                    let Some(default) = &field.default else {
                        return Err(VmError::PatternMismatch);
                    };
                    let value = eval_default(default, env)?;
                    bind_pattern_with_defaults(&field.pattern, value, env, eval_default)?;
                    continue;
                };
                bind_pattern_with_defaults(&field.pattern, value, env, eval_default)?;
            }
            if let Some(spread) = spread {
                let spread = match spread {
                    crate::RecordSpreadPattern::Head(pattern)
                    | crate::RecordSpreadPattern::Tail(pattern) => pattern,
                };
                bind_pattern_with_defaults(spread, VmValue::Record(rest), env, eval_default)?;
            }
            Ok(())
        }
        Pattern::List {
            prefix,
            spread,
            suffix,
            ..
        } => {
            let VmValue::List(values) = value else {
                return Err(VmError::PatternMismatch);
            };
            if values.len() < prefix.len() + suffix.len() {
                return Err(VmError::PatternMismatch);
            }
            if spread.is_none() && values.len() != prefix.len() + suffix.len() {
                return Err(VmError::PatternMismatch);
            }
            for (index, pattern) in prefix.iter().enumerate() {
                let Some(value) = values.index(index) else {
                    return Err(VmError::PatternMismatch);
                };
                bind_pattern_with_defaults(pattern, (*value).clone(), env, eval_default)?;
            }
            if let Some(spread) = spread {
                let start = prefix.len();
                let end = values.len() - suffix.len();
                let Some(slice) = values.index_range(start, end) else {
                    return Err(VmError::PatternMismatch);
                };
                bind_pattern_with_defaults(spread, VmValue::List(slice), env, eval_default)?;
            }
            let suffix_start = values.len() - suffix.len();
            for (offset, pattern) in suffix.iter().enumerate() {
                let Some(value) = values.index(suffix_start + offset) else {
                    return Err(VmError::PatternMismatch);
                };
                bind_pattern_with_defaults(pattern, (*value).clone(), env, eval_default)?;
            }
            Ok(())
        }
    }
}

pub(super) fn value_from_lit(lit: &typed_ir::Lit) -> VmValue {
    match lit {
        typed_ir::Lit::Int(value) => VmValue::Int(value.clone()),
        typed_ir::Lit::Float(value) => VmValue::Float(value.clone()),
        typed_ir::Lit::String(value) => VmValue::String(StringTree::from_str(value)),
        typed_ir::Lit::Bool(value) => VmValue::Bool(*value),
        typed_ir::Lit::Unit => VmValue::Unit,
    }
}

pub(super) fn cast_value(value: VmValue, expected: &typed_ir::Type) -> VmValue {
    if is_float_type(expected) {
        if let VmValue::Int(value) = value {
            return VmValue::Float(if value.contains('.') {
                value
            } else {
                format!("{value}.0")
            });
        }
    }
    value
}

pub(super) fn wrap_result_for_type(result: VmResult, expected_ty: &Type) -> VmResult {
    if !matches!(expected_ty, Type::Thunk { .. }) {
        return result;
    }
    match result {
        VmResult::Value(value) => VmResult::Value(wrap_value_for_type(value, expected_ty)),
        VmResult::Request(request) => VmResult::Request(push_frame(
            request,
            Frame::WrapThunkResult {
                expected_ty: expected_ty.clone(),
            },
        )),
    }
}

pub(super) fn wrap_value_for_type(value: VmValue, expected_ty: &Type) -> VmValue {
    if !matches!(expected_ty, Type::Thunk { .. }) || matches!(value, VmValue::Thunk(_)) {
        return value;
    }
    VmValue::Thunk(Rc::new(VmThunk {
        body: ThunkBody::Value(value),
        env: Env::new(),
        guard_stack: GuardStack::default(),
        blocked: Vec::new(),
    }))
}

pub(super) fn expects_thunk_arg(ty: &Type) -> bool {
    match ty {
        Type::Fun { param, .. } => matches!(param.as_ref(), Type::Thunk { .. }),
        Type::Unknown | Type::Core(_) | Type::Thunk { .. } => false,
    }
}

pub(super) fn int_value(value: &VmValue) -> Result<i64, VmError> {
    match value {
        VmValue::Int(value) => value
            .parse()
            .map_err(|_| VmError::ExpectedInt(value_from_string(value))),
        other => Err(VmError::ExpectedInt(other.clone())),
    }
}

pub(super) fn float_value(value: &VmValue) -> Result<f64, VmError> {
    match value {
        VmValue::Float(value) => value
            .parse()
            .map_err(|_| VmError::ExpectedFloat(VmValue::Float(value.clone()))),
        other => Err(VmError::ExpectedFloat(other.clone())),
    }
}

pub(super) fn format_float_value(value: f64) -> String {
    let mut rendered = value.to_string();
    if !rendered.contains('.') && !rendered.contains('e') && !rendered.contains('E') {
        rendered.push_str(".0");
    }
    rendered
}

pub(super) fn value_from_string(value: &str) -> VmValue {
    VmValue::String(StringTree::from_str(value))
}

pub(super) fn bool_value(value: &VmValue) -> Result<bool, VmError> {
    match value {
        VmValue::Bool(value) => Ok(*value),
        other => Err(VmError::ExpectedBool(other.clone())),
    }
}

pub(super) fn list_value(value: &VmValue) -> Result<&ListTree<Rc<VmValue>>, VmError> {
    match value {
        VmValue::List(value) => Ok(value),
        other => Err(VmError::ExpectedList(other.clone())),
    }
}

pub(super) fn string_value(value: &VmValue) -> Result<&StringTree, VmError> {
    match value {
        VmValue::String(value) => Ok(value),
        other => Err(VmError::ExpectedString(other.clone())),
    }
}

pub(super) fn bytes_value(value: &VmValue) -> Result<&BytesTree, VmError> {
    match value {
        VmValue::Bytes(value) => Ok(value),
        other => Err(VmError::ExpectedBytes(other.clone())),
    }
}

pub(super) fn path_value(value: &VmValue) -> Result<Rc<PathBuf>, VmError> {
    match value {
        VmValue::Path(value) => Ok(value.clone()),
        other => Err(VmError::ExpectedPath(other.clone())),
    }
}

pub(super) fn normalized_int_range_value(
    value: &VmValue,
    len: usize,
) -> Result<(usize, usize), VmError> {
    let original = value.clone();
    let VmValue::Variant { tag, value } = value else {
        return Err(VmError::ExpectedVariant(original));
    };
    if tag.0 != "within" {
        return Err(VmError::ExpectedVariant(original));
    }
    let Some(value) = value.as_ref() else {
        return Err(VmError::ExpectedVariant(original));
    };
    let VmValue::Tuple(items) = value.as_ref() else {
        return Err(VmError::ExpectedVariant((**value).clone()));
    };
    let [start, end] = items.as_slice() else {
        return Err(VmError::ExpectedVariant((**value).clone()));
    };
    let start = normalized_start_bound_value(start)?;
    let end = normalized_end_bound_value(end, len)?;
    if start > end || end > len {
        return Err(VmError::ExpectedVariant(value.as_ref().clone()));
    }
    Ok((start, end))
}

pub(super) fn normalized_start_bound_value(value: &VmValue) -> Result<usize, VmError> {
    let original = value.clone();
    let VmValue::Variant { tag, value } = value else {
        return Err(VmError::ExpectedVariant(original));
    };
    match tag.0.as_str() {
        "unbounded" => Ok(0),
        "included" => {
            let Some(value) = value.as_ref() else {
                return Err(VmError::ExpectedVariant(original));
            };
            usize::try_from(int_value(value)?).map_err(|_| VmError::ExpectedInt((**value).clone()))
        }
        "excluded" => {
            let Some(value) = value.as_ref() else {
                return Err(VmError::ExpectedVariant(original));
            };
            usize::try_from(int_value(value)? + 1)
                .map_err(|_| VmError::ExpectedInt((**value).clone()))
        }
        _ => Err(VmError::ExpectedVariant(original)),
    }
}

pub(super) fn normalized_end_bound_value(value: &VmValue, len: usize) -> Result<usize, VmError> {
    let original = value.clone();
    let VmValue::Variant { tag, value } = value else {
        return Err(VmError::ExpectedVariant(original));
    };
    match tag.0.as_str() {
        "unbounded" => Ok(len),
        "included" => {
            let Some(value) = value.as_ref() else {
                return Err(VmError::ExpectedVariant(original));
            };
            usize::try_from(int_value(value)? + 1)
                .map_err(|_| VmError::ExpectedInt((**value).clone()))
        }
        "excluded" => {
            let Some(value) = value.as_ref() else {
                return Err(VmError::ExpectedVariant(original));
            };
            usize::try_from(int_value(value)?).map_err(|_| VmError::ExpectedInt((**value).clone()))
        }
        _ => Err(VmError::ExpectedVariant(original)),
    }
}