gluon 0.18.2

A static, type inferred programming language for application embedding
Documentation
use std::fs;

use tempfile::NamedTempFile;

use gluon::{
    new_vm,
    vm::api::{Hole, OpaqueValue, OwnedFunction, ValueRef, IO},
    Thread, ThreadExt,
};

use tokio::runtime::Runtime;

#[macro_use]
mod support;

use crate::support::*;

#[test]
fn read_file() {
    let _ = ::env_logger::try_init();

    let thread = new_vm();
    thread.get_database_mut().run_io(true);
    let text = r#"
        let prelude = import! std.prelude
        let array = import! std.array
        let { assert }  = import! std.test
        let io @ { ? } = import! std.io
        let { wrap } = io.applicative
        let { ? } = import! std.byte
        let { unwrap } = import! std.option
        let { flat_map, (>>=) } = import! std.monad

        do file = io.open_file "Cargo.toml"
        do bytes = io.read_file file 9
        let bytes = unwrap bytes

        let _ = assert (array.len bytes == 9)
        let _ = assert (array.index bytes 0 == 91b) // [
        let _ = assert (array.index bytes 1 == 112b) // p

        wrap (array.index bytes 8)
        "#;
    let result = thread.run_expr::<IO<u8>>("<top>", text);

    match result {
        Ok((IO::Value(value), _)) => assert_eq!(value, b']'),
        Ok((IO::Exception(err), _)) => assert!(false, "{}", err),
        Err(err) => assert!(false, "{}", err),
    }
}

#[test]
fn write_and_flush_file() {
    let _ = ::env_logger::try_init();

    let file = NamedTempFile::new().unwrap();

    let thread = new_vm();
    thread.get_database_mut().run_io(true);
    let text = r#"
        let { assert } = import! std.test
        let { OpenOptions, open_file_with, write_slice_file, flush_file, ? } = import! std.io

        \path ->
            do file = open_file_with path [Write]
            do bytes_written = write_slice_file file [1b, 2b, 3b, 4b] 0 4
            let _ = assert (bytes_written == 4)
            flush_file file
    "#;

    let (mut test, _) = thread
        .run_expr::<OwnedFunction<fn(String) -> IO<()>>>("<top>", &text)
        .unwrap_or_else(|err| panic!("{}", err));

    test.call(file.path().to_str().unwrap().to_owned())
        .unwrap_or_else(|err| panic!("{}", err));

    let content = fs::read(file.path()).unwrap();
    assert_eq!(content, vec![1, 2, 3, 4]);
}

test_expr! { no_io_eval,
r#"
let { error } = import! std.prim
let io = import! std.io
let x = io.flat_map (\x -> error "NOOOOOOOO") (io.println "1")
in { x }
"#
}

test_expr! { io_print,
r#"
let io = import! std.io
io.print "123"
"#
}

#[test]
fn run_expr_int() {
    let _ = ::env_logger::try_init();

    let text = r#"
        let io = import! std.io
        let { flat_map } = io.monad
        do result = io.run_expr "123"
        io.applicative.wrap result.value
    "#;
    let vm = make_vm();
    vm.get_database_mut().run_io(true);
    let (result, _) = vm.run_expr::<IO<String>>("<top>", text).unwrap();
    match result {
        IO::Value(result) => {
            let expected = "123";
            assert_eq!(result, expected);
        }
        IO::Exception(err) => panic!("{}", err),
    }
}

test_expr! { io run_expr_io,
r#"
let io = import! std.io
io.flat_map (\x -> io.wrap 100)
            (io.run_expr "
                let io = import! \"std/io.glu\"
                io.print \"123\"
            ")
"#,
100i32
}

#[test]
fn dont_execute_io_in_run_expr_async() {
    let _ = ::env_logger::try_init();
    let vm = make_vm();
    let expr = r#"
let prelude  = import! std.prelude
let io = import! std.io
let { wrap } = io.applicative
wrap 123
"#;
    let value = vm
        .run_expr::<OpaqueValue<&Thread, Hole>>("example", expr)
        .unwrap_or_else(|err| panic!("{}", err));
    assert!(
        value.0.get_ref() != ValueRef::Int(123),
        "Unexpected {:?}",
        value.0
    );
}

#[test]
fn spawn_on_twice() {
    let _ = ::env_logger::try_init();

    let text = r#"
        let { applicative = { wrap }, monad = { flat_map } } = import! std.io
        let thread = import! std.thread

        do child = thread.new_thread ()
        do action = thread.spawn_on child (wrap "abc")
        action
    "#;

    let runtime = Runtime::new().unwrap();
    let vm = make_vm();
    vm.get_database_mut().run_io(true);
    let (result, _) = runtime
        .block_on(vm.run_expr_async::<IO<String>>("<top>", text))
        .unwrap_or_else(|err| panic!("{}", err));
    match result {
        IO::Value(result) => {
            assert_eq!(result, "abc");
        }
        IO::Exception(err) => panic!("{}", err),
    }

    let (result, _) = runtime
        .block_on(vm.run_expr_async::<IO<String>>("<top>", text))
        .unwrap_or_else(|err| panic!("{}", err));
    match result {
        IO::Value(result) => {
            assert_eq!(result, "abc");
        }
        IO::Exception(err) => panic!("{}", err),
    }
}

#[test]
fn spawn_on_runexpr() {
    let _ = ::env_logger::try_init();

    let text = r#"
        let io@{ applicative = applicative@{ wrap }, monad = { flat_map }, ? } = import! std.io
        let thread = import! std.thread

        do child = thread.new_thread ()
        do action = thread.spawn_on child (io.run_expr "123")
        do x = action
        seq io.println x.value
        wrap x.value
    "#;

    let runtime = Runtime::new().unwrap();
    let vm = make_vm();
    vm.get_database_mut().run_io(true);
    let (result, _) = runtime
        .block_on(vm.run_expr_async::<IO<String>>("<top>", text))
        .unwrap_or_else(|err| panic!("{}", err));
    match result {
        IO::Value(result) => {
            assert_eq!(result, "123");
        }
        IO::Exception(err) => panic!("{}", err),
    }
}

#[test]
fn spawn_on_do_action_twice() {
    let _ = ::env_logger::try_init();

    let text = r#"
        let { ? } = import! std.io
        let { ref, modify, load } = import! std.reference
        let thread = import! std.thread
        let { wrap } = import! std.applicative
        let { join } = import! std.monad

        do counter = ref 0

        do child = thread.new_thread ()
        let action = thread.spawn_on child (modify counter (\x -> x + 1))
        seq join action
        seq join action
        load counter
    "#;

    let runtime = Runtime::new().unwrap();
    let vm = make_vm();
    vm.get_database_mut().run_io(true);
    let (result, _) = runtime
        .block_on(vm.run_expr_async::<IO<i32>>("<top>", text))
        .unwrap_or_else(|err| panic!("{}", err));
    assert_eq!(result, IO::Value(2));
}

#[test]
fn spawn_on_force_action_twice() {
    let _ = ::env_logger::try_init();

    let text = r#"
        let { ? } = import! std.io
        let { ref, modify, load } = import! std.reference
        let thread = import! std.thread
        let { wrap } = import! std.applicative

        do counter = ref 0

        do child = thread.new_thread ()
        do action = thread.spawn_on child (modify counter (\x -> x + 1))
        seq action
        seq action
        load counter
    "#;

    let runtime = Runtime::new().unwrap();
    let vm = make_vm();
    vm.get_database_mut().run_io(true);
    let (result, _) = runtime
        .block_on(vm.run_expr_async::<IO<i32>>("<top>", text))
        .unwrap_or_else(|err| panic!("{}", err));
    assert_eq!(result, IO::Value(1));
}

#[test]
fn spawn_on_runexpr_in_catch() {
    let _ = ::env_logger::try_init();

    let text = r#"
        let prelude = import! std.prelude
        let io @ { ? } = import! std.io
        let { Applicative, (*>), wrap } = import! std.applicative
        let { flat_map, (>>=) } = import! std.monad
        let thread = import! std.thread

        let action =
            do eval_thread = thread.new_thread ()
            do a = thread.spawn_on eval_thread (io.run_expr "123")
            do result = a
            wrap result.value
        (io.catch action wrap >>= io.println) *> wrap "123"
    "#;

    let runtime = Runtime::new().unwrap();
    let vm = make_vm();
    vm.get_database_mut().run_io(true);
    let (result, _) = runtime
        .block_on(vm.run_expr_async::<IO<String>>("<top>", text))
        .unwrap_or_else(|err| panic!("{}", err));
    match result {
        IO::Value(result) => {
            assert_eq!(result, "123");
        }
        IO::Exception(err) => panic!("{}", err),
    }

    let (result, _) = runtime
        .block_on(vm.run_expr_async::<IO<String>>("<top>", text))
        .unwrap_or_else(|err| panic!("{}", err));
    match result {
        IO::Value(result) => {
            assert_eq!(result, "123");
        }
        IO::Exception(err) => panic!("{}", err),
    }
}

#[test]
fn io_error_in_catch1() {
    let _ = ::env_logger::try_init();

    let expr = r#"
        let io = import! std.io
        io.catch (io.read_file_to_string "doesnotexist") (\_ -> io.applicative.wrap "error")
    "#;

    let vm = make_vm();

    vm.get_database_mut().implicit_prelude(false).run_io(true);

    let (result, _) = vm
        .run_expr::<IO<String>>("<top>", expr)
        .unwrap_or_else(|err| panic!("{}", err));
    let expected = IO::Value("error".to_string());

    assert_eq!(result, expected);
}

#[test]
fn io_error_in_catch2() {
    let _ = ::env_logger::try_init();

    let expr = r#"
        let { (*>) } = import! std.applicative
        let io @ { ? } = import! std.io
        io.catch (io.println "asd" *> io.read_file_to_string "doesnotexist") (\_ -> io.applicative.wrap "error")
    "#;

    let vm = make_vm();

    vm.get_database_mut().implicit_prelude(false).run_io(true);

    let (result, _) = vm
        .run_expr::<IO<String>>("<top>", expr)
        .unwrap_or_else(|err| panic!("{}", err));
    let expected = IO::Value("error".to_string());

    assert_eq!(result, expected);
}

#[test]
fn io_error_in_catch3() {
    let _ = ::env_logger::try_init();

    let expr = r#"
        let io @ { ? } = import! std.io
        let { flat_map } = import! std.monad
        do _ = io.catch (io.read_file_to_string "doesnotexist") (\_ -> io.applicative.wrap "error")
        io.catch (io.read_file_to_string "doesnotexist") (\_ -> io.applicative.wrap "error2")
    "#;

    let vm = make_vm();

    vm.get_database_mut().implicit_prelude(false).run_io(true);

    let (result, _) = vm
        .run_expr::<IO<String>>("<top>", expr)
        .unwrap_or_else(|err| panic!("{}", err));
    let expected = IO::Value("error2".to_string());

    assert_eq!(result, expected);
}