use crate::ast::{BinOp, CfgCondition, Expr, Stmt, UnOp, Visibility};
use crate::builtins;
use crate::env::Env;
use crate::value::{AsyncLambda, Future, Lambda, Value};
use anyhow::{anyhow, Result};
use std::collections::BTreeMap;
fn eval_cfg_condition(condition: &CfgCondition) -> Result<bool> {
match condition {
CfgCondition::Platform(platform) => {
let current_os = std::env::consts::OS;
Ok(match platform.as_str() {
"windows" => current_os == "windows",
"linux" => current_os == "linux",
"macos" => current_os == "macos",
"unix" => current_os != "windows",
other => current_os == other,
})
}
CfgCondition::Feature(feature) => {
let features = std::env::var("AETHER_FEATURES").unwrap_or_default();
Ok(features.split(',').any(|f| f.trim() == feature))
}
CfgCondition::Not(inner) => Ok(!eval_cfg_condition(inner)?),
CfgCondition::All(conditions) => {
for cond in conditions {
if !eval_cfg_condition(cond)? {
return Ok(false);
}
}
Ok(true)
}
CfgCondition::Any(conditions) => {
for cond in conditions {
if eval_cfg_condition(cond)? {
return Ok(true);
}
}
Ok(false)
}
}
}
pub fn eval_program(stmts: &[Stmt], env: &mut Env) -> Result<Value> {
let mut last = Value::Null;
for s in stmts {
env.set_input(None);
last = eval_stmt(s, env)?;
}
Ok(last)
}
pub fn eval_stream(code: &str, env: &mut Env, on_item: &mut dyn FnMut(Value)) -> Result<usize> {
eval_stream_with_stats(code, env, on_item).map(|s| s.emitted)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct StreamStats {
pub emitted: usize,
pub pulled: usize,
pub streamed: bool,
pub short_circuited: bool,
pub barrier_tail: bool,
}
pub fn eval_stream_with_stats(
code: &str,
env: &mut Env,
on_item: &mut dyn FnMut(Value),
) -> Result<StreamStats> {
let stmts = crate::parser::parse_program(code)?;
let (last, init) = match stmts.split_last() {
Some(parts) => parts,
None => return Ok(StreamStats::default()),
};
for s in init {
env.set_input(None);
eval_stmt(s, env)?;
}
env.set_input(None);
if let Stmt::Expr(expr) = last {
if let Some(stats) = try_stream_pipeline(expr, env, on_item)? {
return Ok(stats);
}
}
let v = eval_stmt(last, env)?;
let emitted = emit_value(v, on_item);
Ok(StreamStats {
emitted,
pulled: emitted,
streamed: false,
short_circuited: false,
barrier_tail: false,
})
}
fn emit_value(v: Value, on_item: &mut dyn FnMut(Value)) -> usize {
match v {
Value::Array(items) => {
let n = items.len();
for it in items {
on_item(it);
}
n
}
Value::Null => 0,
other => {
on_item(other);
1
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum StageKind {
Elementwise,
Prefix(i64),
Barrier,
}
fn stage_kind(e: &Expr) -> StageKind {
let Expr::Call { callee, args, .. } = e else {
return StageKind::Barrier;
};
let Expr::Ident(name) = &**callee else {
return StageKind::Barrier;
};
match name.as_str() {
"map" | "where" | "filter" => StageKind::Elementwise,
"take" => match args.first() {
Some(Expr::LitInt(n)) => StageKind::Prefix(*n),
_ => StageKind::Barrier,
},
_ => StageKind::Barrier,
}
}
fn is_effect_free(e: &Expr) -> bool {
use crate::safety::{effect_of, Effect};
match e {
Expr::LitInt(_)
| Expr::LitFloat(_)
| Expr::LitStr(_)
| Expr::LitBool(_)
| Expr::Null
| Expr::Ident(_) => true,
Expr::Array(items) => items.iter().all(is_effect_free),
Expr::Record(fields) => fields.iter().all(|(_, v)| is_effect_free(v)),
Expr::Lambda { body, .. } => is_effect_free(body),
Expr::Binary { left, right, .. } => is_effect_free(left) && is_effect_free(right),
Expr::Unary { expr, .. } => is_effect_free(expr),
Expr::MemberAccess { object, .. } => is_effect_free(object),
Expr::Pipe { left, right } => is_effect_free(left) && is_effect_free(right),
Expr::Call {
callee,
args,
named,
} => {
let callee_pure = match &**callee {
Expr::Ident(name) => {
crate::builtins::is_dispatched(name) && effect_of(name) == Effect::Pure
}
_ => false,
};
callee_pure
&& args.iter().all(is_effect_free)
&& named.iter().all(|(_, v)| is_effect_free(v))
}
_ => false,
}
}
fn apply_stages(mut batch: Value, stages: &[&Expr], env: &mut Env) -> Result<Value> {
for stage in stages {
let saved = env.input().cloned();
env.set_input(Some(batch));
let res = eval_expr(stage, env);
match saved {
Some(v) => env.set_input(Some(v)),
None => env.set_input(None),
}
batch = res?;
}
Ok(batch)
}
fn try_stream_pipeline(
expr: &Expr,
env: &mut Env,
on_item: &mut dyn FnMut(Value),
) -> Result<Option<StreamStats>> {
let mut stages: Vec<&Expr> = Vec::new();
let mut cur = expr;
while let Expr::Pipe { left, right } = cur {
stages.push(right);
cur = left;
}
if stages.is_empty() {
return Ok(None); }
stages.reverse();
let all_kinds: Vec<StageKind> = stages.iter().map(|s| stage_kind(s)).collect();
let tail_at = all_kinds
.iter()
.position(|k| *k == StageKind::Barrier)
.unwrap_or(stages.len());
if tail_at == 0 {
return Ok(None);
}
let tail: Vec<&Expr> = stages[tail_at..].to_vec();
let stages: Vec<&Expr> = stages[..tail_at].to_vec();
let kinds: Vec<StageKind> = all_kinds[..tail_at].to_vec();
let src = eval_expr(cur, env)?;
let items = match src {
Value::Array(items) => items,
other => {
let every: Vec<&Expr> = stages.iter().chain(tail.iter()).copied().collect();
let out = apply_stages(other, &every, env)?;
let emitted = emit_value(out, on_item);
return Ok(Some(StreamStats {
emitted,
pulled: 1,
streamed: false,
short_circuited: false,
barrier_tail: !tail.is_empty(),
}));
}
};
let may_abandon: Vec<bool> = kinds
.iter()
.enumerate()
.map(|(i, k)| {
matches!(k, StageKind::Prefix(_)) && stages[..i].iter().all(|s| is_effect_free(s))
})
.collect();
let mut taken = vec![0i64; stages.len()];
let mut stats = StreamStats {
streamed: true,
barrier_tail: !tail.is_empty(),
..Default::default()
};
let mut buffered: Vec<Value> = Vec::new();
for x in items {
let satisfied = kinds
.iter()
.enumerate()
.any(|(i, k)| may_abandon[i] && matches!(k, StageKind::Prefix(n) if taken[i] >= *n));
if satisfied {
stats.short_circuited = true;
break;
}
stats.pulled += 1;
let mut batch = Value::Array(vec![x]);
for (i, stage) in stages.iter().enumerate() {
if let StageKind::Prefix(n) = kinds[i] {
let width = match &batch {
Value::Array(items) => items.len() as i64,
Value::Null => 0,
_ => 1,
};
let room = (n - taken[i]).max(0);
if room == 0 {
batch = Value::Array(Vec::new());
break;
}
taken[i] += width.min(room);
if width > room {
if let Value::Array(items) = batch {
batch = Value::Array(items.into_iter().take(room as usize).collect());
}
}
continue;
}
let saved = env.input().cloned();
env.set_input(Some(batch));
let res = eval_expr(stage, env);
match saved {
Some(v) => env.set_input(Some(v)),
None => env.set_input(None),
}
batch = res?;
}
let mut deliver = |v: Value| {
if tail.is_empty() {
on_item(v);
stats.emitted += 1;
} else {
buffered.push(v);
}
};
match batch {
Value::Array(out) => {
for it in out {
deliver(it);
}
}
Value::Null => {}
other => deliver(other),
}
}
if !tail.is_empty() {
let out = apply_stages(Value::Array(buffered), &tail, env)?;
stats.emitted += emit_value(out, on_item);
}
Ok(Some(stats))
}
pub fn eval_stmt(stmt: &Stmt, env: &mut Env) -> Result<Value> {
match stmt {
Stmt::Let {
name,
value,
is_mut,
visibility,
} => {
let v = eval_expr(value, env)?;
env.declare_var(name, v.clone(), *is_mut)
.map_err(|e| anyhow::anyhow!("{}", e))?;
if *visibility == Visibility::Pub {
env.set_public(name);
}
Ok(v)
}
Stmt::Expr(e) => eval_expr(e, env),
Stmt::Import {
items,
source,
alias,
} => {
#[cfg(feature = "native")]
{
use crate::packages::ImportResolver;
let cwd = std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from("."));
let mut resolver = ImportResolver::new(cwd);
resolver.process_import(items, source, alias, env, |stmts, module_env| {
eval_program(stmts, module_env)
})?;
Ok(Value::Null)
}
#[cfg(not(feature = "native"))]
{
let _ = (items, source, alias);
Err(anyhow!("import statements are not supported in this build"))
}
}
Stmt::Export { items, from_source } => {
#[cfg(feature = "native")]
{
if let Some(source) = from_source {
use crate::packages::ImportResolver;
let cwd =
std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from("."));
let mut resolver = ImportResolver::new(cwd);
let module_env = resolver.load_module(source, eval_program)?;
for item in items {
let value = module_env.get_var(&item.name).cloned().ok_or_else(|| {
anyhow!("'{}' not found in module '{}'", item.name, source)
})?;
let export_name = item.alias.as_ref().unwrap_or(&item.name);
env.set_var_unchecked(export_name.clone(), value);
env.add_export(export_name);
}
} else {
for item in items {
if env.get_var(&item.name).is_none() {
return Err(anyhow!("cannot export '{}': not defined", item.name));
}
let export_name = item.alias.as_ref().unwrap_or(&item.name);
if let Some(alias) = &item.alias {
let value = env.get_var(&item.name).cloned().unwrap();
env.set_var_unchecked(alias.clone(), value);
}
env.add_export(export_name);
}
}
Ok(Value::Null)
}
#[cfg(not(feature = "native"))]
{
let _ = (items, from_source);
Err(anyhow!("export statements are not supported in this build"))
}
}
Stmt::Cfg { condition, body } => {
if eval_cfg_condition(condition)? {
eval_stmt(body, env)
} else {
Ok(Value::Null)
}
}
}
}
pub fn eval_expr(expr: &Expr, env: &mut Env) -> Result<Value> {
crate::safety::check_deadline()?;
match expr {
Expr::LitInt(n) => Ok(Value::Int(*n)),
Expr::LitFloat(f) => Ok(Value::Float(*f)),
Expr::LitStr(s) => {
if s.contains("${") {
interpolate_string(s, env)
} else {
Ok(Value::Str(s.clone()))
}
}
Expr::LitBool(b) => Ok(Value::Bool(*b)),
Expr::Null => Ok(Value::Null),
Expr::Ident(name) => Ok(env.get_var(name).cloned().unwrap_or(Value::Null)),
Expr::Array(items) => {
let mut out = Vec::with_capacity(items.len());
for it in items {
out.push(eval_expr(it, env)?);
}
Ok(Value::Array(out))
}
Expr::Record(kvs) => {
let mut m = BTreeMap::new();
for (k, v) in kvs {
m.insert(k.clone(), eval_expr(v, env)?);
}
Ok(Value::Record(m))
}
Expr::Lambda { params, body } => Ok(Value::Lambda(Lambda {
params: params.clone(),
body: body.clone(),
captured: capture_free_vars(params, body, env),
})),
Expr::AsyncLambda { params, body } => Ok(Value::AsyncLambda(AsyncLambda {
params: params.clone(),
body: body.clone(),
captured: capture_free_vars(params, body, env),
})),
Expr::Await(inner) => {
let val = eval_expr(inner, env)?;
match val {
Value::Future(future) => {
let _depth = crate::safety::enter_call()?;
let saved_pipe = env.input().cloned();
env.set_input(None);
let restore_caps = install_captured(&future.lambda.captured, env);
let mut saved: Vec<(String, Option<Value>)> = Vec::new();
for (param, arg) in future.lambda.params.iter().zip(future.args.iter()) {
saved.push((param.clone(), env.get_var(param).cloned()));
env.set_var_unchecked(param, arg.clone());
}
let out = eval_expr(&future.lambda.body, env);
for (name, old) in saved.into_iter().rev() {
match old {
Some(v) => env.set_var_unchecked(&name, v),
None => env.del_var(&name),
}
}
restore_captured(restore_caps, env);
env.set_input(saved_pipe);
out
}
other => Ok(other),
}
}
Expr::TryCatch {
try_expr,
catch_var,
catch_expr,
} => {
match eval_expr(try_expr, env) {
Ok(Value::Error(msg)) => {
bind_caught(catch_var.as_deref(), Value::Str(msg), catch_expr, env)
}
Ok(val) => {
Ok(val)
}
Err(e) => {
let caught = match e.downcast_ref::<crate::safety::SafetyError>() {
Some(se) => Value::from_json(&se.to_json()),
None => Value::Str(e.to_string()),
};
bind_caught(catch_var.as_deref(), caught, catch_expr, env)
}
}
}
Expr::Throw(inner) => {
let val = eval_expr(inner, env)?;
let msg = match val {
Value::Str(s) => s,
other => format!("{:?}", other),
};
Ok(Value::Error(msg))
}
Expr::Call {
callee,
args,
named: _,
} => {
let mut vals = Vec::with_capacity(args.len());
for a in args {
vals.push(eval_expr(a, env)?);
}
let pin = env.input().cloned();
if let Expr::Ident(name) = &**callee {
if let Some(v) = env.get_var(name).cloned() {
match v {
Value::Lambda(_) | Value::AsyncLambda(_) => {
return call_value_with_pipe(v, pin, vals, env)
}
_ => {
return builtins::call_with_input(name, vals, pin, env);
}
}
} else {
return builtins::call_with_input(name, vals, pin, env);
}
}
let f = eval_expr(callee, env)?;
call_value_with_pipe(f, pin, vals, env)
}
Expr::Pipe { left, right } => {
let left_val = eval_expr(left, env)?;
if let Expr::Ident(name) = &**right {
if let Some(v) = env.get_var(name).cloned() {
match v {
Value::Lambda(_) | Value::AsyncLambda(_) => {
return call_value_with_pipe(v, None, vec![left_val], env);
}
_ => {
return crate::builtins::call_with_input(
name,
Vec::new(),
Some(left_val),
env,
);
}
}
} else {
return crate::builtins::call_with_input(name, Vec::new(), Some(left_val), env);
}
}
if let Expr::Lambda { .. } = &**right {
let f = eval_expr(right, env)?;
return call_value_with_pipe(f, Some(left_val), Vec::new(), env);
}
let saved = env.input().cloned();
env.set_input(Some(left_val));
let res = eval_expr(right, env);
match saved {
Some(v) => env.set_input(Some(v)),
None => env.set_input(None),
}
res
}
Expr::Unary { op, expr } => {
let v = eval_expr(expr, env)?;
match (op, v) {
(UnOp::Neg, Value::Int(n)) => Ok(Value::Int(-n)),
(UnOp::Neg, Value::Float(x)) => Ok(Value::Float(-x)),
(UnOp::Not, v) => Ok(Value::Bool(!is_truthy(&v))),
(_, other) => Err(anyhow!("bad unary op on {:?}", other)),
}
}
Expr::Binary { left, op, right } => {
let a = eval_expr(left, env)?;
let b = eval_expr(right, env)?;
binop(op, a, b)
}
Expr::MemberAccess { object, field } => {
let obj = eval_expr(object, env)?;
match obj {
Value::Record(map) => map.get(field).cloned().ok_or_else(|| {
crate::safety::unknown_field(
field,
crate::builtins::nearest_names(field, map.keys().map(|k| k.as_str())),
)
}),
other => Err(anyhow!(
"cannot access field '{}' on non-record value: {:?}",
field,
other
)),
}
}
Expr::Match { scrutinee, arms } => {
let value = eval_expr(scrutinee, env)?;
for arm in arms {
if let Some(bindings) = match_pattern(&arm.pattern, &value) {
if let Some(guard_expr) = &arm.guard {
let mut temp_env = env.clone();
for (name, val) in bindings.iter() {
temp_env.set_var_unchecked(name, val.clone());
}
let guard_result = eval_expr(guard_expr, &mut temp_env)?;
if !is_truthy(&guard_result) {
continue; }
}
for (name, val) in bindings {
env.set_var_unchecked(&name, val);
}
return eval_expr(&arm.body, env);
}
}
Err(anyhow!("match: no arm matched the value"))
}
}
}
use crate::ast::Pattern;
use std::collections::HashMap;
fn match_pattern(pattern: &Pattern, value: &Value) -> Option<HashMap<String, Value>> {
let mut bindings = HashMap::new();
if match_pattern_impl(pattern, value, &mut bindings) {
Some(bindings)
} else {
None
}
}
fn match_pattern_impl(
pattern: &Pattern,
value: &Value,
bindings: &mut HashMap<String, Value>,
) -> bool {
match pattern {
Pattern::Wildcard => true,
Pattern::Ident(name) => {
bindings.insert(name.clone(), value.clone());
true
}
Pattern::LitInt(n) => matches!(value, Value::Int(v) if v == n),
Pattern::LitStr(s) => matches!(value, Value::Str(v) if v == s),
Pattern::LitBool(b) => matches!(value, Value::Bool(v) if v == b),
Pattern::Null => matches!(value, Value::Null),
Pattern::Constructor { name, args } => {
if let Value::Record(map) = value {
if let Some(Value::Str(tag)) = map.get("_tag") {
if tag == name {
if args.is_empty() {
return true;
} else if args.len() == 1 {
if let Some(inner) = map.get("_value") {
return match_pattern_impl(&args[0], inner, bindings);
}
}
}
}
}
false
}
Pattern::Array(patterns) => {
if let Value::Array(values) = value {
if patterns.len() != values.len() {
return false;
}
for (pat, val) in patterns.iter().zip(values.iter()) {
if !match_pattern_impl(pat, val, bindings) {
return false;
}
}
true
} else {
false
}
}
Pattern::Record(field_patterns) => {
if let Value::Record(map) = value {
for (field_name, field_pattern) in field_patterns {
if let Some(field_value) = map.get(field_name) {
if !match_pattern_impl(field_pattern, field_value, bindings) {
return false;
}
} else {
return false; }
}
true
} else {
false
}
}
}
}
fn call_value_with_pipe(
f: Value,
pin: Option<Value>,
mut args: Vec<Value>,
env: &mut Env,
) -> Result<Value> {
match f {
Value::Lambda(l) => {
match (pin, l.params.len(), args.len()) {
(_, 0, 0) => call_lambda0(&l, env),
(Some(Value::Array(arr)), 1, 0) => {
let mut out = Vec::with_capacity(arr.len());
for (i, x) in arr.into_iter().enumerate() {
out.push(call_lambda1(&l, x, i, env)?);
}
Ok(Value::Array(out))
}
(Some(v), 1, 0) => call_lambda1(&l, v, 0, env),
(_, 2, 2) if args.len() == 2 => {
let b = args
.pop()
.ok_or_else(|| anyhow!("Expected second argument for lambda call"))?;
let a = args
.pop()
.ok_or_else(|| anyhow!("Expected first argument for lambda call"))?;
call_lambda2(&l, a, b, 0, env)
}
(_, 1, 1) => {
let arg = args
.pop()
.ok_or_else(|| anyhow!("Expected argument for lambda call"))?;
call_lambda1(&l, arg, 0, env)
}
(_, n, m) if n >= 3 && n == m => call_lambda_n(&l, args, env),
_ => Err(crate::safety::arg_err(
"lambda arity mismatch or missing input",
)),
}
}
Value::AsyncLambda(al) => {
let all_args = if let Some(p) = pin {
let mut all = Vec::with_capacity(1 + args.len());
all.push(p);
all.extend(args);
all
} else {
args
};
Ok(Value::Future(Future {
lambda: al,
args: all_args,
}))
}
Value::Builtin(b) => builtins::call_with_input(&b.name, args, pin, env),
Value::Str(name) | Value::Uri(name) => {
if let Some(p) = pin {
let mut all = Vec::with_capacity(1 + args.len());
all.push(p);
all.extend(args);
builtins::call(&name, all, env)
} else {
builtins::call(&name, args, env)
}
}
Value::Null => Err(anyhow!("cannot call null")),
other => Err(anyhow!("cannot call non-function value: {:?}", other)),
}
}
fn is_truthy(v: &Value) -> bool {
match v {
Value::Null => false,
Value::Bool(b) => *b,
Value::Int(n) => *n != 0,
Value::Float(f) => *f != 0.0,
Value::Str(s) => !s.is_empty(),
Value::Uri(s) => !s.is_empty(),
Value::Array(a) => !a.is_empty(),
Value::Record(m) => !m.is_empty(),
Value::Table(t) => !t.rows.is_empty(),
Value::Lambda(_) => true,
Value::AsyncLambda(_) => true,
Value::Future(_) => true,
Value::Error(_) => false, Value::Builtin(_) => true, }
}
const MAX_STRING_BYTES: usize = 8 * 1024 * 1024;
fn checked_string(s: String) -> Result<Value> {
if s.len() > MAX_STRING_BYTES {
return Err(anyhow!(
"string operation would produce {} bytes, over the {} byte limit",
s.len(),
MAX_STRING_BYTES
));
}
Ok(Value::Str(s))
}
fn bind_caught(
catch_var: Option<&str>,
caught: Value,
catch_expr: &Expr,
env: &mut Env,
) -> Result<Value> {
let Some(name) = catch_var else {
return eval_expr(catch_expr, env);
};
let previous = env.get_var(name).cloned();
env.set_var_unchecked(name, caught);
let out = eval_expr(catch_expr, env);
match previous {
Some(v) => env.set_var_unchecked(name, v),
None => env.del_var(name),
}
out
}
fn free_idents(expr: &Expr, bound: &mut Vec<String>, out: &mut std::collections::BTreeSet<String>) {
match expr {
Expr::LitInt(_) | Expr::LitFloat(_) | Expr::LitStr(_) | Expr::LitBool(_) | Expr::Null => {}
Expr::Ident(name) => {
if !bound.iter().any(|b| b == name) {
out.insert(name.clone());
}
}
Expr::Array(items) => {
for e in items {
free_idents(e, bound, out);
}
}
Expr::Record(fields) => {
for (_, e) in fields {
free_idents(e, bound, out);
}
}
Expr::Lambda { params, body } | Expr::AsyncLambda { params, body } => {
let depth = bound.len();
bound.extend(params.iter().cloned());
free_idents(body, bound, out);
bound.truncate(depth);
}
Expr::Await(inner) | Expr::Throw(inner) => free_idents(inner, bound, out),
Expr::TryCatch {
try_expr,
catch_var,
catch_expr,
} => {
free_idents(try_expr, bound, out);
let depth = bound.len();
if let Some(v) = catch_var {
bound.push(v.clone());
}
free_idents(catch_expr, bound, out);
bound.truncate(depth);
}
Expr::Call {
callee,
args,
named,
} => {
free_idents(callee, bound, out);
for e in args {
free_idents(e, bound, out);
}
for (_, e) in named {
free_idents(e, bound, out);
}
}
Expr::Pipe { left, right } => {
free_idents(left, bound, out);
free_idents(right, bound, out);
}
Expr::Binary { left, right, .. } => {
free_idents(left, bound, out);
free_idents(right, bound, out);
}
Expr::Unary { expr, .. } => free_idents(expr, bound, out),
Expr::MemberAccess { object, .. } => free_idents(object, bound, out),
Expr::Match { scrutinee, arms } => {
free_idents(scrutinee, bound, out);
for arm in arms {
let depth = bound.len();
pattern_bindings(&arm.pattern, bound);
if let Some(g) = &arm.guard {
free_idents(g, bound, out);
}
free_idents(&arm.body, bound, out);
bound.truncate(depth);
}
}
}
}
fn pattern_bindings(p: &Pattern, bound: &mut Vec<String>) {
match p {
Pattern::Wildcard
| Pattern::LitInt(_)
| Pattern::LitStr(_)
| Pattern::LitBool(_)
| Pattern::Null => {}
Pattern::Ident(name) => bound.push(name.clone()),
Pattern::Constructor { args, .. } => {
for a in args {
pattern_bindings(a, bound);
}
}
Pattern::Array(items) => {
for a in items {
pattern_bindings(a, bound);
}
}
Pattern::Record(fields) => {
for (name, sub) in fields {
match sub {
Pattern::Wildcard => bound.push(name.clone()),
other => pattern_bindings(other, bound),
}
}
}
}
}
fn capture_free_vars(
params: &[String],
body: &Expr,
env: &Env,
) -> std::collections::BTreeMap<String, Value> {
let mut bound: Vec<String> = params.to_vec();
let mut free = std::collections::BTreeSet::new();
free_idents(body, &mut bound, &mut free);
let mut captured = std::collections::BTreeMap::new();
for name in free {
#[cfg(feature = "native")]
if crate::modules::is_module_name(&name) {
continue;
}
if env.is_declared_mutable(&name) {
continue;
}
if let Some(v) = env.get_var(&name) {
captured.insert(name, v.clone());
}
}
captured
}
fn install_captured(
captured: &std::collections::BTreeMap<String, Value>,
env: &mut Env,
) -> Vec<(String, Option<Value>)> {
let mut saved = Vec::with_capacity(captured.len());
for (name, value) in captured {
saved.push((name.clone(), env.get_var(name).cloned()));
env.set_var_unchecked(name, value.clone());
}
saved
}
fn restore_captured(saved: Vec<(String, Option<Value>)>, env: &mut Env) {
for (name, old) in saved.into_iter().rev() {
match old {
Some(v) => env.set_var_unchecked(&name, v),
None => env.del_var(&name),
}
}
}
fn call_lambda0(l: &Lambda, env: &mut Env) -> Result<Value> {
let _depth = crate::safety::enter_call()?;
let restore_caps = install_captured(&l.captured, env);
let saved_pipe = env.input().cloned();
env.set_input(None);
let out = eval_expr(&l.body, env);
match saved_pipe {
Some(v) => env.set_input(Some(v)),
None => env.set_input(None),
}
restore_captured(restore_caps, env);
out
}
fn call_lambda1(l: &Lambda, x: Value, i: usize, env: &mut Env) -> Result<Value> {
let _depth = crate::safety::enter_call()?;
let restore_caps = install_captured(&l.captured, env);
let p = l
.params
.first()
.ok_or_else(|| crate::safety::arg_err("lambda needs 1 param"))?
.clone();
let saved_pipe = env.input().cloned();
env.set_input(None);
let old_p = env.get_var(&p).cloned();
env.set_var_unchecked(&p, x);
let mut old_i: Option<Value> = None;
if let Some(ip) = l.params.get(1) {
if ip == "i" {
old_i = env.get_var("i").cloned();
env.set_var_unchecked("i", Value::Int(i as i64));
}
}
let out = eval_expr(&l.body, env);
if let Some(v) = old_i {
env.set_var_unchecked("i", v);
} else if l.params.get(1).map(|s| s.as_str()) == Some("i") {
env.del_var("i");
}
if let Some(v) = old_p {
env.set_var_unchecked(&p, v);
} else {
env.del_var(&p);
}
match saved_pipe {
Some(v) => env.set_input(Some(v)),
None => env.set_input(None),
}
restore_captured(restore_caps, env);
out
}
fn call_lambda_n(l: &Lambda, args: Vec<Value>, env: &mut Env) -> Result<Value> {
let _depth = crate::safety::enter_call()?;
let restore_caps = install_captured(&l.captured, env);
if args.len() != l.params.len() {
return Err(anyhow!(
"lambda expects {} arguments, got {}",
l.params.len(),
args.len()
));
}
let saved_pipe = env.input().cloned();
env.set_input(None);
let mut old_bindings: Vec<(String, Option<Value>)> = Vec::with_capacity(l.params.len());
for (param, arg) in l.params.iter().zip(args) {
old_bindings.push((param.clone(), env.get_var(param).cloned()));
env.set_var_unchecked(param, arg);
}
let out = eval_expr(&l.body, env);
for (param, old_val) in old_bindings.into_iter().rev() {
if let Some(v) = old_val {
env.set_var_unchecked(¶m, v);
} else {
env.del_var(¶m);
}
}
match saved_pipe {
Some(v) => env.set_input(Some(v)),
None => env.set_input(None),
}
restore_captured(restore_caps, env);
out
}
fn call_lambda2(l: &Lambda, a: Value, b: Value, i: usize, env: &mut Env) -> Result<Value> {
let _depth = crate::safety::enter_call()?;
let restore_caps = install_captured(&l.captured, env);
let p1 = l
.params
.first()
.ok_or_else(|| crate::safety::arg_err("lambda needs 2 params"))?
.clone();
let p2 = l
.params
.get(1)
.ok_or_else(|| crate::safety::arg_err("lambda needs 2 params"))?
.clone();
let saved_pipe = env.input().cloned();
env.set_input(None);
let old_p1 = env.get_var(&p1).cloned();
env.set_var_unchecked(&p1, a);
let old_p2 = env.get_var(&p2).cloned();
env.set_var_unchecked(&p2, b);
let mut old_i: Option<Value> = None;
if let Some(ip) = l.params.get(2) {
if ip == "i" {
old_i = env.get_var("i").cloned();
env.set_var_unchecked("i", Value::Int(i as i64));
}
}
let out = eval_expr(&l.body, env);
if let Some(v) = old_i {
env.set_var_unchecked("i", v);
} else if l.params.get(2).map(|s| s.as_str()) == Some("i") {
env.del_var("i");
}
if let Some(v) = old_p2 {
env.set_var_unchecked(&p2, v);
} else {
env.del_var(&p2);
}
if let Some(v) = old_p1 {
env.set_var_unchecked(&p1, v);
} else {
env.del_var(&p1);
}
match saved_pipe {
Some(v) => env.set_input(Some(v)),
None => env.set_input(None),
}
restore_captured(restore_caps, env);
out
}
fn value_eq(a: &Value, b: &Value) -> bool {
use Value::*;
match (a, b) {
(Null, Null) => true,
(Bool(x), Bool(y)) => x == y,
(Int(x), Int(y)) => x == y,
(Float(x), Float(y)) => x == y,
(Int(x), Float(y)) => (*x as f64) == *y,
(Float(x), Int(y)) => *x == (*y as f64),
(Str(x), Str(y)) => x == y,
(Uri(x), Uri(y)) => x == y,
(Array(ax), Array(ay)) => {
ax.len() == ay.len() && ax.iter().zip(ay).all(|(x, y)| value_eq(x, y))
}
(Record(rx), Record(ry)) => {
rx.len() == ry.len()
&& rx
.iter()
.all(|(k, vx)| ry.get(k).is_some_and(|vy| value_eq(vx, vy)))
}
_ => false,
}
}
fn binop(op: &BinOp, a: Value, b: Value) -> Result<Value> {
use BinOp::*;
Ok(match (op, a, b) {
(Add, Value::Int(x), Value::Int(y)) => Value::Int(x + y),
(Add, Value::Float(x), Value::Float(y)) => Value::Float(x + y),
(Add, Value::Int(x), Value::Float(y)) => Value::Float((x as f64) + y),
(Add, Value::Float(x), Value::Int(y)) => Value::Float(x + (y as f64)),
(Add, Value::Str(x), Value::Str(y)) => checked_string(format!("{}{}", x, y))?,
(Add, Value::Str(x), Value::Int(y)) => checked_string(format!("{}{}", x, y))?,
(Add, Value::Str(x), Value::Float(y)) => checked_string(format!("{}{}", x, y))?,
(Add, Value::Int(x), Value::Str(y)) => checked_string(format!("{}{}", x, y))?,
(Add, Value::Float(x), Value::Str(y)) => checked_string(format!("{}{}", x, y))?,
(Sub, Value::Int(x), Value::Int(y)) => Value::Int(x - y),
(Sub, Value::Float(x), Value::Float(y)) => Value::Float(x - y),
(Sub, Value::Int(x), Value::Float(y)) => Value::Float((x as f64) - y),
(Sub, Value::Float(x), Value::Int(y)) => Value::Float(x - (y as f64)),
(Mul, Value::Int(x), Value::Int(y)) => Value::Int(x * y),
(Mul, Value::Float(x), Value::Float(y)) => Value::Float(x * y),
(Mul, Value::Int(x), Value::Float(y)) => Value::Float((x as f64) * y),
(Mul, Value::Float(x), Value::Int(y)) => Value::Float(x * (y as f64)),
(Div, Value::Int(x), Value::Int(y)) => Value::Float((x as f64) / (y as f64)),
(Div, Value::Float(x), Value::Float(y)) => Value::Float(x / y),
(Div, Value::Int(x), Value::Float(y)) => Value::Float((x as f64) / y),
(Div, Value::Float(x), Value::Int(y)) => Value::Float(x / (y as f64)),
(Rem, Value::Int(x), Value::Int(y)) => Value::Int(x % y),
(Eq, x, y) => Value::Bool(value_eq(&x, &y)),
(Ne, x, y) => Value::Bool(!value_eq(&x, &y)),
(Lt, Value::Int(x), Value::Int(y)) => Value::Bool(x < y),
(Lt, Value::Float(x), Value::Float(y)) => Value::Bool(x < y),
(Lte, Value::Int(x), Value::Int(y)) => Value::Bool(x <= y),
(Lte, Value::Float(x), Value::Float(y)) => Value::Bool(x <= y),
(Gt, Value::Int(x), Value::Int(y)) => Value::Bool(x > y),
(Gt, Value::Float(x), Value::Float(y)) => Value::Bool(x > y),
(Gte, Value::Int(x), Value::Int(y)) => Value::Bool(x >= y),
(Gte, Value::Float(x), Value::Float(y)) => Value::Bool(x >= y),
(And, x, y) => Value::Bool(is_truthy(&x) && is_truthy(&y)),
(Or, x, y) => Value::Bool(is_truthy(&x) || is_truthy(&y)),
(Pow, Value::Int(x), Value::Int(y)) => {
if y >= 0 {
Value::Int(x.pow(y as u32))
} else {
Value::Float((x as f64).powf(y as f64))
}
}
(Pow, Value::Float(x), Value::Float(y)) => Value::Float(x.powf(y)),
(Pow, Value::Int(x), Value::Float(y)) => Value::Float((x as f64).powf(y)),
(Pow, Value::Float(x), Value::Int(y)) => Value::Float(x.powf(y as f64)),
(Mul, Value::Str(s), Value::Int(n)) | (Mul, Value::Int(n), Value::Str(s)) => {
if n <= 0 {
Value::Str(String::new())
} else {
let want = s.len().saturating_mul(n as usize);
if want > MAX_STRING_BYTES {
return Err(anyhow!(
"string repeat would produce {} bytes, over the {} byte limit",
want,
MAX_STRING_BYTES
));
}
Value::Str(s.repeat(n as usize))
}
}
(Add, Value::Str(x), other) => {
checked_string(format!("{}{}", x, other.to_display_string()))?
}
(Add, other, Value::Str(y)) => {
checked_string(format!("{}{}", other.to_display_string(), y))?
}
(op, a, b) => return Err(anyhow!("unsupported op {:?} on {:?} and {:?}", op, a, b)),
})
}
fn interpolate_string(s: &str, env: &mut Env) -> Result<Value> {
let mut result = String::new();
let mut chars = s.chars().peekable();
while let Some(ch) = chars.next() {
if ch == '$' && chars.peek() == Some(&'{') {
chars.next();
let mut expr_str = String::new();
let mut depth = 1;
for ch in chars.by_ref() {
if ch == '{' {
depth += 1;
expr_str.push(ch);
} else if ch == '}' {
depth -= 1;
if depth == 0 {
break;
}
expr_str.push(ch);
} else {
expr_str.push(ch);
}
}
match crate::parser::parse_program(&expr_str) {
Ok(stmts) if !stmts.is_empty() => {
if let crate::ast::Stmt::Expr(expr) = &stmts[0] {
match eval_expr(expr, env) {
Ok(val) => {
result.push_str(&val.to_display_string());
}
Err(e) => {
result.push_str(&format!("${{{}}} [error: {}]", expr_str, e));
}
}
} else {
result.push_str(&format!("${{{}}}", expr_str));
}
}
_ => {
result.push_str(&format!("${{{}}} [error: does not parse]", expr_str));
}
}
} else {
result.push(ch);
}
}
Ok(Value::Str(result))
}