use std::collections::HashMap;
use wasm_encoder::ValType;
use super::WasmGcError;
use super::types::TypeRegistry;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(super) enum EffectName {
ConsolePrint,
ConsoleError,
ConsoleWarn,
TimeUnixMs,
RequestMethod,
RequestUrl,
RequestQuery,
RequestBody,
RequestHeadersLoad,
ResponseText,
ResponseSetHeader,
HttpSend,
HttpAddRequestHeader,
HttpClearRequestHeaders,
EnvGet,
EnvSet,
ConsoleReadLine,
ArgsLen,
ArgsGet,
RandomFloat,
RandomInt,
TimeSleep,
TimeNow,
FloatSin,
FloatCos,
FloatAtan2,
FloatPow,
TerminalEnableRawMode,
TerminalDisableRawMode,
TerminalClear,
TerminalMoveTo,
TerminalPrint,
TerminalSetColor,
TerminalResetColor,
TerminalReadKey,
TerminalSize,
TerminalHideCursor,
TerminalShowCursor,
TerminalFlush,
DiskReadText,
DiskWriteText,
DiskAppendText,
DiskExists,
DiskDelete,
DiskDeleteDir,
DiskListDir,
DiskMakeDir,
TcpConnect,
TcpWriteLine,
TcpReadLine,
TcpClose,
TcpSend,
TcpPing,
HttpGet,
HttpHead,
HttpDelete,
HttpPost,
HttpPut,
HttpPatch,
}
impl EffectName {
pub(super) fn from_dotted(s: &str) -> Option<Self> {
match s {
"Console.print" => Some(Self::ConsolePrint),
"Console.error" => Some(Self::ConsoleError),
"Console.warn" => Some(Self::ConsoleWarn),
"Time.unixMs" => Some(Self::TimeUnixMs),
"Request.method" => Some(Self::RequestMethod),
"Request.url" | "Request.path" => Some(Self::RequestUrl),
"Request.query" => Some(Self::RequestQuery),
"Request.body" => Some(Self::RequestBody),
"Request.headersLoad" | "Request.headers" => Some(Self::RequestHeadersLoad),
"Response.text" => Some(Self::ResponseText),
"Response.setHeader" => Some(Self::ResponseSetHeader),
"Http.send" => Some(Self::HttpSend),
"Http.addRequestHeader" => Some(Self::HttpAddRequestHeader),
"Http.clearRequestHeaders" => Some(Self::HttpClearRequestHeaders),
"Env.get" => Some(Self::EnvGet),
"Env.set" => Some(Self::EnvSet),
"Console.readLine" => Some(Self::ConsoleReadLine),
"Args._len" | "Args.len" => Some(Self::ArgsLen),
"Args._get" => Some(Self::ArgsGet),
"Random.float" => Some(Self::RandomFloat),
"Random.int" => Some(Self::RandomInt),
"Time.sleep" => Some(Self::TimeSleep),
"Time.now" => Some(Self::TimeNow),
"Float.sin" => Some(Self::FloatSin),
"Float.cos" => Some(Self::FloatCos),
"Float.atan2" => Some(Self::FloatAtan2),
"Float.pow" => Some(Self::FloatPow),
"Terminal.enableRawMode" => Some(Self::TerminalEnableRawMode),
"Terminal.disableRawMode" => Some(Self::TerminalDisableRawMode),
"Terminal.clear" => Some(Self::TerminalClear),
"Terminal.moveTo" => Some(Self::TerminalMoveTo),
"Terminal.print" => Some(Self::TerminalPrint),
"Terminal.setColor" => Some(Self::TerminalSetColor),
"Terminal.resetColor" => Some(Self::TerminalResetColor),
"Terminal.readKey" => Some(Self::TerminalReadKey),
"Terminal.size" => Some(Self::TerminalSize),
"Terminal.hideCursor" => Some(Self::TerminalHideCursor),
"Terminal.showCursor" => Some(Self::TerminalShowCursor),
"Terminal.flush" => Some(Self::TerminalFlush),
"Disk.readText" => Some(Self::DiskReadText),
"Disk.writeText" => Some(Self::DiskWriteText),
"Disk.appendText" => Some(Self::DiskAppendText),
"Disk.exists" => Some(Self::DiskExists),
"Disk.delete" => Some(Self::DiskDelete),
"Disk.deleteDir" => Some(Self::DiskDeleteDir),
"Disk.listDir" => Some(Self::DiskListDir),
"Disk.makeDir" => Some(Self::DiskMakeDir),
"Tcp.connect" => Some(Self::TcpConnect),
"Tcp.writeLine" => Some(Self::TcpWriteLine),
"Tcp.readLine" => Some(Self::TcpReadLine),
"Tcp.close" => Some(Self::TcpClose),
"Tcp.send" => Some(Self::TcpSend),
"Tcp.ping" => Some(Self::TcpPing),
"Http.get" => Some(Self::HttpGet),
"Http.head" => Some(Self::HttpHead),
"Http.delete" => Some(Self::HttpDelete),
"Http.post" => Some(Self::HttpPost),
"Http.put" => Some(Self::HttpPut),
"Http.patch" => Some(Self::HttpPatch),
_ => None,
}
}
pub(super) fn canonical(self) -> &'static str {
match self {
Self::ConsolePrint => "Console.print",
Self::ConsoleError => "Console.error",
Self::ConsoleWarn => "Console.warn",
Self::TimeUnixMs => "Time.unixMs",
Self::RequestMethod => "Request.method",
Self::RequestUrl => "Request.url",
Self::RequestQuery => "Request.query",
Self::RequestBody => "Request.body",
Self::RequestHeadersLoad => "Request.headersLoad",
Self::ResponseText => "Response.text",
Self::ResponseSetHeader => "Response.setHeader",
Self::HttpSend => "Http.send",
Self::HttpAddRequestHeader => "Http.addRequestHeader",
Self::HttpClearRequestHeaders => "Http.clearRequestHeaders",
Self::EnvGet => "Env.get",
Self::EnvSet => "Env.set",
Self::ConsoleReadLine => "Console.readLine",
Self::ArgsLen => "Args.len",
Self::ArgsGet => "Args.get",
Self::RandomFloat => "Random.float",
Self::RandomInt => "Random.int",
Self::TimeSleep => "Time.sleep",
Self::TimeNow => "Time.now",
Self::FloatSin => "Float.sin",
Self::FloatCos => "Float.cos",
Self::FloatAtan2 => "Float.atan2",
Self::FloatPow => "Float.pow",
Self::TerminalEnableRawMode => "Terminal.enableRawMode",
Self::TerminalDisableRawMode => "Terminal.disableRawMode",
Self::TerminalClear => "Terminal.clear",
Self::TerminalMoveTo => "Terminal.moveTo",
Self::TerminalPrint => "Terminal.print",
Self::TerminalSetColor => "Terminal.setColor",
Self::TerminalResetColor => "Terminal.resetColor",
Self::TerminalReadKey => "Terminal.readKey",
Self::TerminalSize => "Terminal.size",
Self::TerminalHideCursor => "Terminal.hideCursor",
Self::TerminalShowCursor => "Terminal.showCursor",
Self::TerminalFlush => "Terminal.flush",
Self::DiskReadText => "Disk.readText",
Self::DiskWriteText => "Disk.writeText",
Self::DiskAppendText => "Disk.appendText",
Self::DiskExists => "Disk.exists",
Self::DiskDelete => "Disk.delete",
Self::DiskDeleteDir => "Disk.deleteDir",
Self::DiskListDir => "Disk.listDir",
Self::DiskMakeDir => "Disk.makeDir",
Self::TcpConnect => "Tcp.connect",
Self::TcpWriteLine => "Tcp.writeLine",
Self::TcpReadLine => "Tcp.readLine",
Self::TcpClose => "Tcp.close",
Self::TcpSend => "Tcp.send",
Self::TcpPing => "Tcp.ping",
Self::HttpGet => "Http.get",
Self::HttpHead => "Http.head",
Self::HttpDelete => "Http.delete",
Self::HttpPost => "Http.post",
Self::HttpPut => "Http.put",
Self::HttpPatch => "Http.patch",
}
}
pub(super) fn import_pair(self) -> (&'static str, &'static str) {
match self {
Self::ConsolePrint => ("aver", "console_print"),
Self::ConsoleError => ("aver", "console_error"),
Self::ConsoleWarn => ("aver", "console_warn"),
Self::TimeUnixMs => ("aver", "time_unix_ms"),
Self::RequestMethod => ("aver", "request_method"),
Self::RequestUrl => ("aver", "request_url"),
Self::RequestQuery => ("aver", "request_query"),
Self::RequestBody => ("aver", "request_body"),
Self::RequestHeadersLoad => ("aver", "request_headers_load"),
Self::ResponseText => ("aver", "response_text"),
Self::ResponseSetHeader => ("aver", "response_set_header"),
Self::HttpSend => ("aver", "http_send"),
Self::HttpAddRequestHeader => ("aver", "http_add_request_header"),
Self::HttpClearRequestHeaders => ("aver", "http_clear_request_headers"),
Self::EnvGet => ("aver", "env_get"),
Self::EnvSet => ("aver", "env_set"),
Self::ConsoleReadLine => ("aver", "console_read_line"),
Self::ArgsLen => ("aver", "args_len"),
Self::ArgsGet => ("aver", "args_get"),
Self::RandomFloat => ("aver", "random_float"),
Self::RandomInt => ("aver", "random_int"),
Self::TimeSleep => ("aver", "time_sleep"),
Self::TimeNow => ("aver", "time_now"),
Self::FloatSin => ("aver", "float_sin"),
Self::FloatCos => ("aver", "float_cos"),
Self::FloatAtan2 => ("aver", "float_atan2"),
Self::FloatPow => ("aver", "float_pow"),
Self::TerminalEnableRawMode => ("aver", "terminal_enable_raw_mode"),
Self::TerminalDisableRawMode => ("aver", "terminal_disable_raw_mode"),
Self::TerminalClear => ("aver", "terminal_clear"),
Self::TerminalMoveTo => ("aver", "terminal_move_to"),
Self::TerminalPrint => ("aver", "terminal_print"),
Self::TerminalSetColor => ("aver", "terminal_set_color"),
Self::TerminalResetColor => ("aver", "terminal_reset_color"),
Self::TerminalReadKey => ("aver", "terminal_read_key"),
Self::TerminalSize => ("aver", "terminal_size"),
Self::TerminalHideCursor => ("aver", "terminal_hide_cursor"),
Self::TerminalShowCursor => ("aver", "terminal_show_cursor"),
Self::TerminalFlush => ("aver", "terminal_flush"),
Self::DiskReadText => ("aver", "disk_read_text"),
Self::DiskWriteText => ("aver", "disk_write_text"),
Self::DiskAppendText => ("aver", "disk_append_text"),
Self::DiskExists => ("aver", "disk_exists"),
Self::DiskDelete => ("aver", "disk_delete"),
Self::DiskDeleteDir => ("aver", "disk_delete_dir"),
Self::DiskListDir => ("aver", "disk_list_dir"),
Self::DiskMakeDir => ("aver", "disk_make_dir"),
Self::TcpConnect => ("aver", "tcp_connect"),
Self::TcpWriteLine => ("aver", "tcp_write_line"),
Self::TcpReadLine => ("aver", "tcp_read_line"),
Self::TcpClose => ("aver", "tcp_close"),
Self::TcpSend => ("aver", "tcp_send"),
Self::TcpPing => ("aver", "tcp_ping"),
Self::HttpGet => ("aver", "http_get"),
Self::HttpHead => ("aver", "http_head"),
Self::HttpDelete => ("aver", "http_delete"),
Self::HttpPost => ("aver", "http_post"),
Self::HttpPut => ("aver", "http_put"),
Self::HttpPatch => ("aver", "http_patch"),
}
}
pub(super) fn params(self, registry: &TypeRegistry) -> Result<Vec<ValType>, WasmGcError> {
match self {
Self::ConsolePrint | Self::ConsoleError | Self::ConsoleWarn => Ok(vec![any_ref_ty()]),
Self::TimeUnixMs => Ok(vec![]),
Self::RequestMethod
| Self::RequestUrl
| Self::RequestQuery
| Self::RequestBody
| Self::RequestHeadersLoad
| Self::HttpClearRequestHeaders => Ok(vec![]),
Self::ResponseText => Ok(vec![ValType::I64, any_ref_ty()]),
Self::ResponseSetHeader => Ok(vec![any_ref_ty(), any_ref_ty()]),
Self::HttpSend => Ok(vec![any_ref_ty(), any_ref_ty(), any_ref_ty(), any_ref_ty()]),
Self::HttpAddRequestHeader => Ok(vec![any_ref_ty(), any_ref_ty()]),
Self::EnvGet => Ok(vec![any_ref_ty()]),
Self::EnvSet => Ok(vec![any_ref_ty(), any_ref_ty()]),
Self::ConsoleReadLine | Self::ArgsLen | Self::RandomFloat | Self::TimeNow => Ok(vec![]),
Self::ArgsGet | Self::TimeSleep => Ok(vec![ValType::I64]),
Self::RandomInt => Ok(vec![ValType::I64, ValType::I64]),
Self::FloatSin | Self::FloatCos => Ok(vec![ValType::F64]),
Self::FloatAtan2 | Self::FloatPow => Ok(vec![ValType::F64, ValType::F64]),
Self::TerminalEnableRawMode
| Self::TerminalDisableRawMode
| Self::TerminalClear
| Self::TerminalResetColor
| Self::TerminalReadKey
| Self::TerminalSize
| Self::TerminalHideCursor
| Self::TerminalShowCursor
| Self::TerminalFlush => Ok(vec![]),
Self::TerminalMoveTo => Ok(vec![ValType::I64, ValType::I64]),
Self::TerminalPrint => Ok(vec![any_ref_ty()]),
Self::TerminalSetColor => Ok(vec![any_ref_ty()]),
Self::DiskReadText
| Self::DiskExists
| Self::DiskDelete
| Self::DiskDeleteDir
| Self::DiskListDir
| Self::DiskMakeDir => Ok(vec![any_ref_ty()]),
Self::DiskWriteText | Self::DiskAppendText => Ok(vec![any_ref_ty(), any_ref_ty()]),
Self::TcpConnect => Ok(vec![any_ref_ty(), ValType::I64]),
Self::TcpClose | Self::TcpReadLine => Ok(vec![any_ref_ty()]),
Self::TcpWriteLine => Ok(vec![any_ref_ty(), any_ref_ty()]),
Self::TcpSend => Ok(vec![any_ref_ty(), ValType::I64, any_ref_ty()]),
Self::TcpPing => Ok(vec![any_ref_ty(), ValType::I64]),
Self::HttpGet | Self::HttpHead | Self::HttpDelete => Ok(vec![any_ref_ty()]),
Self::HttpPost | Self::HttpPut | Self::HttpPatch => Ok(vec![
any_ref_ty(),
any_ref_ty(),
any_ref_ty(),
map_string_list_string_ref_ty(registry)?,
]),
}
}
pub(super) fn results(self, registry: &TypeRegistry) -> Result<Vec<ValType>, WasmGcError> {
match self {
Self::ConsolePrint | Self::ConsoleError | Self::ConsoleWarn => Ok(vec![]),
Self::TimeUnixMs => Ok(vec![ValType::I64]),
Self::RequestMethod
| Self::RequestUrl
| Self::RequestQuery
| Self::RequestBody
| Self::EnvGet => Ok(vec![string_ref_ty(registry)?]),
Self::RequestHeadersLoad => Ok(vec![map_string_list_string_ref_ty(registry)?]),
Self::ResponseText
| Self::ResponseSetHeader
| Self::HttpAddRequestHeader
| Self::HttpClearRequestHeaders
| Self::EnvSet => Ok(vec![]),
Self::HttpSend => Ok(vec![
ValType::I64,
string_ref_ty(registry)?,
map_string_list_string_ref_ty(registry)?,
string_ref_ty(registry)?,
]),
Self::ConsoleReadLine => {
let idx = registry.result_type_idx("Result<String,String>").ok_or(
WasmGcError::Validation(
"Console.readLine: Result<String,String> slot not registered".into(),
),
)?;
Ok(vec![ValType::Ref(wasm_encoder::RefType {
nullable: true,
heap_type: wasm_encoder::HeapType::Concrete(idx),
})])
}
Self::ArgsGet | Self::TimeNow => Ok(vec![string_ref_ty(registry)?]),
Self::ArgsLen | Self::RandomInt => Ok(vec![ValType::I64]),
Self::TimeSleep => Ok(vec![]),
Self::RandomFloat
| Self::FloatSin
| Self::FloatCos
| Self::FloatAtan2
| Self::FloatPow => Ok(vec![ValType::F64]),
Self::TerminalEnableRawMode
| Self::TerminalDisableRawMode
| Self::TerminalClear
| Self::TerminalMoveTo
| Self::TerminalPrint
| Self::TerminalSetColor
| Self::TerminalResetColor
| Self::TerminalHideCursor
| Self::TerminalShowCursor
| Self::TerminalFlush => Ok(vec![]),
Self::TerminalReadKey => {
let opt_idx =
registry
.option_type_idx("Option<String>")
.ok_or(WasmGcError::Validation(
"Terminal.readKey: Option<String> slot not registered".into(),
))?;
Ok(vec![ValType::Ref(wasm_encoder::RefType {
nullable: true,
heap_type: wasm_encoder::HeapType::Concrete(opt_idx),
})])
}
Self::TerminalSize => {
let idx =
registry
.record_type_idx("Terminal.Size")
.ok_or(WasmGcError::Validation(
"Terminal.size: Terminal.Size record slot not registered \
(did you call Terminal.size in user code?)"
.into(),
))?;
Ok(vec![ValType::Ref(wasm_encoder::RefType {
nullable: true,
heap_type: wasm_encoder::HeapType::Concrete(idx),
})])
}
Self::DiskReadText => Ok(vec![result_ref_ty(registry, "Result<String,String>")?]),
Self::DiskWriteText
| Self::DiskAppendText
| Self::DiskDelete
| Self::DiskDeleteDir
| Self::DiskMakeDir => Ok(vec![result_ref_ty(registry, "Result<Unit,String>")?]),
Self::DiskListDir => Ok(vec![result_ref_ty(
registry,
"Result<List<String>,String>",
)?]),
Self::DiskExists => Ok(vec![ValType::I32]),
Self::TcpConnect => Ok(vec![result_ref_ty(
registry,
"Result<Tcp.Connection,String>",
)?]),
Self::TcpReadLine | Self::TcpSend => {
Ok(vec![result_ref_ty(registry, "Result<String,String>")?])
}
Self::TcpWriteLine | Self::TcpClose | Self::TcpPing => {
Ok(vec![result_ref_ty(registry, "Result<Unit,String>")?])
}
Self::HttpGet
| Self::HttpHead
| Self::HttpDelete
| Self::HttpPost
| Self::HttpPut
| Self::HttpPatch => Ok(vec![result_ref_ty(
registry,
"Result<HttpResponse,String>",
)?]),
}
}
}
fn result_ref_ty(registry: &TypeRegistry, canonical: &str) -> Result<ValType, WasmGcError> {
let idx = registry
.result_type_idx(canonical)
.ok_or(WasmGcError::Validation(format!(
"effect requires `{canonical}` slot but none was registered"
)))?;
Ok(ValType::Ref(wasm_encoder::RefType {
nullable: true,
heap_type: wasm_encoder::HeapType::Concrete(idx),
}))
}
fn map_string_list_string_ref_ty(registry: &TypeRegistry) -> Result<ValType, WasmGcError> {
let slots = registry
.map_slots("Map<String,List<String>>")
.ok_or(WasmGcError::Validation(
"fetch effect requires `Map<String, List<String>>` slot but none was registered".into(),
))?;
Ok(ValType::Ref(wasm_encoder::RefType {
nullable: true,
heap_type: wasm_encoder::HeapType::Concrete(slots.map),
}))
}
fn any_ref_ty() -> ValType {
ValType::Ref(wasm_encoder::RefType {
nullable: true,
heap_type: wasm_encoder::HeapType::Abstract {
shared: false,
ty: wasm_encoder::AbstractHeapType::Any,
},
})
}
#[derive(Default)]
pub(super) struct EffectRegistry {
order: Vec<EffectName>,
wasm_fn_idx: HashMap<EffectName, u32>,
wasm_type_idx: HashMap<EffectName, u32>,
}
impl EffectRegistry {
pub(super) fn new() -> Self {
Self::default()
}
pub(super) fn register(&mut self, name: EffectName) {
if !self.order.contains(&name) {
self.order.push(name);
}
}
pub(super) fn iter(&self) -> impl Iterator<Item = EffectName> + '_ {
self.order.iter().copied()
}
pub(super) fn import_count(&self) -> u32 {
self.order.len() as u32
}
pub(super) fn assign_slots(&mut self, next_type_idx: &mut u32) {
for (i, name) in self.order.iter().copied().enumerate() {
self.wasm_fn_idx.insert(name, i as u32);
self.wasm_type_idx.insert(name, *next_type_idx);
*next_type_idx += 1;
}
}
pub(super) fn lookup_wasm_fn_idx(&self, name: EffectName) -> Option<u32> {
self.wasm_fn_idx.get(&name).copied()
}
pub(super) fn lookup_wasm_type_idx(&self, name: EffectName) -> Option<u32> {
self.wasm_type_idx.get(&name).copied()
}
}
fn string_ref_ty(registry: &TypeRegistry) -> Result<ValType, WasmGcError> {
let idx = registry
.string_array_type_idx
.ok_or(WasmGcError::Validation(
"effect requires String repr but no string type slot was allocated".into(),
))?;
Ok(ValType::Ref(wasm_encoder::RefType {
nullable: true,
heap_type: wasm_encoder::HeapType::Concrete(idx),
}))
}