use super::emit_ctx::EmitCtx;
use super::expr::{clone_arg, emit_expr};
use crate::ast::Expr;
use crate::codegen::CodegenContext;
fn builtin_needs_str_conversion(name: &str) -> bool {
matches!(
name,
"Console.readLine"
| "Time.now"
| "Int.fromString"
| "Float.fromString"
| "String.slice"
| "String.charAt"
| "String.toLower"
| "String.toUpper"
| "String.trim"
| "String.trimStart"
| "String.trimEnd"
| "String.split"
| "String.replace"
| "String.replaceFirst"
| "String.join"
| "String.repeat"
| "String.reverse"
| "String.fromInt"
| "String.fromFloat"
| "String.fromBool"
| "String.chars"
| "Char.fromCode"
| "Byte.toHex"
| "Byte.fromHex"
| "Disk.readText"
| "Disk.writeText"
| "Disk.appendText"
| "Disk.delete"
| "Disk.deleteDir"
| "Disk.listDir"
| "Disk.makeDir"
| "Env.get"
| "Terminal.readKey"
| "Http.get"
| "Http.head"
| "Http.delete"
| "Http.post"
| "Http.put"
| "Http.patch"
| "Tcp.send"
| "Tcp.ping"
| "Tcp.connect"
| "Tcp.writeLine"
| "Tcp.readLine"
| "Tcp.close"
| "Int.mod"
)
}
fn builtin_effect_name(name: &str) -> &str {
match name {
"SelfHostRuntime.httpServerListen" => "HttpServer.listen",
"SelfHostRuntime.httpServerListenWith" => "HttpServer.listenWith",
_ => name,
}
}
fn builtin_is_effectful(name: &str) -> bool {
matches!(
builtin_effect_name(name).split('.').next(),
Some(
"Args"
| "Console"
| "Http"
| "HttpServer"
| "Disk"
| "Env"
| "Random"
| "SelfHostRuntime"
| "Tcp"
| "Terminal"
| "Time"
)
)
}
fn emit_effectful_builtin_call_with_temps(name: &str, args: &[String]) -> Option<String> {
match name {
"Console.print" => Some(format!("aver_rt::console_print(&{})", args[0])),
"Console.error" => Some(format!("aver_rt::console_error(&{})", args[0])),
"Console.warn" => Some(format!("aver_rt::console_warn(&{})", args[0])),
"Console.readLine" => Some("aver_rt::read_line()".to_string()),
"Http.get" => Some(format!("aver_rt::http::get(&{})", args[0])),
"Http.head" => Some(format!("aver_rt::http::head(&{})", args[0])),
"Http.delete" => Some(format!("aver_rt::http::delete(&{})", args[0])),
"Http.post" => Some(format!(
"aver_rt::http::post(&{}, &{}, &{}, &{})",
args[0], args[1], args[2], args[3]
)),
"Http.put" => Some(format!(
"aver_rt::http::put(&{}, &{}, &{}, &{})",
args[0], args[1], args[2], args[3]
)),
"Http.patch" => Some(format!(
"aver_rt::http::patch(&{}, &{}, &{}, &{})",
args[0], args[1], args[2], args[3]
)),
"HttpServer.listen" => Some(format!(
"{{ if let Err(e) = crate::http_server_listen({}, {}) {{ panic!(\"{{}}\", e); }} }}",
args[0], args[1]
)),
"HttpServer.listenWith" => Some(format!(
"{{ if let Err(e) = crate::http_server_listen_with({}, {}.clone(), {}) {{ panic!(\"{{}}\", e); }} }}",
args[0], args[1], args[2]
)),
"SelfHostRuntime.httpServerListen" => Some(format!(
"crate::self_host_support::http_server_listen({}, {})",
args[0], args[1]
)),
"SelfHostRuntime.httpServerListenWith" => Some(format!(
"crate::self_host_support::http_server_listen_with({}, {}.clone(), {})",
args[0], args[1], args[2]
)),
"Disk.readText" => Some(format!("aver_rt::read_text(&{})", args[0])),
"Disk.writeText" => Some(format!("aver_rt::write_text(&{}, &{})", args[0], args[1])),
"Disk.appendText" => Some(format!("aver_rt::append_text(&{}, &{})", args[0], args[1])),
"Disk.exists" => Some(format!("aver_rt::path_exists(&{})", args[0])),
"Disk.delete" => Some(format!("aver_rt::delete_file(&{})", args[0])),
"Disk.deleteDir" => Some(format!("aver_rt::delete_dir(&{})", args[0])),
"Disk.listDir" => Some(format!("aver_rt::list_dir(&{})", args[0])),
"Disk.makeDir" => Some(format!("aver_rt::make_dir(&{})", args[0])),
"Env.get" => Some(format!("aver_rt::env_get(&{})", args[0])),
"Env.set" => Some(format!(
"aver_rt::env_set(&{}, &{}).expect(\"Env.set failed\")",
args[0], args[1]
)),
"Args.get" => Some("aver_replay::current_cli_args()".to_string()),
"Time.now" => Some("aver_rt::time_now()".to_string()),
"Time.unixMs" => Some("aver_rt::time_unix_ms()".to_string()),
"Time.sleep" => Some(format!("aver_rt::time_sleep({})", args[0])),
"Random.int" => Some(format!(
"aver_rt::random::random_int({}, {}).unwrap()",
args[0], args[1]
)),
"Random.float" => Some("aver_rt::random::random_float()".to_string()),
"Tcp.send" => Some(format!(
"aver_rt::tcp::send(&{}, {}, &{})",
args[0], args[1], args[2]
)),
"Tcp.ping" => Some(format!("aver_rt::tcp::ping(&{}, {})", args[0], args[1])),
"Tcp.connect" => Some(format!("aver_rt::tcp::connect(&{}, {})", args[0], args[1])),
"Tcp.writeLine" => Some(format!("aver_rt::tcp::write_line(&{}, &{})", args[0], args[1])),
"Tcp.readLine" => Some(format!("aver_rt::tcp::read_line(&{})", args[0])),
"Tcp.close" => Some(format!("aver_rt::tcp::close(&{})", args[0])),
"Terminal.enableRawMode" => Some("aver_rt::terminal_enable_raw_mode().unwrap()".to_string()),
"Terminal.disableRawMode" => {
Some("aver_rt::terminal_disable_raw_mode().unwrap()".to_string())
}
"Terminal.clear" => Some("aver_rt::terminal_clear().unwrap()".to_string()),
"Terminal.moveTo" => Some(format!(
"aver_rt::terminal_move_to({}, {}).unwrap()",
args[0], args[1]
)),
"Terminal.print" => Some(format!(
"{{ let __s = format!(\"{{}}\", {}); aver_rt::terminal_print(&__s).unwrap() }}",
args[0]
)),
"Terminal.setColor" => {
Some(format!("aver_rt::terminal_set_color(&{}).unwrap()", args[0]))
}
"Terminal.resetColor" => Some("aver_rt::terminal_reset_color().unwrap()".to_string()),
"Terminal.readKey" => Some("aver_rt::terminal_read_key()".to_string()),
"Terminal.size" => Some(
"{ let (w, h) = aver_rt::terminal_size().unwrap(); aver_rt::TerminalSize { width: w, height: h } }".to_string(),
),
"Terminal.hideCursor" => Some("aver_rt::terminal_hide_cursor().unwrap()".to_string()),
"Terminal.showCursor" => Some("aver_rt::terminal_show_cursor().unwrap()".to_string()),
"Terminal.flush" => Some("aver_rt::terminal_flush().unwrap()".to_string()),
_ => None,
}
}
fn emit_replay_effect_call(
name: &str,
args: &[Expr],
ctx: &CodegenContext,
ectx: &EmitCtx,
) -> Option<String> {
let effect_name = builtin_effect_name(name);
let temp_names = (0..args.len())
.map(|idx| format!("__effect_arg{}", idx))
.collect::<Vec<_>>();
let raw = emit_effectful_builtin_call_with_temps(name, &temp_names)?;
let final_result = if builtin_needs_str_conversion(name) {
format!("({}).into_aver()", raw)
} else {
raw
};
let mut lines = Vec::new();
lines.push("{".to_string());
for (idx, arg) in args.iter().enumerate() {
let emitted = clone_arg(arg, ctx, ectx);
lines.push(format!(" let {} = {};", temp_names[idx], emitted));
}
lines.push(" crate::cancel_checkpoint();".to_string());
let json_args = emit_replay_effect_arg_json(effect_name, &temp_names).join(", ");
lines.push(format!(
" aver_replay::invoke_effect({:?}, vec![{}], || {})",
effect_name, json_args, final_result
));
lines.push("}".to_string());
Some(lines.join("\n"))
}
fn emit_replay_effect_arg_json(name: &str, temp_names: &[String]) -> Vec<String> {
match name {
"Console.print" | "Console.error" | "Console.warn" | "Terminal.print" => vec![format!(
"serde_json::Value::String(format!(\"{{}}\", {}))",
temp_names[0]
)],
"HttpServer.listen" => vec![
format!(
"aver_replay::ReplayValue::to_replay_json(&{})",
temp_names[0]
),
"serde_json::Value::String(\"<handler>\".to_string())".to_string(),
],
"HttpServer.listenWith" => vec![
format!(
"aver_replay::ReplayValue::to_replay_json(&{})",
temp_names[0]
),
format!(
"aver_replay::ReplayValue::to_replay_json(&{})",
temp_names[1]
),
"serde_json::Value::String(\"<handler>\".to_string())".to_string(),
],
_ => temp_names
.iter()
.map(|name| format!("aver_replay::ReplayValue::to_replay_json(&{})", name))
.collect(),
}
}
pub fn emit_builtin_call(
name: &str,
args: &[Expr],
ctx: &CodegenContext,
ectx: &EmitCtx,
) -> Option<String> {
if ctx.emit_replay_runtime && builtin_is_effectful(name) {
return emit_replay_effect_call(name, args, ctx, ectx);
}
let result = emit_builtin_call_inner(name, args, ctx, ectx)?;
let result = if builtin_needs_str_conversion(name) {
format!("({}).into_aver()", result)
} else {
result
};
if ctx.policy.is_some() && !ctx.emit_replay_runtime {
if name.starts_with("Http.") && !args.is_empty() {
let url_arg = emit_expr(&args[0], ctx, ectx);
return Some(format!(
"{{ crate::cancel_checkpoint(); aver_policy::check_http(\"{}\", &{}).expect(\"aver.toml policy violation\"); {} }}",
name, url_arg, result
));
}
if name.starts_with("Disk.") && !args.is_empty() {
let path_arg = emit_expr(&args[0], ctx, ectx);
return Some(format!(
"{{ crate::cancel_checkpoint(); aver_policy::check_disk(\"{}\", &{}).expect(\"aver.toml policy violation\"); {} }}",
name, path_arg, result
));
}
if name.starts_with("Env.") && !args.is_empty() {
let key_arg = emit_expr(&args[0], ctx, ectx);
return Some(format!(
"{{ crate::cancel_checkpoint(); aver_policy::check_env(\"{}\", &{}).expect(\"aver.toml policy violation\"); {} }}",
name, key_arg, result
));
}
}
if builtin_is_effectful(name) {
Some(format!("{{ crate::cancel_checkpoint(); {} }}", result))
} else {
Some(result)
}
}
fn emit_builtin_call_inner(
name: &str,
args: &[Expr],
ctx: &CodegenContext,
ectx: &EmitCtx,
) -> Option<String> {
let emit_arg = |idx: usize| emit_expr(&args[idx], ctx, ectx);
match name {
"__buf_new" => {
let cap = emit_arg(0);
Some(format!(
"aver_rt::Buffer::with_capacity(({}) as usize)",
cap
))
}
"__buf_append" => {
let buf = emit_arg(0);
let s = emit_arg(1);
Some(format!(
"{{ let mut __b = {}; __b.push_str(&{}); __b }}",
buf, s
))
}
"__buf_append_sep_unless_first" => {
let buf = emit_arg(0);
let sep = emit_arg(1);
Some(format!(
"{{ let mut __b = {}; if !__b.is_empty() {{ __b.push_str(&{}); }} __b }}",
buf, sep
))
}
"__buf_finalize" => {
let buf = emit_arg(0);
Some(format!("aver_rt::AverStr::from({})", buf))
}
"__to_str" => {
let arg = emit_arg(0);
Some(format!(
"aver_rt::AverStr::from(aver_rt::aver_display(&({})))",
arg
))
}
"Console.print" => {
let arg = emit_arg(0);
Some(format!("aver_rt::console_print(&{})", arg))
}
"Console.error" | "Console.warn" => {
let arg = emit_arg(0);
let helper = if name == "Console.warn" {
"console_warn"
} else {
"console_error"
};
Some(format!("aver_rt::{}(&{})", helper, arg))
}
"Console.readLine" => Some("aver_rt::read_line()".to_string()),
"Result.Ok" => {
let arg = clone_arg(&args[0], ctx, ectx);
Some(format!("Ok({})", arg))
}
"Result.Err" => {
let arg = clone_arg(&args[0], ctx, ectx);
Some(format!("Err({})", arg))
}
"Result.withDefault" => {
let result = clone_arg(&args[0], ctx, ectx);
let default = clone_arg(&args[1], ctx, ectx);
Some(format!("{}.unwrap_or({})", result, default))
}
"Option.Some" => {
let arg = clone_arg(&args[0], ctx, ectx);
Some(format!("Some({})", arg))
}
"Option.withDefault" => {
let opt = clone_arg(&args[0], ctx, ectx);
let default = clone_arg(&args[1], ctx, ectx);
Some(format!("{}.unwrap_or({})", opt, default))
}
"Option.toResult" => {
let opt = clone_arg(&args[0], ctx, ectx);
let err = clone_arg(&args[1], ctx, ectx);
Some(format!("{}.ok_or({})", opt, err))
}
"Int.abs" => {
let arg = emit_arg(0);
Some(format!("{}.abs()", arg))
}
"Int.fromFloat" => {
let arg = emit_arg(0);
Some(format!("({} as i64)", arg))
}
"Int.fromString" => {
let arg = emit_arg(0);
Some(format!("{}.parse::<i64>().map_err(|e| e.to_string())", arg))
}
"Int.min" => {
let a = emit_arg(0);
let b = emit_arg(1);
Some(format!("{}.min({})", a, b))
}
"Int.max" => {
let a = emit_arg(0);
let b = emit_arg(1);
Some(format!("{}.max({})", a, b))
}
"Int.mod" => {
let a = emit_arg(0);
let b = emit_arg(1);
Some(format!(
"if ({b}) == 0i64 {{ Err(\"Int.mod: divisor must not be zero\".to_string()) }} else {{ Ok(({a}).rem_euclid({b})) }}"
))
}
"Float.abs" => {
let arg = emit_arg(0);
Some(format!("{}.abs()", arg))
}
"Float.round" => {
let arg = emit_arg(0);
Some(format!("{}.round() as i64", arg))
}
"Float.floor" => {
let arg = emit_arg(0);
Some(format!("{}.floor() as i64", arg))
}
"Float.ceil" => {
let arg = emit_arg(0);
Some(format!("{}.ceil() as i64", arg))
}
"Float.fromString" => {
let arg = emit_arg(0);
Some(format!("{}.parse::<f64>().map_err(|e| e.to_string())", arg))
}
"Float.sqrt" => {
let arg = emit_arg(0);
Some(format!("{}.sqrt()", arg))
}
"Float.pow" => {
let base = emit_arg(0);
let exp = emit_arg(1);
Some(format!("{}.powf({})", base, exp))
}
"Float.min" => {
let a = emit_arg(0);
let b = emit_arg(1);
Some(format!("{}.min({})", a, b))
}
"Float.max" => {
let a = emit_arg(0);
let b = emit_arg(1);
Some(format!("{}.max({})", a, b))
}
"Float.sin" => Some(format!("{}.sin()", emit_arg(0))),
"Float.cos" => Some(format!("{}.cos()", emit_arg(0))),
"Float.atan2" => {
let y = emit_arg(0);
let x = emit_arg(1);
Some(format!("{}.atan2({})", y, x))
}
"Float.pi" => Some("std::f64::consts::PI".to_string()),
"Float.fromInt" => Some(format!("{} as f64", emit_arg(0))),
"String.fromInt" => {
let arg = emit_arg(0);
Some(format!("{}.to_string()", arg))
}
"String.fromFloat" => {
let arg = emit_arg(0);
Some(format!("{}.to_string()", arg))
}
"String.fromBool" => {
let arg = emit_arg(0);
Some(format!("{}.to_string()", arg))
}
"String.charAt" => {
let s = emit_arg(0);
let idx = emit_arg(1);
Some(format!(
"{}.chars().nth({} as usize).map(|c| c.to_string())",
s, idx
))
}
"String.len" => {
let arg = emit_arg(0);
Some(format!("({}.chars().count() as i64)", arg))
}
"String.slice" => {
let s = emit_arg(0);
let from = emit_arg(1);
let to = emit_arg(2);
Some(format!("aver_rt::string_slice(&{}, {}, {})", s, from, to))
}
"String.contains" => {
let s = emit_arg(0);
let sub = emit_str_arg_or_deref(&args[1], ectx, ctx);
Some(format!("{}.contains({})", s, sub))
}
"String.startsWith" => {
let s = emit_arg(0);
let prefix = emit_str_arg_or_deref(&args[1], ectx, ctx);
Some(format!("{}.starts_with({})", s, prefix))
}
"String.endsWith" => {
let s = emit_arg(0);
let suffix = emit_str_arg_or_deref(&args[1], ectx, ctx);
Some(format!("{}.ends_with({})", s, suffix))
}
"String.trim" => {
let arg = emit_arg(0);
Some(format!("{}.trim().to_string()", arg))
}
"String.toUpper" => {
let arg = emit_arg(0);
Some(format!("{}.to_uppercase()", arg))
}
"String.toLower" => {
let arg = emit_arg(0);
Some(format!("{}.to_lowercase()", arg))
}
"String.split" => {
let s = emit_arg(0);
let delim = emit_arg(1);
Some(format!(
"aver_rt::AverList::from_vec({}.split(&*{}).map(|s| s.to_string()).collect::<Vec<_>>())",
s, delim
))
}
"String.join" => {
let parts = emit_arg(0);
let delim = emit_arg(1);
Some(format!("aver_rt::string_join(&{}, &{})", parts, delim))
}
"String.replace" => {
let s = emit_arg(0);
let from = emit_arg(1);
let to = emit_arg(2);
Some(format!("{}.replace(&*{}, &*{})", s, from, to))
}
"String.chars" => {
let arg = emit_arg(0);
Some(format!(
"aver_rt::AverList::from_vec({}.chars().map(|c| c.to_string()).collect::<Vec<_>>())",
arg
))
}
"String.repeat" => {
let s = emit_arg(0);
let n = emit_arg(1);
Some(format!("{}.repeat({} as usize)", s, n))
}
"String.indexOf" => {
let s = emit_arg(0);
let sub = emit_arg(1);
Some(format!(
"{}.find(&*{}).map(|i| i as i64).unwrap_or(-1i64)",
s, sub
))
}
"List.len" => {
if let Expr::List(items) = &args[0]
&& items.is_empty()
{
Some("0i64".to_string())
} else {
let arg = emit_arg(0);
Some(format!("({}.len() as i64)", arg))
}
}
"List.prepend" => {
let item = clone_arg(&args[0], ctx, ectx);
let list = clone_arg(&args[1], ctx, ectx);
Some(format!("aver_rt::AverList::prepend({}, &{})", item, list))
}
"List.take" => {
let list = emit_arg(0);
let count = emit_arg(1);
Some(format!(
"{{ let __n = if ({count}) <= 0 {{ 0usize }} else {{ usize::try_from({count}).unwrap_or(usize::MAX) }}; aver_rt::AverList::from_vec(({list}).iter().take(__n).cloned().collect::<Vec<_>>()) }}"
))
}
"List.drop" => {
let list = emit_arg(0);
let count = emit_arg(1);
Some(format!(
"{{ let __n = if ({count}) <= 0 {{ 0usize }} else {{ usize::try_from({count}).unwrap_or(usize::MAX) }}; aver_rt::AverList::from_vec(({list}).iter().skip(__n).cloned().collect::<Vec<_>>()) }}"
))
}
"List.concat" => {
let left = clone_arg(&args[0], ctx, ectx);
let right = clone_arg(&args[1], ctx, ectx);
Some(format!("aver_rt::AverList::concat(&{}, &{})", left, right))
}
"List.reverse" => {
let list = emit_expr(&args[0], ctx, ectx);
Some(format!("{}.reverse()", list))
}
"List.contains" => {
let list = emit_arg(0);
let item = emit_arg(1);
Some(format!("{}.contains(&{})", list, item))
}
"List.zip" => {
let a = emit_arg(0);
let b = emit_arg(1);
Some(format!(
"aver_rt::AverList::from_vec({}.iter().zip({}.iter()).map(|(a, b)| (a.clone(), b.clone())).collect::<Vec<_>>())",
a, b
))
}
"Map.fromList" => {
let list = clone_arg(&args[0], ctx, ectx);
Some(format!(
"{{ let mut m = HashMap::new(); for (k, v) in {}.iter().cloned() {{ m = m.insert_owned(k, v); }} m }}",
list
))
}
"Map.entries" => {
let map = emit_arg(0);
Some(format!(
"{{ let mut es: Vec<_> = {}.iter().map(|(k, v)| (k.clone(), v.clone())).collect(); es.sort_by(|a, b| a.0.cmp(&b.0)); aver_rt::AverList::from_vec(es) }}",
map
))
}
"Map.get" => {
let map = emit_arg(0);
let key = emit_arg(1);
Some(format!("{}.get(&{}).cloned()", map, key))
}
"Map.set" => {
let map = clone_arg(&args[0], ctx, ectx);
let key = clone_arg(&args[1], ctx, ectx);
let val = clone_arg(&args[2], ctx, ectx);
Some(format!("{}.insert_owned({}, {})", map, key, val))
}
"Map.has" => {
let map = emit_arg(0);
let key = emit_arg(1);
Some(format!("{}.contains_key(&{})", map, key))
}
"Map.remove" => {
let map = clone_arg(&args[0], ctx, ectx);
let key = emit_arg(1);
Some(format!("{}.remove_owned(&{})", map, key))
}
"Map.keys" => {
let map = emit_arg(0);
Some(format!(
"{{ let mut ks: Vec<_> = {}.keys().cloned().collect(); ks.sort(); aver_rt::AverList::from_vec(ks) }}",
map
))
}
"Map.values" => {
let map = emit_arg(0);
Some(format!(
"aver_rt::AverList::from_vec({}.values().cloned().collect::<Vec<_>>())",
map
))
}
"Map.len" => {
let map = emit_arg(0);
Some(format!("({}.len() as i64)", map))
}
"Bool.or" => {
let a = emit_arg(0);
let b = emit_arg(1);
Some(format!("({} || {})", a, b))
}
"Bool.and" => {
let a = emit_arg(0);
let b = emit_arg(1);
Some(format!("({} && {})", a, b))
}
"Bool.not" => {
let a = emit_arg(0);
Some(format!("(!{})", a))
}
"Char.toCode" => {
let arg = emit_arg(0);
Some(format!(
"({}.chars().next().map(|c| c as i64).unwrap_or(0i64))",
arg
))
}
"Char.fromCode" => {
let arg = emit_arg(0);
Some(format!(
"char::from_u32({} as u32).map(|c| c.to_string())",
arg
))
}
"Byte.toHex" => {
let arg = emit_arg(0);
Some(format!(
"{{ let __n = {}; if (0i64..=255i64).contains(&__n) {{ Ok(format!(\"{{:02x}}\", __n as u8)) }} else {{ Err(format!(\"Byte.toHex: {{}} is out of range 0–255\", __n)) }} }}",
arg
))
}
"Byte.fromHex" => {
let arg = emit_arg(0);
Some(format!(
"{{ let __s = {}; if __s.len() != 2 {{ Err(format!(\"Byte.fromHex: expected exactly 2 hex chars, got '{{}}'\", __s)) }} else {{ u8::from_str_radix(&__s, 16).map(|n| n as i64).map_err(|_| format!(\"Byte.fromHex: invalid hex '{{}}'\", __s)) }} }}",
arg
))
}
"String.byteLength" => {
let arg = emit_arg(0);
Some(format!("({}.len() as i64)", arg))
}
"Vector.new" => {
let size = emit_arg(0);
let default = clone_arg(&args[1], ctx, ectx);
Some(format!(
"aver_rt::AverVector::new({} as usize, {})",
size, default
))
}
"Vector.get" => {
let vec = emit_arg(0);
let idx = emit_arg(1);
Some(format!("{}.get({} as usize).cloned()", vec, idx))
}
"Vector.set" => {
let vec = clone_arg(&args[0], ctx, ectx);
let idx = emit_arg(1);
let val = clone_arg(&args[2], ctx, ectx);
Some(format!("{}.set_owned({} as usize, {})", vec, idx, val))
}
"Vector.len" => {
let vec = emit_arg(0);
Some(format!("({}.len() as i64)", vec))
}
"Vector.fromList" => {
let list = emit_arg(0);
Some(format!("aver_rt::AverVector::from_vec({}.to_vec())", list))
}
"List.fromVector" => {
let vec = emit_arg(0);
Some(format!("{}.to_list()", vec))
}
"Tcp.connect" => {
let host = emit_arg(0);
let port = emit_arg(1);
Some(format!("aver_rt::tcp::connect(&{}, {})", host, port))
}
"Tcp.writeLine" => {
let conn = emit_arg(0);
let line = emit_arg(1);
Some(format!("aver_rt::tcp::write_line(&{}, &{})", conn, line))
}
"Tcp.readLine" => {
let conn = emit_arg(0);
Some(format!("aver_rt::tcp::read_line(&{})", conn))
}
"Tcp.close" => {
let conn = emit_arg(0);
Some(format!("aver_rt::tcp::close(&{})", conn))
}
"Tcp.send" => {
let host = emit_arg(0);
let port = emit_arg(1);
let msg = emit_arg(2);
Some(format!("aver_rt::tcp::send(&{}, {}, &{})", host, port, msg))
}
"Tcp.ping" => {
let host = emit_arg(0);
let port = emit_arg(1);
Some(format!("aver_rt::tcp::ping(&{}, {})", host, port))
}
"Http.get" => {
let url = emit_arg(0);
Some(format!("aver_rt::http::get(&{})", url))
}
"Http.head" => {
let url = emit_arg(0);
Some(format!("aver_rt::http::head(&{})", url))
}
"Http.delete" => {
let url = emit_arg(0);
Some(format!("aver_rt::http::delete(&{})", url))
}
"Http.post" => {
let url = emit_arg(0);
let body = emit_arg(1);
let ct = emit_arg(2);
let headers = emit_arg(3);
Some(format!(
"aver_rt::http::post(&{}, &{}, &{}, &{})",
url, body, ct, headers
))
}
"Http.put" => {
let url = emit_arg(0);
let body = emit_arg(1);
let ct = emit_arg(2);
let headers = emit_arg(3);
Some(format!(
"aver_rt::http::put(&{}, &{}, &{}, &{})",
url, body, ct, headers
))
}
"Http.patch" => {
let url = emit_arg(0);
let body = emit_arg(1);
let ct = emit_arg(2);
let headers = emit_arg(3);
Some(format!(
"aver_rt::http::patch(&{}, &{}, &{}, &{})",
url, body, ct, headers
))
}
"HttpServer.listen" => {
let port = emit_arg(0);
let handler = emit_arg(1);
Some(format!(
"{{ if let Err(e) = crate::http_server_listen({}, {}) {{ panic!(\"{{}}\", e); }} }}",
port, handler
))
}
"HttpServer.listenWith" => {
let port = emit_arg(0);
let context = emit_arg(1);
let handler = emit_arg(2);
Some(format!(
"{{ if let Err(e) = crate::http_server_listen_with({}, {}.clone(), {}) {{ panic!(\"{{}}\", e); }} }}",
port, context, handler
))
}
"SelfHostRuntime.httpServerListen" => {
let port = emit_arg(0);
let handler = emit_arg(1);
Some(format!(
"crate::self_host_support::http_server_listen({}, {})",
port, handler
))
}
"SelfHostRuntime.httpServerListenWith" => {
let port = emit_arg(0);
let context = emit_arg(1);
let handler = emit_arg(2);
Some(format!(
"crate::self_host_support::http_server_listen_with({}, {}.clone(), {})",
port, context, handler
))
}
"Disk.readText" => {
let path = emit_arg(0);
Some(format!("aver_rt::read_text(&{})", path))
}
"Disk.writeText" => {
let path = emit_arg(0);
let content = emit_arg(1);
Some(format!("aver_rt::write_text(&{}, &{})", path, content))
}
"Disk.appendText" => {
let path = emit_arg(0);
let content = emit_arg(1);
Some(format!("aver_rt::append_text(&{}, &{})", path, content))
}
"Disk.exists" => {
let path = emit_arg(0);
Some(format!("aver_rt::path_exists(&{})", path))
}
"Disk.delete" => {
let path = emit_arg(0);
Some(format!("aver_rt::delete_file(&{})", path))
}
"Disk.deleteDir" => {
let path = emit_arg(0);
Some(format!("aver_rt::delete_dir(&{})", path))
}
"Disk.listDir" => {
let path = emit_arg(0);
Some(format!("aver_rt::list_dir(&{})", path))
}
"Disk.makeDir" => {
let path = emit_arg(0);
Some(format!("aver_rt::make_dir(&{})", path))
}
"Env.get" => {
let key = emit_arg(0);
Some(format!("aver_rt::env_get(&{})", key))
}
"Env.set" => {
let key = emit_arg(0);
let value = emit_arg(1);
Some(format!(
"aver_rt::env_set(&{}, &{}).expect(\"Env.set failed\")",
key, value
))
}
"Args.get" => Some("aver_rt::cli_args().into_aver()".to_string()),
"Time.now" => Some("aver_rt::time_now()".to_string()),
"Time.unixMs" => Some("aver_rt::time_unix_ms()".to_string()),
"Time.sleep" => {
let ms = emit_arg(0);
Some(format!("aver_rt::time_sleep({})", ms))
}
"Random.int" => {
let min = emit_arg(0);
let max = emit_arg(1);
Some(format!(
"aver_rt::random::random_int({}, {}).unwrap()",
min, max
))
}
"Random.float" => Some("aver_rt::random::random_float()".to_string()),
"Terminal.enableRawMode" => {
Some("aver_rt::terminal_enable_raw_mode().unwrap()".to_string())
}
"Terminal.disableRawMode" => {
Some("aver_rt::terminal_disable_raw_mode().unwrap()".to_string())
}
"Terminal.clear" => Some("aver_rt::terminal_clear().unwrap()".to_string()),
"Terminal.moveTo" => {
let x = emit_arg(0);
let y = emit_arg(1);
Some(format!("aver_rt::terminal_move_to({}, {}).unwrap()", x, y))
}
"Terminal.print" => {
let s = emit_arg(0);
Some(format!(
"{{ let __s = aver_rt::aver_display(&{}); aver_rt::terminal_print(&__s).unwrap() }}",
s
))
}
"Terminal.setColor" => {
let c = emit_arg(0);
Some(format!("aver_rt::terminal_set_color(&{}).unwrap()", c))
}
"Terminal.resetColor" => Some("aver_rt::terminal_reset_color().unwrap()".to_string()),
"Terminal.readKey" => Some("aver_rt::terminal_read_key()".to_string()),
"Terminal.size" => {
Some("{ let (w, h) = aver_rt::terminal_size().unwrap(); aver_rt::TerminalSize { width: w, height: h } }".to_string())
}
"Terminal.hideCursor" => Some("aver_rt::terminal_hide_cursor().unwrap()".to_string()),
"Terminal.showCursor" => Some("aver_rt::terminal_show_cursor().unwrap()".to_string()),
"Terminal.flush" => Some("aver_rt::terminal_flush().unwrap()".to_string()),
_ => None,
}
}
fn emit_str_arg_or_deref(expr: &Expr, ectx: &EmitCtx, ctx: &CodegenContext) -> String {
if let Expr::Literal(crate::ast::Literal::Str(s)) = expr {
format!("{:?}", s)
} else {
let code = emit_expr(expr, ctx, ectx);
format!("&*{}", code)
}
}
#[cfg(test)]
mod tests {
use super::emit_builtin_call;
use crate::ast::{AnnotBool, Expr, Literal, Spanned};
use crate::codegen::CodegenContext;
use crate::codegen::rust::emit_ctx::EmitCtx;
use crate::types::Type;
use std::collections::{HashMap, HashSet};
fn empty_ctx() -> CodegenContext {
CodegenContext {
items: vec![],
fn_sigs: HashMap::new(),
memo_fns: HashSet::new(),
memo_safe_types: HashSet::new(),
type_defs: vec![],
fn_defs: vec![],
project_name: "test".to_string(),
modules: vec![],
module_prefixes: HashSet::new(),
policy: None,
emit_replay_runtime: false,
runtime_policy_from_env: false,
guest_entry: None,
emit_self_host_support: false,
extra_fn_defs: Vec::new(),
mutual_tco_members: HashSet::new(),
recursive_fns: HashSet::new(),
fn_analyses: HashMap::new(),
buffer_build_sinks: HashMap::new(),
buffer_fusion_sites: Vec::new(),
synthesized_buffered_fns: Vec::new(),
}
}
#[test]
fn list_len_empty_literal_emits_typed_free_zero() {
let emitted = emit_builtin_call(
"List.len",
&[Expr::List(vec![])],
&empty_ctx(),
&EmitCtx::empty(),
)
.expect("List.len should emit");
assert_eq!(emitted, "0i64");
}
#[test]
fn http_post_preserves_nested_arg_liveness() {
let config_not_last = Expr::Resolved {
slot: 0,
name: "config".to_string(),
last_use: AnnotBool(false),
};
let config_last = Expr::Resolved {
slot: 0,
name: "config".to_string(),
last_use: AnnotBool(true),
};
let args = vec![
Expr::FnCall(
Box::new(Spanned::bare(Expr::Ident("queryUrl".to_string()))),
vec![Spanned::bare(config_not_last)],
),
Expr::FnCall(
Box::new(Spanned::bare(Expr::Ident("sqlBody".to_string()))),
vec![Spanned::bare(Expr::Ident("sql".to_string()))],
),
Expr::Literal(Literal::Str("application/json".to_string())),
Expr::List(vec![Spanned::bare(Expr::FnCall(
Box::new(Spanned::bare(Expr::Ident("authHeader".to_string()))),
vec![Spanned::bare(config_last)],
))]),
];
let mut local_types = HashMap::new();
local_types.insert("config".to_string(), Type::Named("DbConfig".to_string()));
local_types.insert("sql".to_string(), Type::Str);
let emitted = emit_builtin_call(
"Http.post",
&args,
&empty_ctx(),
&EmitCtx::for_fn(local_types),
)
.expect("Http.post should emit");
assert!(emitted.contains("queryUrl(config.clone())"));
assert!(emitted.contains("authHeader(config.clone())"));
}
}