use crate::nan_value::{Arena, NanValue};
#[cfg(feature = "terminal")]
use crate::services::terminal;
use crate::services::{console, disk, env, http, random, tcp, time};
use crate::types::{bool, byte, char, float, int, list, map, option, result, string};
use crate::value::RuntimeError;
macro_rules! vm_builtins {
($($variant:ident => $name:literal,)+) => {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u16)]
pub(crate) enum VmBuiltin {
$($variant,)+
}
impl VmBuiltin {
pub(crate) const fn name(self) -> &'static str {
match self {
$(Self::$variant => $name,)+
}
}
pub(crate) const ALL: &'static [Self] = &[
$(Self::$variant,)+
];
}
};
}
vm_builtins! {
ArgsGet => "Args.get",
ConsolePrint => "Console.print",
ConsoleError => "Console.error",
ConsoleWarn => "Console.warn",
ConsoleReadLine => "Console.readLine",
HttpGet => "Http.get",
HttpHead => "Http.head",
HttpDelete => "Http.delete",
HttpPost => "Http.post",
HttpPut => "Http.put",
HttpPatch => "Http.patch",
HttpServerListen => "HttpServer.listen",
HttpServerListenWith => "HttpServer.listenWith",
SelfHostRuntimeHttpServerListen => "SelfHostRuntime.httpServerListen",
SelfHostRuntimeHttpServerListenWith => "SelfHostRuntime.httpServerListenWith",
DiskReadText => "Disk.readText",
DiskWriteText => "Disk.writeText",
DiskAppendText => "Disk.appendText",
DiskExists => "Disk.exists",
DiskDelete => "Disk.delete",
DiskDeleteDir => "Disk.deleteDir",
DiskListDir => "Disk.listDir",
DiskMakeDir => "Disk.makeDir",
EnvGet => "Env.get",
EnvSet => "Env.set",
RandomInt => "Random.int",
RandomFloat => "Random.float",
TcpSend => "Tcp.send",
TcpPing => "Tcp.ping",
TcpConnect => "Tcp.connect",
TcpWriteLine => "Tcp.writeLine",
TcpReadLine => "Tcp.readLine",
TcpClose => "Tcp.close",
TerminalEnableRawMode => "Terminal.enableRawMode",
TerminalDisableRawMode => "Terminal.disableRawMode",
TerminalClear => "Terminal.clear",
TerminalMoveTo => "Terminal.moveTo",
TerminalPrint => "Terminal.print",
TerminalSetColor => "Terminal.setColor",
TerminalResetColor => "Terminal.resetColor",
TerminalReadKey => "Terminal.readKey",
TerminalSize => "Terminal.size",
TerminalHideCursor => "Terminal.hideCursor",
TerminalShowCursor => "Terminal.showCursor",
TerminalFlush => "Terminal.flush",
TimeNow => "Time.now",
TimeUnixMs => "Time.unixMs",
TimeSleep => "Time.sleep",
BoolOr => "Bool.or",
BoolAnd => "Bool.and",
BoolNot => "Bool.not",
IntFromString => "Int.fromString",
IntFromFloat => "Int.fromFloat",
IntToString => "Int.toString",
IntAbs => "Int.abs",
IntMin => "Int.min",
IntMax => "Int.max",
IntMod => "Int.mod",
IntToFloat => "Int.toFloat",
FloatFromString => "Float.fromString",
FloatFromInt => "Float.fromInt",
FloatToString => "Float.toString",
FloatAbs => "Float.abs",
FloatFloor => "Float.floor",
FloatCeil => "Float.ceil",
FloatRound => "Float.round",
FloatMin => "Float.min",
FloatMax => "Float.max",
FloatSin => "Float.sin",
FloatCos => "Float.cos",
FloatSqrt => "Float.sqrt",
FloatPow => "Float.pow",
FloatAtan2 => "Float.atan2",
FloatPi => "Float.pi",
StringLen => "String.len",
StringByteLength => "String.byteLength",
StringStartsWith => "String.startsWith",
StringEndsWith => "String.endsWith",
StringContains => "String.contains",
StringSlice => "String.slice",
StringTrim => "String.trim",
StringSplit => "String.split",
StringReplace => "String.replace",
StringJoin => "String.join",
StringCharAt => "String.charAt",
StringChars => "String.chars",
StringFromInt => "String.fromInt",
StringFromFloat => "String.fromFloat",
StringFromBool => "String.fromBool",
StringToLower => "String.toLower",
StringToUpper => "String.toUpper",
ListLen => "List.len",
ListPrepend => "List.prepend",
ListTake => "List.take",
ListDrop => "List.drop",
ListConcat => "List.concat",
ListReverse => "List.reverse",
ListContains => "List.contains",
ListZip => "List.zip",
MapEmpty => "Map.empty",
MapSet => "Map.set",
MapGet => "Map.get",
MapRemove => "Map.remove",
MapHas => "Map.has",
MapKeys => "Map.keys",
MapValues => "Map.values",
MapEntries => "Map.entries",
MapLen => "Map.len",
MapFromList => "Map.fromList",
VectorNew => "Vector.new",
VectorGet => "Vector.get",
VectorSet => "Vector.set",
VectorLen => "Vector.len",
VectorFromList => "Vector.fromList",
VectorToList => "Vector.toList",
OptionWithDefault => "Option.withDefault",
OptionToResult => "Option.toResult",
ResultWithDefault => "Result.withDefault",
CharToCode => "Char.toCode",
CharFromCode => "Char.fromCode",
ByteToHex => "Byte.toHex",
ByteFromHex => "Byte.fromHex",
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) enum VmBuiltinParentThinClass {
Heapless,
Cheap,
AllocHeavy,
}
impl VmBuiltin {
pub(crate) const fn is_http_server(self) -> bool {
matches!(
self,
Self::HttpServerListen
| Self::HttpServerListenWith
| Self::SelfHostRuntimeHttpServerListen
| Self::SelfHostRuntimeHttpServerListenWith
)
}
pub(crate) const fn parent_thin_class(self) -> VmBuiltinParentThinClass {
match self {
Self::BoolOr
| Self::BoolAnd
| Self::BoolNot
| Self::IntFromFloat
| Self::IntAbs
| Self::IntMin
| Self::IntMax
| Self::IntToFloat
| Self::FloatFromInt
| Self::FloatAbs
| Self::FloatFloor
| Self::FloatCeil
| Self::FloatRound
| Self::FloatMin
| Self::FloatMax
| Self::FloatSin
| Self::FloatCos
| Self::FloatSqrt
| Self::FloatPow
| Self::FloatAtan2
| Self::FloatPi
| Self::StringLen
| Self::StringByteLength
| Self::StringStartsWith
| Self::StringEndsWith
| Self::StringContains
| Self::ListContains
| Self::MapEmpty
| Self::MapLen
| Self::MapHas
| Self::VectorLen
| Self::OptionWithDefault
| Self::ResultWithDefault
| Self::CharToCode => VmBuiltinParentThinClass::Heapless,
Self::MapGet | Self::VectorGet => VmBuiltinParentThinClass::Cheap,
_ => VmBuiltinParentThinClass::AllocHeavy,
}
}
pub(crate) fn effects(self) -> &'static [&'static str] {
match self {
Self::ConsolePrint | Self::ConsoleError | Self::ConsoleWarn | Self::ConsoleReadLine => {
console::effects(self.name())
}
Self::HttpGet
| Self::HttpHead
| Self::HttpDelete
| Self::HttpPost
| Self::HttpPut
| Self::HttpPatch => http::effects(self.name()),
Self::HttpServerListen
| Self::HttpServerListenWith
| Self::SelfHostRuntimeHttpServerListen
| Self::SelfHostRuntimeHttpServerListenWith => {
crate::services::http_server::effects(self.name())
}
Self::DiskReadText
| Self::DiskWriteText
| Self::DiskAppendText
| Self::DiskExists
| Self::DiskDelete
| Self::DiskDeleteDir
| Self::DiskListDir
| Self::DiskMakeDir => disk::effects(self.name()),
Self::EnvGet | Self::EnvSet => env::effects(self.name()),
Self::RandomInt | Self::RandomFloat => random::effects(self.name()),
Self::TcpSend
| Self::TcpPing
| Self::TcpConnect
| Self::TcpWriteLine
| Self::TcpReadLine
| Self::TcpClose => tcp::effects(self.name()),
#[cfg(feature = "terminal")]
Self::TerminalEnableRawMode
| Self::TerminalDisableRawMode
| Self::TerminalClear
| Self::TerminalMoveTo
| Self::TerminalPrint
| Self::TerminalSetColor
| Self::TerminalResetColor
| Self::TerminalReadKey
| Self::TerminalSize
| Self::TerminalHideCursor
| Self::TerminalShowCursor
| Self::TerminalFlush => terminal::effects(self.name()),
Self::TimeNow | Self::TimeUnixMs | Self::TimeSleep => time::effects(self.name()),
_ => &[],
}
}
pub(crate) fn invoke_nv(
self,
args: &[NanValue],
arena: &mut Arena,
cli_args: &[String],
silent_console: bool,
) -> Result<NanValue, RuntimeError> {
if silent_console
&& matches!(
self,
Self::ConsolePrint | Self::ConsoleError | Self::ConsoleWarn
)
{
return Ok(NanValue::UNIT);
}
let result = match self {
Self::ArgsGet => crate::services::args::call_nv(self.name(), args, cli_args, arena),
Self::ConsolePrint | Self::ConsoleError | Self::ConsoleWarn | Self::ConsoleReadLine => {
console::call_nv(self.name(), args, arena)
}
Self::HttpGet
| Self::HttpHead
| Self::HttpDelete
| Self::HttpPost
| Self::HttpPut
| Self::HttpPatch => http::call_nv(self.name(), args, arena),
Self::DiskReadText
| Self::DiskWriteText
| Self::DiskAppendText
| Self::DiskExists
| Self::DiskDelete
| Self::DiskDeleteDir
| Self::DiskListDir
| Self::DiskMakeDir => disk::call_nv(self.name(), args, arena),
Self::EnvGet | Self::EnvSet => env::call_nv(self.name(), args, arena),
Self::RandomInt | Self::RandomFloat => random::call_nv(self.name(), args, arena),
Self::TcpSend
| Self::TcpPing
| Self::TcpConnect
| Self::TcpWriteLine
| Self::TcpReadLine
| Self::TcpClose => tcp::call_nv(self.name(), args, arena),
#[cfg(feature = "terminal")]
Self::TerminalEnableRawMode
| Self::TerminalDisableRawMode
| Self::TerminalClear
| Self::TerminalMoveTo
| Self::TerminalPrint
| Self::TerminalSetColor
| Self::TerminalResetColor
| Self::TerminalReadKey
| Self::TerminalSize
| Self::TerminalHideCursor
| Self::TerminalShowCursor
| Self::TerminalFlush => terminal::call_nv(self.name(), args, arena),
Self::TimeNow | Self::TimeUnixMs | Self::TimeSleep => {
time::call_nv(self.name(), args, arena)
}
Self::BoolOr | Self::BoolAnd | Self::BoolNot => bool::call_nv(self.name(), args, arena),
Self::IntFromString
| Self::IntFromFloat
| Self::IntToString
| Self::IntAbs
| Self::IntMin
| Self::IntMax
| Self::IntMod
| Self::IntToFloat => int::call_nv(self.name(), args, arena),
Self::FloatFromString
| Self::FloatFromInt
| Self::FloatToString
| Self::FloatAbs
| Self::FloatFloor
| Self::FloatCeil
| Self::FloatRound
| Self::FloatMin
| Self::FloatMax
| Self::FloatSin
| Self::FloatCos
| Self::FloatSqrt
| Self::FloatPow
| Self::FloatAtan2
| Self::FloatPi => float::call_nv(self.name(), args, arena),
Self::StringLen
| Self::StringByteLength
| Self::StringStartsWith
| Self::StringEndsWith
| Self::StringContains
| Self::StringSlice
| Self::StringTrim
| Self::StringSplit
| Self::StringReplace
| Self::StringJoin
| Self::StringCharAt
| Self::StringChars
| Self::StringFromInt
| Self::StringFromFloat
| Self::StringFromBool
| Self::StringToLower
| Self::StringToUpper => string::call_nv(self.name(), args, arena),
Self::ListLen
| Self::ListPrepend
| Self::ListTake
| Self::ListDrop
| Self::ListConcat
| Self::ListReverse
| Self::ListContains
| Self::ListZip => list::call_nv(self.name(), args, arena),
Self::MapEmpty
| Self::MapSet
| Self::MapGet
| Self::MapRemove
| Self::MapHas
| Self::MapKeys
| Self::MapValues
| Self::MapEntries
| Self::MapLen
| Self::MapFromList => map::call_nv(self.name(), args, arena),
Self::VectorNew
| Self::VectorGet
| Self::VectorSet
| Self::VectorLen
| Self::VectorFromList
| Self::VectorToList => crate::types::vector::call_nv(self.name(), args, arena),
Self::OptionWithDefault | Self::OptionToResult => {
option::call_nv(self.name(), args, arena)
}
Self::ResultWithDefault => result::call_nv(self.name(), args, arena),
Self::CharToCode | Self::CharFromCode => char::call_nv(self.name(), args, arena),
Self::ByteToHex | Self::ByteFromHex => byte::call_nv(self.name(), args, arena),
Self::HttpServerListen
| Self::HttpServerListenWith
| Self::SelfHostRuntimeHttpServerListen
| Self::SelfHostRuntimeHttpServerListenWith => None,
};
result.expect("VmBuiltin list and call_nv dispatch are out of sync")
}
}
#[cfg(test)]
mod tests {
use super::VmBuiltin;
#[test]
fn builtin_names_are_unique() {
let mut seen = std::collections::HashSet::new();
for builtin in [
VmBuiltin::ConsolePrint,
VmBuiltin::HttpServerListenWith,
VmBuiltin::StringReplace,
VmBuiltin::OptionToResult,
VmBuiltin::MapFromList,
] {
assert!(
seen.insert(builtin.name()),
"duplicate builtin name {}",
builtin.name()
);
}
}
}