geam-core 0.2.2

Typed Gleam planning, host, and execution core for Geam
Documentation
use geam_core::{
    HostModule, HostProviderSet, HostedExecution, ModuleSource, PackageSource, Value,
    compile_typed_host_program, plan_host_program,
};
use num_bigint::BigInt;

#[test]
fn executes_package_qualified_rust_host_functions() {
    let math = HostModule::new("host_support", "host/math")
        .expect("host module should be valid")
        .with_function("subtract", |left: BigInt, right: BigInt| left - right)
        .expect("host function should be valid")
        .with_function("add", |left: BigInt, right: BigInt| left + right)
        .expect("host function should be valid")
        .with_function("unused", |left: BigInt, right: BigInt| left + right)
        .expect("host function should be valid");
    let hosts = HostProviderSet::new([math]).expect("host modules should be unique");
    let source = r#"
import host/math.{add, subtract}

fn apply(function: fn(Int, Int) -> Int, left: Int, right: Int) {
  function(left, right)
}

fn tail(left: Int, right: Int) {
  add(left, right)
}

fn left() {
  echo 100000000000000000000000000000000000000 as "left"
}

fn right() {
  echo 3 as "right"
}

pub fn main() {
  let qualified = math.add
  #(
    subtract(left(), right()),
    apply(qualified, 20, 22),
    tail(40, 2),
    qualified == add,
    add == add,
  )
}
"#;
    let typed = compile_typed_host_program(
        "application",
        "main",
        [PackageSource::new(
            "application",
            ["host_support"],
            [ModuleSource::new("main", "main.gleam", source)],
        )],
        hosts,
    )
    .expect("host program should compile");
    let plan = plan_host_program(typed).expect("host program should plan");
    assert_eq!(
        plan.modules()[0]
            .functions()
            .iter()
            .map(|function| {
                function
                    .host_template()
                    .expect("dependency should retain host functions")
                    .name()
                    .as_str()
            })
            .collect::<Vec<_>>(),
        ["subtract", "add", "unused"],
    );
    let execution =
        HostedExecution::try_from_module_plan(plan).expect("hosted execution should seal");
    let expected = Value::Tuple(vec![
        Value::Int(
            "99999999999999999999999999999999999997"
                .parse()
                .expect("expected value should be an Int"),
        ),
        Value::Int(42.into()),
        Value::Int(42.into()),
        Value::Bool(true),
        Value::Bool(true),
    ]);

    let mut first_echoes = Vec::new();
    assert_eq!(
        execution.run_main(&mut (), &mut first_echoes),
        Ok(expected.clone()),
    );
    assert_eq!(
        first_echoes
            .iter()
            .map(|output| output.message().map(|message| message.as_str()))
            .collect::<Vec<_>>(),
        [Some("left"), Some("right")],
    );

    let mut second_echoes = Vec::new();
    assert_eq!(
        execution.run_main(&mut (), &mut second_echoes),
        Ok(expected),
    );
    assert_eq!(
        second_echoes
            .iter()
            .map(|output| output.message().map(|message| message.as_str()))
            .collect::<Vec<_>>(),
        [Some("left"), Some("right")],
    );

    let expected_explanation = r#"
module main
main tuple#0

function int#0
  host host_support::host/math.add signature=fn(Int, Int) -> Int

function int#1
  entry b0 params=[] captures=[]
  block b0 params=[]
    %int#0:shape#0(Int) = int.value 100000000000000000000000000000000000000
    %string#0:shape#4(String) = string.value "left"
    echo subject=%int#0 message=%string#0 site=main::left@196..250 next=b1(%int#0)
  block b1 params=[%int#0:shape#0(Int)]
    return %int#0

function int#2
  entry b0 params=[] captures=[]
  block b0 params=[]
    %int#0:shape#0(Int) = int.value 3
    %string#0:shape#4(String) = string.value "right"
    echo subject=%int#0 message=%string#0 site=main::right@269..286 next=b1(%int#0)
  block b1 params=[%int#0:shape#0(Int)]
    return %int#0

function int#3
  host host_support::host/math.subtract signature=fn(Int, Int) -> Int

function int#4
  entry b0 params=[%function.int#0:shape#1(fn(Int, Int) -> Int), %int#0:shape#0(Int), %int#1:shape#0(Int)] captures=[]
  block b0 params=[%function.int#0:shape#1(fn(Int, Int) -> Int), %int#0:shape#0(Int), %int#1:shape#0(Int)]
    %int#2:shape#0(Int) = int.function_call %function.int#0 args=[%int#0, %int#1]
    return %int#2

function int#5
  entry b0 params=[%int#0:shape#0(Int), %int#1:shape#0(Int)] captures=[]
  block b0 params=[%int#0:shape#0(Int), %int#1:shape#0(Int)]
    tail int#0 args=[%int#0, %int#1]

function tuple#0
  entry b0 params=[] captures=[]
  block b0 params=[]
    %function.int#0:shape#1(fn(Int, Int) -> Int) = function[Int] reference int#0
    %int#0:shape#0(Int) = int.call int#1 args=[]
    %int#1:shape#0(Int) = int.call int#2 args=[]
    %int#2:shape#0(Int) = int.call int#3 args=[%int#0, %int#1]
    %int#3:shape#0(Int) = int.value 20
    %int#4:shape#0(Int) = int.value 22
    %int#5:shape#0(Int) = int.call int#4 args=[%function.int#0, %int#3, %int#4]
    %int#6:shape#0(Int) = int.value 40
    %int#7:shape#0(Int) = int.value 2
    %int#8:shape#0(Int) = int.call int#5 args=[%int#6, %int#7]
    %function.int#1:shape#1(fn(Int, Int) -> Int) = function[Int] reference int#0
    %bool#0:shape#2(Bool) = bool.equal %function.int#0 %function.int#1
    %function.int#2:shape#1(fn(Int, Int) -> Int) = function[Int] reference int#0
    %function.int#3:shape#1(fn(Int, Int) -> Int) = function[Int] reference int#0
    %bool#1:shape#2(Bool) = bool.equal %function.int#2 %function.int#3
    %tuple#0:shape#3(#(Int, Int, Int, Bool, Bool)) = tuple.value elements=[%int#2, %int#5, %int#8, %bool#0, %bool#1]
    return %tuple#0
"#;

    assert_eq!(
        execution.explain().to_string().trim(),
        expected_explanation.trim(),
    );
}