harn-kernel 0.10.55

Portable compiler, program artifact, and deterministic execution kernel for Harn
Documentation
use super::*;

pub(super) fn binary(
    frame: &mut Frame,
    operation: fn(RuntimeValue, RuntimeValue) -> Result<RuntimeValue, Diagnostic>,
) -> Result<RuntimeValue, OpStep> {
    let right = frame
        .stack
        .pop()
        .ok_or_else(|| OpStep::Error(diagnostic("stack_underflow", "binary rhs missing")))?;
    let left = frame
        .stack
        .pop()
        .ok_or_else(|| OpStep::Error(diagnostic("stack_underflow", "binary lhs missing")))?;
    operation(left, right).map_err(OpStep::Error)
}
pub(super) fn compare(
    machine: &mut Machine<'_>,
    frame: &mut Frame,
    predicate: fn(i8) -> bool,
) -> Result<(), OpStep> {
    let right = frame
        .stack
        .pop()
        .ok_or_else(|| OpStep::Error(diagnostic("stack_underflow", "comparison rhs missing")))?;
    let left = frame
        .stack
        .pop()
        .ok_or_else(|| OpStep::Error(diagnostic("stack_underflow", "comparison lhs missing")))?;
    machine
        .charge_values_work(&[&left, &right])
        .map_err(OpStep::Error)?;
    let value = ordering(&left, &right).map(predicate).unwrap_or(false);
    frame.stack.push(RuntimeValue::Bool(value));
    Ok(())
}

pub(super) fn compare_equality(
    machine: &mut Machine<'_>,
    frame: &mut Frame,
    expected: bool,
) -> Result<(), OpStep> {
    let right = frame
        .stack
        .pop()
        .ok_or_else(|| OpStep::Error(diagnostic("stack_underflow", "comparison rhs missing")))?;
    let left = frame
        .stack
        .pop()
        .ok_or_else(|| OpStep::Error(diagnostic("stack_underflow", "comparison lhs missing")))?;
    let equal = machine.values_equal(&left, &right).map_err(OpStep::Error)?;
    frame.stack.push(RuntimeValue::Bool(equal == expected));
    Ok(())
}
pub(super) fn equal(a: &RuntimeValue, b: &RuntimeValue) -> bool {
    semantic_values_equal(a, b)
}
pub(super) fn ordering(a: &RuntimeValue, b: &RuntimeValue) -> Option<i8> {
    semantic_try_compare(a, b)
}
pub(super) fn get_property(value: &RuntimeValue, name: &str) -> Option<RuntimeValue> {
    match value {
        RuntimeValue::Record(values) => values.get(name).cloned(),
        RuntimeValue::Enum(value) if name == "variant" => {
            Some(RuntimeValue::String(value.variant.clone()))
        }
        RuntimeValue::Enum(value) if name == "fields" => {
            Some(RuntimeValue::List(value.fields.clone()))
        }
        RuntimeValue::List(values) if name == "count" => {
            Some(RuntimeValue::Int(values.len() as i64))
        }
        RuntimeValue::String(value) if name == "count" => {
            Some(RuntimeValue::Int(value.chars().count() as i64))
        }
        RuntimeValue::Harness(root) if root == "root" => {
            Some(RuntimeValue::Harness(name.to_string()))
        }
        _ => None,
    }
}

pub(super) fn set_property_value(
    target: RuntimeValue,
    property: &str,
    value: RuntimeValue,
) -> Result<RuntimeValue, Diagnostic> {
    let RuntimeValue::Record(values) = target else {
        return Err(diagnostic(
            "property_assignment",
            format!("cannot set property `{property}` on a non-record value"),
        ));
    };
    let mut updated = Rc::unwrap_or_clone(values);
    updated.insert(property.to_string(), value);
    Ok(RuntimeValue::Record(Rc::new(updated)))
}

pub(super) fn set_subscript_value(
    target: RuntimeValue,
    index: RuntimeValue,
    value: RuntimeValue,
) -> Result<RuntimeValue, Diagnostic> {
    match target {
        RuntimeValue::List(values) => {
            let RuntimeValue::Int(index) = index else {
                return Err(diagnostic(
                    "subscript_assignment",
                    "list assignment requires an integer index",
                ));
            };
            let Some(index) = normalized_index(values.len(), index) else {
                return Err(diagnostic(
                    "subscript_assignment",
                    "list assignment index is out of bounds",
                ));
            };
            let mut updated = Rc::unwrap_or_clone(values);
            updated[index] = value;
            Ok(RuntimeValue::List(Rc::new(updated)))
        }
        RuntimeValue::Record(values) => {
            let mut updated = Rc::unwrap_or_clone(values);
            updated.insert(index.display(), value);
            Ok(RuntimeValue::Record(Rc::new(updated)))
        }
        _ => Err(diagnostic(
            "subscript_assignment",
            "only lists and records support subscript assignment",
        )),
    }
}
pub(super) fn slice(
    value: RuntimeValue,
    start: RuntimeValue,
    end: RuntimeValue,
) -> Result<RuntimeValue, Diagnostic> {
    match value {
        RuntimeValue::List(values) => {
            let (start, end) = slice_bounds(values.len(), start, end)?;
            Ok(RuntimeValue::List(Rc::new(values[start..end].to_vec())))
        }
        RuntimeValue::String(value) => {
            let chars: Vec<_> = value.chars().collect();
            let (start, end) = slice_bounds(chars.len(), start, end)?;
            Ok(RuntimeValue::String(Arc::from(
                chars[start..end].iter().collect::<String>(),
            )))
        }
        _ => Err(diagnostic(
            "slice_type",
            "slice receiver must be list or string",
        )),
    }
}

pub(super) fn normalized_index(length: usize, index: i64) -> Option<usize> {
    let length = i64::try_from(length).ok()?;
    let index = if index < 0 {
        length.checked_add(index)?
    } else {
        index
    };
    (0..length).contains(&index).then_some(index as usize)
}

pub(super) fn slice_bounds(
    length: usize,
    start: RuntimeValue,
    end: RuntimeValue,
) -> Result<(usize, usize), Diagnostic> {
    let length = i64::try_from(length)
        .map_err(|_| diagnostic("slice_range", "slice receiver is too large"))?;
    let bound = |value: RuntimeValue, default: i64, label: &str| match value {
        RuntimeValue::Nil => Ok(default),
        RuntimeValue::Int(value) if value < 0 => Ok((length + value).max(0)),
        RuntimeValue::Int(value) => Ok(value.min(length)),
        _ => Err(diagnostic(
            "slice_type",
            format!("slice {label} must be int or nil"),
        )),
    };
    let start = bound(start, 0, "start")?;
    let end = bound(end, length, "end")?;
    if start >= end {
        Ok((0, 0))
    } else {
        Ok((start as usize, end as usize))
    }
}
pub(super) fn runtime_value_kind(value: &RuntimeValue) -> &'static str {
    match value {
        RuntimeValue::Nil => "nil",
        RuntimeValue::Bool(_) => "bool",
        RuntimeValue::Int(_) => "int",
        RuntimeValue::Float(_) => "float",
        RuntimeValue::String(_) => "string",
        RuntimeValue::Bytes(_) => "bytes",
        RuntimeValue::List(_) => "list",
        RuntimeValue::Record(_) => "record",
        RuntimeValue::Enum(_) => "enum",
        RuntimeValue::Closure(_) => "closure",
        RuntimeValue::Builtin(_) => "builtin",
        RuntimeValue::Harness(_) => "harness",
    }
}
pub(super) fn handle_throw(frames: &mut Vec<Frame>, value: RuntimeValue) -> bool {
    while let Some(frame) = frames.last_mut() {
        if let Some(handler) = frame.handlers.pop() {
            frame.stack.truncate(handler.stack_depth);
            frame.env = handler.env;
            frame.stack.push(value);
            frame.ip = handler.target;
            return true;
        }
        frames.pop();
    }
    false
}

pub(super) fn request_id(
    digest: [u8; 32],
    ordinal: u64,
    capability: &str,
    operation: &str,
    arguments: &DataValue,
) -> String {
    let mut hasher = blake3::Hasher::new();
    hasher.update(&digest);
    hasher.update(&ordinal.to_be_bytes());
    hasher.update(capability.as_bytes());
    hasher.update(&[0]);
    hasher.update(operation.as_bytes());
    hasher.update(&serde_json::to_vec(arguments).unwrap_or_default());
    hasher.finalize().to_hex()[..32].to_string()
}
pub(super) fn diagnostic(code: &str, message: impl Into<String>) -> Diagnostic {
    Diagnostic {
        code: code.to_string(),
        message: message.into(),
        line: None,
        column: None,
    }
}
pub(super) fn failed(code: &str, message: impl Into<String>) -> Execution {
    Execution::Failed {
        diagnostic: diagnostic(code, message),
    }
}