geam 0.1.1

Experimental Rust-embedded execution runtime for typed Gleam programs
mod expression;
mod function;
mod module;

pub(crate) fn host_call_site(
    source: &str,
    function: &str,
    call: &str,
) -> crate::plan::HostCallSite {
    host_call_site_in(source, function, call, call)
}

pub(crate) fn host_call_site_in(
    source: &str,
    function: &str,
    context: &str,
    call: &str,
) -> crate::plan::HostCallSite {
    let function_start = if function.starts_with('<') {
        0
    } else {
        let function_declaration = format!("fn {function}(");
        let declaration_start = source
            .find(&function_declaration)
            .unwrap_or_else(|| panic!("function `{function}` should appear in the source"));
        source[declaration_start..]
            .find('{')
            .map(|body| declaration_start + body + 1)
            .unwrap_or_else(|| panic!("function `{function}` should have a body"))
    };
    let context_start = source[function_start..]
        .find(context)
        .map(|start| function_start + start)
        .unwrap_or_else(|| panic!("context `{context}` should appear in the source"));
    let call_start = context
        .find(call)
        .unwrap_or_else(|| panic!("call `{call}` should appear in context `{context}`"));
    let start = context_start + call_start;
    crate::plan::HostCallSite::new(
        "main".into(),
        function.into(),
        crate::plan::SourceSpan::new(start, start + call.len()),
    )
}

pub(crate) use expression::{
    bit_array, bit_array_function_ref, block_function, block_int, block_int_function, bool_,
    bool_arg, bool_case_int_function, bool_function_ref, call_bool_at, call_float_at, call_int_at,
    call_int_function_at, call_int_returning_function_at, call_list_at, capture_int, capture_tuple,
    equal, evaluate_step, float, float_arg, float_function_ref, function_function_closure,
    function_function_ref, function_ref, int, int_arg, int_case_int_function, int_function_arg,
    int_function_call_arg, int_function_closure, int_function_ref, let_bool_function_step,
    let_bool_step, let_float_step, let_int_function_step, let_int_step, let_list_step,
    let_nil_function_step, let_nil_step, let_string_function_step, let_string_step, let_tuple_step,
    let_utf_codepoint_step, list, list_function_ref, list_spread, local_bool, local_float,
    local_int, local_int_function, local_list, local_nil, local_string, local_tuple,
    local_utf_codepoint, nil, nil_arg, nil_function_ref, not_equal, string, string_arg,
    string_function_ref, tuple, tuple_arg, tuple_function_closure, tuple_function_ref,
    utf_codepoint_function_ref,
};
pub(crate) use function::{
    bool_function_return_block, bool_function_return_bool_case, bool_function_return_expr,
    bool_function_return_int_case, bool_function_return_string_case,
    bool_function_return_tail_call_at, bool_return_block, bool_return_bool_case, bool_return_expr,
    bool_return_float_case, bool_return_int_case, bool_return_string_case,
    bool_return_tail_call_at, float_return_block, float_return_expr, float_return_float_case,
    function, function_function_return_block, function_function_return_expr,
    function_function_return_int_case, function_function_return_string_case,
    function_function_return_tail_call_at, int_function_return_block,
    int_function_return_bool_case, int_function_return_expr, int_function_return_int_case,
    int_function_return_string_case, int_function_return_tail_call_at, int_return_block,
    int_return_bool_case, int_return_expr, int_return_float_case, int_return_int_case,
    int_return_string_case, int_return_tail_call_at, list_return_block, list_return_bool_case,
    list_return_expr, list_return_float_case, list_return_int_case, list_return_string_case,
    nil_function_return_block, nil_function_return_bool_case, nil_function_return_expr,
    nil_function_return_int_case, nil_function_return_string_case,
    nil_function_return_tail_call_at, nil_return_block, nil_return_bool_case, nil_return_expr,
    nil_return_float_case, nil_return_int_case, nil_return_string_case, nil_return_tail_call_at,
    return_bool_function, return_function_function, return_int_function, return_list,
    return_nil_function, return_string_function, string_function_return_block,
    string_function_return_bool_case, string_function_return_expr, string_function_return_int_case,
    string_function_return_string_case, string_function_return_tail_call_at, string_return_block,
    string_return_bool_case, string_return_expr, string_return_float_case, string_return_int_case,
    string_return_string_case, string_return_tail_call_at, utf_codepoint_return_block,
    utf_codepoint_return_expr,
};
pub(crate) use module::{module, module_with_anonymous};

#[cfg(test)]
mod tests {
    use super::{host_call_site, host_call_site_in};

    #[test]
    fn host_call_site_uses_exact_source_bytes() {
        let source = "pub fn main() { add(1, 2) }";

        let site = host_call_site(source, "main", "add(1, 2)");

        assert_eq!(site.module(), "main");
        assert_eq!(site.function(), "main");
        assert_eq!(site.span(), crate::plan::SourceSpan::new(16, 25));
    }

    #[test]
    #[should_panic(expected = "context `missing()` should appear in the source")]
    fn host_call_site_rejects_missing_call_text() {
        host_call_site("pub fn main() { 1 }", "main", "missing()");
    }

    #[test]
    #[should_panic(expected = "function `missing` should appear in the source")]
    fn host_call_site_rejects_missing_function() {
        host_call_site("pub fn main() { 1 }", "missing", "1");
    }

    #[test]
    #[should_panic(expected = "function `main` should have a body")]
    fn host_call_site_rejects_missing_function_body() {
        host_call_site("pub fn main()", "main", "main");
    }

    #[test]
    fn host_call_site_in_uses_the_visible_context() {
        let source = "pub fn main() { let f = 1\n1 |> f }";

        let site = host_call_site_in(source, "main", "1 |> f", "f");

        assert_eq!(site.span(), crate::plan::SourceSpan::new(31, 32));
    }

    #[test]
    #[should_panic(expected = "context `missing` should appear in the source")]
    fn host_call_site_in_rejects_missing_context() {
        host_call_site_in("pub fn main() { 1 }", "main", "missing", "1");
    }

    #[test]
    #[should_panic(expected = "call `missing` should appear in context `1`")]
    fn host_call_site_in_rejects_call_outside_context() {
        host_call_site_in("pub fn main() { 1 }", "main", "1", "missing");
    }
}