aver-lang 0.10.1

VM and transpiler for Aver, a statically-typed language designed for AI-assisted development
Documentation
use std::cell::RefCell;

use crate::AverStr;
use crate::aver_generated::domain::ast::FnDef;
use crate::aver_generated::domain::eval::callResolved;
use crate::aver_generated::domain::eval::store::{FnStore, fnsToStore, lookupFnOption};
use crate::aver_generated::domain::value::Val;

thread_local! {
    static SELF_HOST_FN_STORE: RefCell<Option<FnStore>> = const { RefCell::new(None) };
}

pub fn with_fn_store<T, F>(fns: FnStore, run: F) -> T
where
    F: FnOnce() -> T,
{
    let previous = SELF_HOST_FN_STORE.with(|cell| cell.replace(Some(fns)));
    let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(run));
    SELF_HOST_FN_STORE.with(|cell| {
        cell.replace(previous);
    });
    match result {
        Ok(value) => value,
        Err(payload) => std::panic::resume_unwind(payload),
    }
}

pub fn with_program_fn_store<T, F>(
    local_fns: crate::aver_rt::AverList<FnDef>,
    module_fns: crate::aver_rt::AverList<FnDef>,
    run: F,
) -> T
where
    F: FnOnce() -> T,
{
    let all_fns = crate::aver_rt::AverList::concat(&module_fns, &local_fns);
    let fns = fnsToStore(&all_fns);
    with_fn_store(fns, run)
}

fn current_fn_store() -> Option<FnStore> {
    SELF_HOST_FN_STORE.with(|cell| cell.borrow().clone())
}

pub fn http_server_listen(port: i64, handler: Val) -> Result<(), AverStr> {
    if crate::runtime_support::should_skip_http_server() {
        return Ok(());
    }
    crate::aver_rt::http_server::listen(port, move |request| {
        dispatch_http_handler(handler.clone(), None, request)
    })
    .map_err(AverStr::from)
}

pub fn http_server_listen_with(port: i64, context: Val, handler: Val) -> Result<(), AverStr> {
    if crate::runtime_support::should_skip_http_server() {
        return Ok(());
    }
    crate::aver_rt::http_server::listen_with(port, context, move |ctx, request| {
        dispatch_http_handler(handler.clone(), Some(ctx), request)
    })
    .map_err(AverStr::from)
}

fn dispatch_http_handler(
    handler: Val,
    context: Option<Val>,
    request: crate::aver_rt::HttpRequest,
) -> crate::aver_rt::HttpResponse {
    let handler_name = match handler {
        Val::ValFnRef(name) => name,
        other => {
            return http_error_response(format!(
                "HttpServer handler must be a function reference, got {}",
                crate::aver_rt::aver_display(&other)
            ));
        }
    };

    let Some(fns) = current_fn_store() else {
        return http_error_response(
            "Self-host HttpServer callback store is not active".to_string(),
        );
    };

    let Some(fd) = lookupFnOption(&fns, handler_name) else {
        return http_error_response("Self-host HttpServer handler is not loaded".to_string());
    };

    let mut callback_args = Vec::new();
    if let Some(ctx) = context {
        callback_args.push(ctx);
    }
    callback_args.push(http_request_to_val(request));

    let args_list = crate::aver_rt::AverList::from_vec(callback_args);
    match callResolved(&fd, &args_list, &fns) {
        Ok(value) => match val_to_http_response(value) {
            Ok(resp) => resp,
            Err(err) => http_error_response(err.to_string()),
        },
        Err(err) => http_error_response(err.to_string()),
    }
}

fn http_request_to_val(request: crate::aver_rt::HttpRequest) -> Val {
    Val::ValRecord(
        AverStr::from("HttpRequest"),
        crate::aver_rt::AverList::from_vec(vec![
            (AverStr::from("method"), Val::ValStr(request.method)),
            (AverStr::from("path"), Val::ValStr(request.path)),
            (AverStr::from("body"), Val::ValStr(request.body)),
            (
                AverStr::from("headers"),
                Val::ValList(headers_to_val(request.headers)),
            ),
        ]),
    )
}

fn headers_to_val(
    headers: crate::aver_rt::AverList<crate::aver_rt::Header>,
) -> crate::aver_rt::AverList<Val> {
    crate::aver_rt::AverList::from_vec(
        headers
            .to_vec()
            .into_iter()
            .map(|header| {
                Val::ValRecord(
                    AverStr::from("Header"),
                    crate::aver_rt::AverList::from_vec(vec![
                        (AverStr::from("name"), Val::ValStr(header.name)),
                        (AverStr::from("value"), Val::ValStr(header.value)),
                    ]),
                )
            })
            .collect(),
    )
}

fn val_to_http_response(value: Val) -> Result<crate::aver_rt::HttpResponse, AverStr> {
    let Val::ValRecord(type_name, fields) = value else {
        return Err(AverStr::from(
            "HttpServer handler must return HttpResponse record",
        ));
    };

    if &*type_name != "HttpResponse" {
        return Err(AverStr::from(format!(
            "HttpServer handler must return HttpResponse, got {}",
            type_name
        )));
    }

    let mut status = None;
    let mut body = None;
    let mut headers = crate::aver_rt::AverList::empty();

    for (field_name, field_value) in fields.to_vec() {
        match &*field_name {
            "status" => match field_value {
                Val::ValInt(n) => status = Some(n),
                _ => return Err(AverStr::from("HttpResponse.status must be Int")),
            },
            "body" => match field_value {
                Val::ValStr(s) => body = Some(s),
                _ => return Err(AverStr::from("HttpResponse.body must be String")),
            },
            "headers" => {
                headers = val_to_headers(field_value)?;
            }
            _ => {}
        }
    }

    Ok(crate::aver_rt::HttpResponse {
        status: status.ok_or_else(|| AverStr::from("HttpResponse.status is required"))?,
        body: body.ok_or_else(|| AverStr::from("HttpResponse.body is required"))?,
        headers,
    })
}

fn val_to_headers(value: Val) -> Result<crate::aver_rt::AverList<crate::aver_rt::Header>, AverStr> {
    let Val::ValList(items) = value else {
        return Err(AverStr::from("HttpResponse.headers must be List<Header>"));
    };

    let mut out = Vec::new();
    for item in items.to_vec() {
        let Val::ValRecord(_, fields) = item else {
            return Err(AverStr::from(
                "HttpResponse.headers entries must be Header records",
            ));
        };

        let mut name = None;
        let mut value = None;
        for (field_name, field_value) in fields.to_vec() {
            match &*field_name {
                "name" => match field_value {
                    Val::ValStr(s) => name = Some(s),
                    _ => return Err(AverStr::from("Header.name must be String")),
                },
                "value" => match field_value {
                    Val::ValStr(s) => value = Some(s),
                    _ => return Err(AverStr::from("Header.value must be String")),
                },
                _ => {}
            }
        }

        out.push(crate::aver_rt::Header {
            name: name.ok_or_else(|| AverStr::from("Header.name is required"))?,
            value: value.ok_or_else(|| AverStr::from("Header.value is required"))?,
        });
    }

    Ok(crate::aver_rt::AverList::from_vec(out))
}

fn http_error_response(message: String) -> crate::aver_rt::HttpResponse {
    crate::aver_rt::HttpResponse {
        status: 500,
        body: AverStr::from(message),
        headers: crate::aver_rt::AverList::empty(),
    }
}