geam 0.1.2

Experimental Rust-embedded execution runtime for typed Gleam programs
use super::function::JsonProvider;
use super::{Component, GleamJsonProfile, GleamJsonRunState, GleamJsonStores};
use crate::gleam_stdlib::{
    Component as GleamStdlibComponent, DictSchema, DynamicSchema, GleamStdlibHostProfile,
    GleamStdlibRunState, GleamStdlibStores, IoOutput, StringTreeSchema,
};
use crate::{
    HostComponentProfile, HostModule, HostProfile, HostProviderModule, HostProviderSet,
    HostedExecution, ModuleSource, PackageSource, compile_typed_host_program, plan_host_program,
};
use ecow::EcoString;

pub(super) struct CustomProfile;

#[derive(Default)]
pub(super) struct CustomStores {
    pub(super) stdlib: GleamStdlibStores,
    pub(super) json: GleamJsonStores,
}

pub(super) struct CustomRunState {
    pub(super) stdlib: GleamStdlibRunState,
    pub(super) json: (),
}

impl HostProfile for CustomProfile {
    type RunState = CustomRunState;
    type ExternalStores = CustomStores;
}

impl HostComponentProfile<GleamStdlibComponent> for CustomProfile {
    fn component_stores(stores: &Self::ExternalStores) -> &GleamStdlibStores {
        &stores.stdlib
    }

    fn component_state(state: &mut Self::RunState) -> &mut GleamStdlibRunState {
        &mut state.stdlib
    }
}

impl GleamStdlibHostProfile for CustomProfile {
    type Io = Vec<IoOutput>;
}

impl HostComponentProfile<Component> for CustomProfile {
    fn component_stores(stores: &Self::ExternalStores) -> &GleamJsonStores {
        &stores.json
    }

    fn component_state(state: &mut Self::RunState) -> &mut () {
        &mut state.json
    }
}

const DYNAMIC_SOURCE: &str = "pub type Dynamic";
const DICT_SOURCE: &str = "pub type Dict(key, value)";
const STRING_TREE_SOURCE: &str = "pub type StringTree";
const DYNAMIC_DECODE_SOURCE: &str = r#"
pub type DecodeError {
  DecodeError(expected: String, found: String, path: List(String))
}
"#;
const JSON_DECLARATIONS: &str = r#"
import gleam/dynamic.{type Dynamic}
import gleam/dynamic/decode
import gleam/string_tree.{type StringTree}

pub type Json

pub type DecodeError {
  UnexpectedEndOfInput
  UnexpectedByte(String)
  UnexpectedSequence(String)
  UnableToDecode(List(decode.DecodeError))
}

@external(erlang, "host", "decode_to_dynamic")
fn decode_to_dynamic(json: BitArray) -> Result(Dynamic, DecodeError)

@external(erlang, "host", "do_to_string")
fn do_to_string(json: Json) -> String

@external(erlang, "host", "to_string_tree")
fn to_string_tree(json: Json) -> StringTree

@external(erlang, "host", "do_string")
fn do_string(value: String) -> Json

@external(erlang, "host", "do_bool")
fn do_bool(value: Bool) -> Json

@external(erlang, "host", "do_int")
fn do_int(value: Int) -> Json

@external(erlang, "host", "do_float")
fn do_float(value: Float) -> Json

@external(erlang, "host", "do_null")
fn do_null() -> Json

@external(erlang, "host", "do_object")
fn do_object(entries: List(#(String, Json))) -> Json

@external(erlang, "host", "do_preprocessed_array")
fn do_preprocessed_array(values: List(Json)) -> Json
"#;

pub(super) fn execution(source: &str) -> HostedExecution<GleamJsonProfile> {
    execution_with_modules(source, Vec::<HostModule<GleamJsonProfile>>::new())
}

pub(super) fn execution_with_modules(
    source: &str,
    modules: impl IntoIterator<Item = HostModule<GleamJsonProfile>>,
) -> HostedExecution<GleamJsonProfile> {
    let providers =
        [
            HostProviderModule::new("gleam_stdlib", "gleam/dynamic")
                .and_then(
                    HostProviderModule::with_external_type::<
                        JsonProvider<GleamJsonProfile>,
                        DynamicSchema,
                    >,
                )
                .expect("synthetic Dynamic declaration should register"),
            HostProviderModule::new("gleam_stdlib", "gleam/dict")
                .and_then(
                    HostProviderModule::with_external_type::<
                        JsonProvider<GleamJsonProfile>,
                        DictSchema,
                    >,
                )
                .expect("synthetic Dict declaration should register"),
            HostProviderModule::new("gleam_stdlib", "gleam/string_tree")
                .and_then(
                    HostProviderModule::with_external_type::<
                        JsonProvider<GleamJsonProfile>,
                        StringTreeSchema,
                    >,
                )
                .expect("synthetic StringTree declaration should register"),
            super::host_provider::<GleamJsonProfile>()
                .expect("official JSON provider should register"),
        ];
    let source = format!("{JSON_DECLARATIONS}\n{source}");
    let packages = [
        PackageSource::new(
            "gleam_stdlib",
            Vec::<EcoString>::new(),
            [
                ModuleSource::new("gleam/dynamic", "src/gleam/dynamic.gleam", DYNAMIC_SOURCE),
                ModuleSource::new("gleam/dict", "src/gleam/dict.gleam", DICT_SOURCE),
                ModuleSource::new(
                    "gleam/string_tree",
                    "src/gleam/string_tree.gleam",
                    STRING_TREE_SOURCE,
                ),
                ModuleSource::new(
                    "gleam/dynamic/decode",
                    "src/gleam/dynamic/decode.gleam",
                    DYNAMIC_DECODE_SOURCE,
                ),
            ],
        ),
        PackageSource::new(
            "gleam_json",
            [EcoString::from("gleam_stdlib")],
            [ModuleSource::new(
                "gleam/json",
                "src/gleam/json.gleam",
                source,
            )],
        ),
    ];
    let hosts = HostProviderSet::with_providers(modules, providers)
        .expect("synthetic provider modules should be unique");
    let typed = compile_typed_host_program("gleam_json", "gleam/json", packages, hosts)
        .expect("synthetic JSON source should compile");
    let plan = plan_host_program(typed).expect("synthetic JSON source should plan");
    HostedExecution::try_from_module_plan(plan).expect("synthetic JSON execution should seal")
}

pub(super) fn run_state(seed: [u8; 32]) -> GleamJsonRunState {
    GleamJsonRunState::new(GleamStdlibRunState::from_seed(seed))
}