#[cfg(feature = "no_std")]
use alloc::{format, string::{String, ToString}, vec, vec::Vec};
use alloc_import::collections::BTreeMap;
#[cfg(not(feature = "no_std"))]
use std as alloc_import;
#[cfg(feature = "no_std")]
use alloc as alloc_import;
#[cfg(feature = "no_std")]
use alloc::rc::Rc;
#[cfg(not(feature = "no_std"))]
use std::rc::Rc;
use core::cell::RefCell;
use crate::builtins::{
self, error, error_at, error_fatal_with_hint, error_with_hint, error_with_hint_at,
};
use crate::error::BopError;
use crate::lexer::StringPart;
use crate::methods;
use crate::ops;
use crate::parser::*;
use crate::value::{BopFn, EnumPayload, FnBody, Value};
use crate::{BopHost, BopLimits};
enum ImportSlot {
Loading,
Loaded(ModuleBindings),
}
#[derive(Clone)]
struct ModuleBindings {
bindings: Vec<(String, Value)>,
fn_decls: Vec<(String, FnDef)>,
struct_defs: Vec<((String, String), Vec<String>)>,
enum_defs: Vec<((String, String), Vec<crate::parser::VariantDecl>)>,
methods: Vec<((String, String), String, FnDef)>,
}
type ImportCache = Rc<RefCell<alloc_import::collections::BTreeMap<String, ImportSlot>>>;
const MAX_CALL_DEPTH: usize = 64;
enum Signal {
None,
Break,
Continue,
Return(Value),
}
#[derive(Clone)]
struct FnDef {
params: Vec<String>,
body: Vec<Stmt>,
}
pub struct Evaluator<'h, H: BopHost> {
scopes: Vec<BTreeMap<String, Value>>,
functions: BTreeMap<String, FnDef>,
current_module: String,
struct_defs: BTreeMap<(String, String), Vec<String>>,
enum_defs: BTreeMap<(String, String), Vec<VariantDecl>>,
methods: BTreeMap<(String, String), BTreeMap<String, FnDef>>,
type_bindings: Vec<BTreeMap<String, String>>,
module_aliases: BTreeMap<String, Rc<crate::value::BopModule>>,
host: &'h mut H,
steps: u64,
call_depth: usize,
limits: BopLimits,
rand_state: u64,
imports: ImportCache,
imported_here: alloc_import::collections::BTreeSet<String>,
pending_try_return: Option<Value>,
runtime_warnings: Vec<crate::error::BopWarning>,
}
impl<'h, H: BopHost> Evaluator<'h, H> {
pub fn new(host: &'h mut H, limits: BopLimits) -> Self {
crate::memory::bop_memory_init(limits.max_memory);
let (struct_defs, enum_defs, builtin_bindings) = seed_builtin_types();
Self {
scopes: vec![BTreeMap::new()],
functions: BTreeMap::new(),
current_module: String::from(crate::value::ROOT_MODULE_PATH),
struct_defs,
enum_defs,
methods: BTreeMap::new(),
type_bindings: vec![builtin_bindings],
module_aliases: BTreeMap::new(),
host,
steps: 0,
call_depth: 0,
limits,
rand_state: 0,
imports: Rc::new(RefCell::new(alloc_import::collections::BTreeMap::new())),
imported_here: alloc_import::collections::BTreeSet::new(),
pending_try_return: None,
runtime_warnings: Vec::new(),
}
}
fn new_for_module(
host: &'h mut H,
limits: BopLimits,
imports: ImportCache,
module_path: String,
) -> Self {
let (struct_defs, enum_defs, builtin_bindings) = seed_builtin_types();
Self {
scopes: vec![BTreeMap::new()],
functions: BTreeMap::new(),
current_module: module_path,
struct_defs,
enum_defs,
methods: BTreeMap::new(),
type_bindings: vec![builtin_bindings],
module_aliases: BTreeMap::new(),
host,
steps: 0,
call_depth: 0,
limits,
rand_state: 0,
imports,
imported_here: alloc_import::collections::BTreeSet::new(),
pending_try_return: None,
runtime_warnings: Vec::new(),
}
}
pub fn run(mut self, stmts: &[Stmt]) -> Result<(), BopError> {
let result = self.exec_block(stmts);
#[cfg(not(feature = "no_std"))]
{
for w in &self.runtime_warnings {
eprintln!("warning: {}", w.message);
}
}
match result? {
Signal::Break => {
return Err(error(0, "break used outside of a loop"));
}
Signal::Continue => {
return Err(error(0, "continue used outside of a loop"));
}
_ => {}
}
Ok(())
}
fn tick(&mut self, line: u32) -> Result<(), BopError> {
self.steps += 1;
if self.steps > self.limits.max_steps {
return Err(error_fatal_with_hint(
line,
"Your code took too many steps (possible infinite loop)",
"Check your loops — make sure they have a condition that eventually stops them.",
));
}
if crate::memory::bop_memory_exceeded() {
return Err(error_fatal_with_hint(
line,
"Memory limit exceeded",
"Your code is using too much memory. Check for large strings or arrays growing in loops.",
));
}
self.host.on_tick()?;
Ok(())
}
fn push_scope(&mut self) {
self.scopes.push(BTreeMap::new());
self.type_bindings.push(BTreeMap::new());
}
fn pop_scope(&mut self) {
self.scopes.pop();
self.type_bindings.pop();
}
fn resolve_type_ref(&self, namespace: Option<&str>, type_name: &str) -> Option<String> {
resolve_type_in(
&self.scopes,
&self.type_bindings,
&self.module_aliases,
namespace,
type_name,
)
}
fn bind_local_type(&mut self, name: &str) {
if let Some(top) = self.type_bindings.last_mut() {
top.insert(name.to_string(), self.current_module.clone());
}
}
fn define(&mut self, name: String, value: Value) {
if let Some(scope) = self.scopes.last_mut() {
scope.insert(name, value);
}
}
fn get_var(&self, name: &str) -> Option<&Value> {
for scope in self.scopes.iter().rev() {
if let Some(val) = scope.get(name) {
return Some(val);
}
}
None
}
fn set_var(&mut self, name: &str, value: Value) -> bool {
for scope in self.scopes.iter_mut().rev() {
if scope.contains_key(name) {
scope.insert(name.to_string(), value);
return true;
}
}
false
}
fn value_candidates_hint(&self, target: &str) -> Option<String> {
let mut candidates: Vec<String> = Vec::new();
for scope in &self.scopes {
for k in scope.keys() {
candidates.push(k.clone());
}
}
for name in self.functions.keys() {
candidates.push(name.clone());
}
crate::suggest::did_you_mean(target, candidates)
}
fn callable_candidates_hint(&self, target: &str) -> Option<String> {
let mut candidates: Vec<String> =
self.functions.keys().cloned().collect();
for builtin in crate::suggest::CORE_CALLABLE_BUILTINS {
candidates.push((*builtin).to_string());
}
crate::suggest::did_you_mean(target, candidates)
}
fn exec_block(&mut self, stmts: &[Stmt]) -> Result<Signal, BopError> {
for stmt in stmts {
let signal = self.exec_stmt(stmt)?;
match signal {
Signal::None => {}
other => return Ok(other),
}
}
Ok(Signal::None)
}
fn exec_stmt(&mut self, stmt: &Stmt) -> Result<Signal, BopError> {
self.tick(stmt.line)?;
match &stmt.kind {
StmtKind::Let { name, value, is_const: _ } => {
let val = self.eval_expr(value)?;
self.define(name.clone(), val);
Ok(Signal::None)
}
StmtKind::Assign { target, op, value } => {
let new_val = self.eval_expr(value)?;
self.exec_assign(target, op, new_val, stmt.line)?;
Ok(Signal::None)
}
StmtKind::If {
condition,
body,
else_ifs,
else_body,
} => {
if self.eval_expr(condition)?.is_truthy() {
self.push_scope();
let sig = self.exec_block(body)?;
self.pop_scope();
return Ok(sig);
}
for (elif_cond, elif_body) in else_ifs {
if self.eval_expr(elif_cond)?.is_truthy() {
self.push_scope();
let sig = self.exec_block(elif_body)?;
self.pop_scope();
return Ok(sig);
}
}
if let Some(else_body) = else_body {
self.push_scope();
let sig = self.exec_block(else_body)?;
self.pop_scope();
return Ok(sig);
}
Ok(Signal::None)
}
StmtKind::While { condition, body } => {
loop {
self.tick(stmt.line)?;
if !self.eval_expr(condition)?.is_truthy() {
break;
}
self.push_scope();
let sig = self.exec_block(body)?;
self.pop_scope();
match sig {
Signal::Break => break,
Signal::Continue => continue,
Signal::Return(v) => return Ok(Signal::Return(v)),
Signal::None => {}
}
}
Ok(Signal::None)
}
StmtKind::Repeat { count, body } => {
let n = match self.eval_expr(count)? {
Value::Int(n) => n,
Value::Number(n) => n as i64,
other => {
return Err(error(
stmt.line,
format!("repeat needs a number, but got {}", other.type_name()),
));
}
};
for _ in 0..n.max(0) {
self.tick(stmt.line)?;
self.push_scope();
let sig = self.exec_block(body)?;
self.pop_scope();
match sig {
Signal::Break => break,
Signal::Continue => continue,
Signal::Return(v) => return Ok(Signal::Return(v)),
Signal::None => {}
}
}
Ok(Signal::None)
}
StmtKind::ForIn {
var,
iterable,
body,
} => {
let val = self.eval_expr(iterable)?;
if matches!(
&val,
Value::Array(_) | Value::Str(_) | Value::Dict(_)
) {
let mut val = val;
let items: Vec<Value> = match &mut val {
Value::Array(arr) => arr.take(),
Value::Str(s) => s
.chars()
.map(|c| Value::new_str(c.to_string()))
.collect(),
Value::Dict(d) => d
.iter()
.map(|(k, _)| Value::new_str(k.clone()))
.collect(),
_ => unreachable!(),
};
for item in items {
self.tick(stmt.line)?;
self.push_scope();
self.define(var.clone(), item);
let sig = self.exec_block(body)?;
self.pop_scope();
match sig {
Signal::Break => break,
Signal::Continue => continue,
Signal::Return(v) => return Ok(Signal::Return(v)),
Signal::None => {}
}
}
return Ok(Signal::None);
}
let iterator = match &val {
Value::Iter(_) | Value::Struct(_) | Value::EnumVariant(_) => {
self.call_method_full(&val, "iter", Vec::new(), stmt.line)?
}
other => {
return Err(error(
stmt.line,
crate::error_messages::cant_iterate_over(
other.type_name(),
),
));
}
};
loop {
self.tick(stmt.line)?;
let next_val =
self.call_method_full(&iterator, "next", Vec::new(), stmt.line)?;
let item = match unwrap_iter_result(&next_val) {
Some(IterStep::Next(v)) => v,
Some(IterStep::Done) => break,
None => {
return Err(error(
stmt.line,
format!(
"`.next()` on a `for` iterator must return `Iter::Next(v)` or `Iter::Done`, got {}",
next_val.inspect()
),
));
}
};
self.push_scope();
self.define(var.clone(), item);
let sig = self.exec_block(body)?;
self.pop_scope();
match sig {
Signal::Break => break,
Signal::Continue => continue,
Signal::Return(v) => return Ok(Signal::Return(v)),
Signal::None => {}
}
}
Ok(Signal::None)
}
StmtKind::FnDecl { name, params, body } => {
self.functions.insert(
name.clone(),
FnDef {
params: params.clone(),
body: body.clone(),
},
);
Ok(Signal::None)
}
StmtKind::MethodDecl {
type_name,
method_name,
params,
body,
} => {
let type_key = (self.current_module.clone(), type_name.clone());
self.methods
.entry(type_key)
.or_default()
.insert(
method_name.clone(),
FnDef {
params: params.clone(),
body: body.clone(),
},
);
Ok(Signal::None)
}
StmtKind::Return { value } => {
let val = match value {
Some(expr) => self.eval_expr(expr)?,
None => Value::None,
};
Ok(Signal::Return(val))
}
StmtKind::Break => Ok(Signal::Break),
StmtKind::Continue => Ok(Signal::Continue),
StmtKind::Use { path, items, alias } => {
self.exec_import(
path,
items.as_deref(),
alias.as_deref(),
stmt.line,
)?;
Ok(Signal::None)
}
StmtKind::StructDecl { name, fields } => {
let mut seen = alloc_import::collections::BTreeSet::new();
for f in fields {
if !seen.insert(f.clone()) {
return Err(error(
stmt.line,
format!("Struct `{}` has duplicate field `{}`", name, f),
));
}
}
let key = (self.current_module.clone(), name.clone());
if let Some(existing) = self.struct_defs.get(&key) {
if existing == fields {
self.bind_local_type(name);
return Ok(Signal::None);
}
return Err(error(
stmt.line,
format!("Struct `{}` is already declared", name),
));
}
self.struct_defs.insert(key, fields.clone());
self.bind_local_type(name);
Ok(Signal::None)
}
StmtKind::EnumDecl { name, variants } => {
let key = (self.current_module.clone(), name.clone());
if let Some(existing) = self.enum_defs.get(&key) {
if variants_equivalent(existing, variants) {
self.bind_local_type(name);
return Ok(Signal::None);
}
return Err(error(
stmt.line,
format!("Enum `{}` is already declared", name),
));
}
let mut seen_variants = alloc_import::collections::BTreeSet::new();
for v in variants {
if !seen_variants.insert(v.name.clone()) {
return Err(error(
stmt.line,
format!(
"Enum `{}` has duplicate variant `{}`",
name, v.name
),
));
}
if let VariantKind::Struct(fields) | VariantKind::Tuple(fields) =
&v.kind
{
let mut seen_fields = alloc_import::collections::BTreeSet::new();
for f in fields {
if !seen_fields.insert(f.clone()) {
return Err(error(
stmt.line,
format!(
"Enum variant `{}::{}` has duplicate field `{}`",
name, v.name, f
),
));
}
}
}
}
self.enum_defs.insert(key, variants.clone());
self.bind_local_type(name);
Ok(Signal::None)
}
StmtKind::ExprStmt(expr) => {
self.eval_expr(expr)?;
Ok(Signal::None)
}
}
}
fn exec_import(
&mut self,
path: &str,
items: Option<&[String]>,
alias: Option<&str>,
line: u32,
) -> Result<(), BopError> {
let is_plain_glob = items.is_none() && alias.is_none();
if is_plain_glob && self.imported_here.contains(path) {
return Ok(());
}
let bindings = self.load_module(path, line)?;
for (key, fields) in &bindings.struct_defs {
if let Some(existing) = self.struct_defs.get(key) {
if existing == fields {
continue;
}
return Err(error(
line,
format!(
"Type `{}` from `{}` reloaded with different fields",
key.1, key.0
),
));
}
self.struct_defs.insert(key.clone(), fields.clone());
}
for (key, variants) in &bindings.enum_defs {
if let Some(existing) = self.enum_defs.get(key) {
if variants_equivalent(existing, variants) {
continue;
}
return Err(error(
line,
format!(
"Type `{}` from `{}` reloaded with different variants",
key.1, key.0
),
));
}
self.enum_defs.insert(key.clone(), variants.clone());
}
for (type_key, method_name, fn_def) in &bindings.methods {
let slot = self.methods.entry(type_key.clone()).or_default();
slot.insert(method_name.clone(), fn_def.clone());
}
let mut exports: Vec<(String, Value)> =
Vec::with_capacity(bindings.fn_decls.len() + bindings.bindings.len());
let mut fn_entries: Vec<(String, FnDef)> =
Vec::with_capacity(bindings.fn_decls.len());
for (name, fn_def) in &bindings.fn_decls {
let value = Value::new_fn(
fn_def.params.clone(),
Vec::new(),
fn_def.body.clone(),
Some(name.clone()),
);
exports.push((name.clone(), value));
fn_entries.push((name.clone(), fn_def.clone()));
}
for (name, value) in &bindings.bindings {
exports.push((name.clone(), value.clone()));
}
if let Some(list) = items {
let available: alloc_import::collections::BTreeSet<&str> =
exports.iter().map(|(k, _)| k.as_str()).collect();
for wanted in list {
if !available.contains(wanted.as_str())
&& !bindings
.struct_defs
.iter()
.any(|((_, n), _)| n == wanted)
&& !bindings
.enum_defs
.iter()
.any(|((_, n), _)| n == wanted)
{
return Err(error(
line,
format!(
"`{}` isn't exported from `{}` (selective import)",
wanted, path
),
));
}
}
let listed: alloc_import::collections::BTreeSet<String> =
list.iter().cloned().collect();
exports.retain(|(k, _)| listed.contains(k));
fn_entries.retain(|(k, _)| listed.contains(k));
}
let module_type_names: Vec<String> = bindings
.struct_defs
.iter()
.map(|((_, n), _)| n.clone())
.chain(bindings.enum_defs.iter().map(|((_, n), _)| n.clone()))
.collect();
let exposed_types: Vec<String> = match items {
Some(list) => module_type_names
.into_iter()
.filter(|n| list.iter().any(|i| i == n))
.collect(),
None => module_type_names
.into_iter()
.filter(|n| !crate::naming::is_private(n))
.collect(),
};
if let Some(alias_name) = alias {
if self
.scopes
.last()
.map(|s| s.contains_key(alias_name))
.unwrap_or(false)
|| self.functions.contains_key(alias_name)
{
return Err(error(
line,
format!(
"`{}` is already bound — can't use it as a module alias",
alias_name
),
));
}
for (name, fn_def) in fn_entries {
if !self.functions.contains_key(&name) {
self.functions.insert(name, fn_def);
}
}
let module_rc = Rc::new(crate::value::BopModule {
path: path.to_string(),
bindings: exports,
types: exposed_types,
});
self.define(
alias_name.to_string(),
Value::Module(Rc::clone(&module_rc)),
);
self.module_aliases
.insert(alias_name.to_string(), Rc::clone(&module_rc));
if let Some(scope) = self.type_bindings.last_mut() {
scope.insert(alias_name.to_string(), path.to_string());
}
} else {
let skip_private = items.is_none();
for (name, fn_def) in fn_entries {
if skip_private && crate::naming::is_private(&name) {
continue;
}
if self
.scopes
.last()
.map(|s| s.contains_key(&name))
.unwrap_or(false)
|| self.functions.contains_key(&name)
{
self.runtime_warnings.push(crate::error::BopWarning::at(
format!(
"`{}` from `{}` shadowed by an existing binding — the first definition wins",
name, path
),
line,
));
continue;
}
self.functions.insert(name, fn_def);
}
for (name, value) in exports {
if skip_private && crate::naming::is_private(&name) {
continue;
}
if self
.scopes
.last()
.map(|s| s.contains_key(&name))
.unwrap_or(false)
{
self.runtime_warnings.push(crate::error::BopWarning::at(
format!(
"`{}` from `{}` shadowed by an existing binding — the first definition wins",
name, path
),
line,
));
continue;
}
self.define(name, value);
}
for tn in &exposed_types {
let already_bound = self
.type_bindings
.last()
.map(|s| s.contains_key(tn))
.unwrap_or(false);
if already_bound {
continue;
}
if let Some(scope) = self.type_bindings.last_mut() {
scope.insert(tn.clone(), path.to_string());
}
}
}
if is_plain_glob {
self.imported_here.insert(path.to_string());
}
Ok(())
}
fn load_module(&mut self, path: &str, line: u32) -> Result<ModuleBindings, BopError> {
{
let cache = self.imports.borrow();
if let Some(ImportSlot::Loaded(bindings)) = cache.get(path) {
return Ok(bindings.clone());
}
if let Some(ImportSlot::Loading) = cache.get(path) {
return Err(error(
line,
format!("Circular import: module `{}` is still loading", path),
));
}
}
let source = match self.host.resolve_module(path) {
Some(Ok(s)) => s,
Some(Err(e)) => return Err(e),
None => {
return Err(error(
line,
format!("Module `{}` not found", path),
));
}
};
self.imports
.borrow_mut()
.insert(path.to_string(), ImportSlot::Loading);
let result = self.evaluate_module(path, &source, line);
match result {
Ok(bindings) => {
self.imports
.borrow_mut()
.insert(path.to_string(), ImportSlot::Loaded(bindings.clone()));
Ok(bindings)
}
Err(e) => {
self.imports.borrow_mut().remove(path);
Err(e)
}
}
}
fn evaluate_module(
&mut self,
module_path: &str,
source: &str,
line: u32,
) -> Result<ModuleBindings, BopError> {
let _ = line;
let stmts = crate::parse(source)?;
let imports = Rc::clone(&self.imports);
let limits = self.limits.clone();
let mut sub = Evaluator::new_for_module(
self.host,
limits,
imports,
module_path.to_string(),
);
match sub.exec_block(&stmts)? {
Signal::Return(_) | Signal::None => {}
Signal::Break => {
return Err(error(0, "break used outside of a loop"));
}
Signal::Continue => {
return Err(error(0, "continue used outside of a loop"));
}
}
let mut bindings: Vec<(String, Value)> = Vec::new();
if let Some(top_scope) = sub.scopes.into_iter().next() {
for (k, v) in top_scope {
bindings.push((k, v));
}
}
let fn_decls: Vec<(String, FnDef)> =
sub.functions.into_iter().collect();
let builtin_mp = crate::value::BUILTIN_MODULE_PATH;
let struct_defs: Vec<((String, String), Vec<String>)> = sub
.struct_defs
.into_iter()
.filter(|((mp, _), _)| mp != builtin_mp)
.collect();
let enum_defs: Vec<((String, String), Vec<crate::parser::VariantDecl>)> = sub
.enum_defs
.into_iter()
.filter(|((mp, _), _)| mp != builtin_mp)
.collect();
let mut methods: Vec<((String, String), String, FnDef)> = Vec::new();
for (type_key, by_method) in sub.methods {
for (method_name, fn_def) in by_method {
methods.push((type_key.clone(), method_name, fn_def));
}
}
Ok(ModuleBindings {
bindings,
fn_decls,
struct_defs,
enum_defs,
methods,
})
}
fn validate_namespaced_type(
&self,
ns: &str,
type_name: &str,
line: u32,
) -> Result<(), BopError> {
if let Some(v) = self.get_var(ns) {
let module = match v {
Value::Module(m) => m,
_ => {
return Err(error(
line,
format!(
"`{}` is a {}, not a module alias — can't reach `{}` through it",
ns,
v.type_name(),
type_name
),
));
}
};
if !module.types.iter().any(|t| t == type_name) {
return Err(error(
line,
format!(
"`{}` isn't a type exported from `{}`",
type_name, module.path
),
));
}
return Ok(());
}
if let Some(module) = self.module_aliases.get(ns) {
if !module.types.iter().any(|t| t == type_name) {
return Err(error(
line,
format!(
"`{}` isn't a type exported from `{}`",
type_name, module.path
),
));
}
return Ok(());
}
Err(error(
line,
format!("`{}` isn't a module alias in scope", ns),
))
}
fn eval_enum_construct(
&mut self,
namespace: Option<&str>,
type_name: &str,
variant: &str,
payload: &VariantPayload,
line: u32,
) -> Result<Value, BopError> {
let module_path = match namespace {
Some(ns) => {
self.validate_namespaced_type(ns, type_name, line)?;
self.resolve_type_ref(Some(ns), type_name)
.unwrap_or_else(|| self.current_module.clone())
}
None => self
.resolve_type_ref(None, type_name)
.ok_or_else(|| {
error(line, crate::error_messages::enum_not_declared(type_name))
})?,
};
let key = (module_path.clone(), type_name.to_string());
let variants = self.enum_defs.get(&key).ok_or_else(|| {
error(line, crate::error_messages::enum_not_declared(type_name))
})?
.clone();
let decl = variants.iter().find(|v| v.name == variant).ok_or_else(|| {
let msg = crate::error_messages::enum_has_no_variant(type_name, variant);
let names = variants.iter().map(|v| v.name.as_str());
match crate::suggest::did_you_mean(variant, names) {
Some(hint) => error_with_hint(line, msg, hint),
None => error(line, msg),
}
})?
.clone();
match (&decl.kind, payload) {
(VariantKind::Unit, VariantPayload::Unit) => Ok(Value::new_enum_unit(
module_path,
type_name.to_string(),
variant.to_string(),
)),
(VariantKind::Tuple(fields), VariantPayload::Tuple(args)) => {
if args.len() != fields.len() {
return Err(error(
line,
format!(
"`{}::{}` expects {} argument{}, but got {}",
type_name,
variant,
fields.len(),
if fields.len() == 1 { "" } else { "s" },
args.len()
),
));
}
let mut items = Vec::with_capacity(args.len());
for arg in args {
items.push(self.eval_expr(arg)?);
}
Ok(Value::new_enum_tuple(
module_path,
type_name.to_string(),
variant.to_string(),
items,
))
}
(VariantKind::Struct(decl_fields), VariantPayload::Struct(provided)) => {
let mut seen = alloc_import::collections::BTreeSet::new();
let mut provided_map: BTreeMap<String, Value> = BTreeMap::new();
for (fname, fexpr) in provided {
if !seen.insert(fname.clone()) {
return Err(error(
line,
format!(
"Field `{}` specified twice in `{}::{}`",
fname, type_name, variant
),
));
}
if !decl_fields.iter().any(|d| d == fname) {
return Err(error(
line,
crate::error_messages::variant_has_no_field(
type_name, variant, fname,
),
));
}
provided_map.insert(fname.clone(), self.eval_expr(fexpr)?);
}
let mut values: Vec<(String, Value)> =
Vec::with_capacity(decl_fields.len());
for decl_field in decl_fields {
match provided_map.remove(decl_field) {
Some(v) => values.push((decl_field.clone(), v)),
None => {
return Err(error(
line,
format!(
"Missing field `{}` in `{}::{}` construction",
decl_field, type_name, variant
),
));
}
}
}
Ok(Value::new_enum_struct(
module_path,
type_name.to_string(),
variant.to_string(),
values,
))
}
(VariantKind::Unit, _) => Err(error(
line,
format!(
"Variant `{}::{}` takes no payload",
type_name, variant
),
)),
(VariantKind::Tuple(_), _) => Err(error(
line,
format!(
"Variant `{}::{}` expects positional arguments `(…)`",
type_name, variant
),
)),
(VariantKind::Struct(_), _) => Err(error(
line,
format!(
"Variant `{}::{}` expects named fields `{{ … }}`",
type_name, variant
),
)),
}
}
fn exec_assign(
&mut self,
target: &AssignTarget,
op: &AssignOp,
new_val: Value,
line: u32,
) -> Result<(), BopError> {
match target {
AssignTarget::Variable(name) => {
let final_val = match op {
AssignOp::Eq => new_val,
_ => {
let current = self
.get_var(name)
.ok_or_else(|| {
error(line, format!("Variable `{}` doesn't exist yet", name))
})?
.clone();
self.apply_compound_op(¤t, op, &new_val, line)?
}
};
if !self.set_var(name, final_val) {
return Err(error_with_hint(
line,
format!("Variable `{}` doesn't exist yet", name),
format!("Use `let` to create a new variable: let {} = ...", name),
));
}
Ok(())
}
AssignTarget::Index { object, index } => {
let idx = self.eval_expr(index)?;
let val_to_set = match op {
AssignOp::Eq => new_val,
_ => {
let obj = self.eval_expr(object)?;
let current = ops::index_get(&obj, &idx, line)?;
self.apply_compound_op(¤t, op, &new_val, line)?
}
};
if let ExprKind::Ident(name) = &object.kind {
let mut obj = self
.get_var(name)
.ok_or_else(|| {
error(line, format!("Variable `{}` doesn't exist", name))
})?
.clone();
ops::index_set(&mut obj, &idx, val_to_set, line)?;
self.set_var(name, obj);
Ok(())
} else {
Err(error(
line,
"Can only assign to indexed variables (like `arr[0] = val`)",
))
}
}
AssignTarget::Field { object, field } => {
let name = match &object.kind {
ExprKind::Ident(n) => n.clone(),
_ => {
return Err(error(
line,
"Can only assign to fields of named variables (like `p.x = val`)",
));
}
};
let mut obj = self
.get_var(&name)
.ok_or_else(|| {
error(line, format!("Variable `{}` doesn't exist", name))
})?
.clone();
let val_to_set = match op {
AssignOp::Eq => new_val,
_ => {
let current = match &obj {
Value::Struct(s) => s.field(field).cloned().ok_or_else(|| {
error(
line,
crate::error_messages::struct_has_no_field(
s.type_name(),
field,
),
)
})?,
other => {
return Err(error(
line,
crate::error_messages::cant_assign_field(
field,
other.type_name(),
),
));
}
};
self.apply_compound_op(¤t, op, &new_val, line)?
}
};
match &mut obj {
Value::Struct(s) => {
let struct_type = s.type_name().to_string();
if !s.set_field(field, val_to_set) {
return Err(error(
line,
crate::error_messages::struct_has_no_field(&struct_type, field),
));
}
}
other => {
return Err(error(
line,
crate::error_messages::cant_assign_field(
field,
other.type_name(),
),
));
}
}
self.set_var(&name, obj);
Ok(())
}
}
}
fn apply_compound_op(
&self,
left: &Value,
op: &AssignOp,
right: &Value,
line: u32,
) -> Result<Value, BopError> {
match op {
AssignOp::Eq => Ok(right.clone()),
AssignOp::AddEq => ops::add(left, right, line),
AssignOp::SubEq => ops::sub(left, right, line),
AssignOp::MulEq => ops::mul(left, right, line),
AssignOp::DivEq => ops::div(left, right, line),
AssignOp::ModEq => ops::rem(left, right, line),
}
}
fn eval_expr(&mut self, expr: &Expr) -> Result<Value, BopError> {
match &expr.kind {
ExprKind::Int(n) => Ok(Value::Int(*n)),
ExprKind::Number(n) => Ok(Value::Number(*n)),
ExprKind::Str(s) => Ok(Value::new_str(s.clone())),
ExprKind::Bool(b) => Ok(Value::Bool(*b)),
ExprKind::None => Ok(Value::None),
ExprKind::StringInterp(parts) => {
let mut result = String::new();
for part in parts {
match part {
StringPart::Literal(s) => result.push_str(s),
StringPart::Variable(name) => {
let val = self
.get_var(name)
.ok_or_else(|| {
error_at(expr.line, expr.column, crate::error_messages::variable_not_found(name))
})?
.clone();
result.push_str(&format!("{}", val));
}
}
}
Ok(Value::new_str(result))
}
ExprKind::Ident(name) => {
if let Some(v) = self.get_var(name) {
return Ok(v.clone());
}
if let Some(f) = self.functions.get(name) {
return Ok(Value::new_fn(
f.params.clone(),
Vec::new(),
f.body.clone(),
Some(name.to_string()),
));
}
let hint = self
.value_candidates_hint(name)
.unwrap_or_else(|| "Did you forget to create it with `let`?".to_string());
Err(error_with_hint_at(
expr.line,
expr.column,
crate::error_messages::variable_not_found(name),
hint,
))
}
ExprKind::BinaryOp { left, op, right } => {
if matches!(op, BinOp::And) {
let lval = self.eval_expr(left)?;
if !lval.is_truthy() {
return Ok(Value::Bool(false));
}
let rval = self.eval_expr(right)?;
return Ok(Value::Bool(rval.is_truthy()));
}
if matches!(op, BinOp::Or) {
let lval = self.eval_expr(left)?;
if lval.is_truthy() {
return Ok(Value::Bool(true));
}
let rval = self.eval_expr(right)?;
return Ok(Value::Bool(rval.is_truthy()));
}
let lval = self.eval_expr(left)?;
let rval = self.eval_expr(right)?;
self.binary_op(&lval, op, &rval, expr.line)
}
ExprKind::UnaryOp { op, expr: inner } => {
let val = self.eval_expr(inner)?;
match op {
UnaryOp::Neg => ops::neg(&val, expr.line),
UnaryOp::Not => Ok(ops::not(&val)),
}
}
ExprKind::Call { callee, args } => {
let mut eval_args = Vec::new();
for arg in args {
eval_args.push(self.eval_expr(arg)?);
}
if let ExprKind::Ident(name) = &callee.kind {
if let Some(v) = self.get_var(name).cloned() {
return self.call_value(v, eval_args, expr.line, Some(name));
}
return self.call_function(name, eval_args, expr.line);
}
let callee_val = self.eval_expr(callee)?;
self.call_value(callee_val, eval_args, expr.line, None)
}
ExprKind::Lambda { params, body } => {
let captures = self.snapshot_captures();
Ok(Value::new_fn(
params.clone(),
captures,
body.clone(),
None,
))
}
ExprKind::MethodCall {
object,
method,
args,
} => {
let mut eval_args = Vec::new();
for arg in args {
eval_args.push(self.eval_expr(arg)?);
}
let obj_val = self.eval_expr(object)?;
if let Value::Module(m) = &obj_val {
if let Some(result) =
methods::common_method(&obj_val, method, &eval_args, expr.line)?
{
return Ok(result.0);
}
if let Some((_, v)) = m.bindings.iter().find(|(k, _)| k == method) {
let callee = v.clone();
return self.call_value(callee, eval_args, expr.line, Some(method));
}
return Err(error(
expr.line,
format!(
"`{}` isn't exported from `{}`",
method, m.path
),
));
}
let type_key: Option<(String, String)> = match &obj_val {
Value::Struct(s) => Some((
s.module_path().to_string(),
s.type_name().to_string(),
)),
Value::EnumVariant(e) => Some((
e.module_path().to_string(),
e.type_name().to_string(),
)),
_ => None,
};
if let Some(key) = type_key {
let user = self
.methods
.get(&key)
.and_then(|ms| ms.get(method))
.cloned();
if let Some(m) = user {
if m.params.len() != eval_args.len() + 1 {
return Err(error(
expr.line,
format!(
"`{}.{}` expects {} argument{} (including `self`), but got {}",
key.1,
method,
m.params.len(),
if m.params.len() == 1 { "" } else { "s" },
eval_args.len() + 1
),
));
}
let mut full_args = Vec::with_capacity(eval_args.len() + 1);
full_args.push(obj_val);
full_args.extend(eval_args);
let bop_fn = Rc::new(BopFn {
params: m.params,
captures: Vec::new(),
body: FnBody::Ast(m.body),
self_name: None,
});
return self.call_bop_fn(&bop_fn, full_args, expr.line);
}
}
if methods::is_builtin_result(&obj_val) {
if let Some(kind) = methods::is_result_callable_method(method) {
return self.call_result_callable_method(
&obj_val,
kind,
method,
eval_args,
expr.line,
);
}
}
let (ret, mutated) = self.call_method(&obj_val, method, &eval_args, expr.line)?;
if methods::is_mutating_method(method) {
if let ExprKind::Ident(name) = &object.kind {
if let Some(new_obj) = mutated {
self.set_var(name, new_obj);
}
}
}
Ok(ret)
}
ExprKind::Index { object, index } => {
let obj = self.eval_expr(object)?;
let idx = self.eval_expr(index)?;
ops::index_get(&obj, &idx, expr.line)
}
ExprKind::FieldAccess { object, field } => {
let obj = self.eval_expr(object)?;
match &obj {
Value::Struct(s) => s.field(field).cloned().ok_or_else(|| {
let msg =
crate::error_messages::struct_has_no_field(s.type_name(), field);
let field_names: Vec<&str> =
s.fields().iter().map(|(k, _)| k.as_str()).collect();
match crate::suggest::did_you_mean(field, field_names) {
Some(hint) => error_with_hint_at(expr.line, expr.column, msg, hint),
None => error_at(expr.line, expr.column, msg),
}
}),
Value::EnumVariant(e) => e.field(field).cloned().ok_or_else(|| {
error(
expr.line,
crate::error_messages::variant_has_no_field(
e.type_name(),
e.variant(),
field,
),
)
}),
Value::Module(m) => {
if let Some((_, v)) = m.bindings.iter().find(|(k, _)| k == field) {
return Ok(v.clone());
}
if m.types.iter().any(|t| t == field) {
return Err(error_with_hint(
expr.line,
format!("`{}` in `{}` is a type, not a value", field, m.path),
format!(
"construct through the alias: `{}.{} {{ ... }}` or `{}.{}::Variant(...)`",
m.path.split('.').last().unwrap_or(&m.path),
field,
m.path.split('.').last().unwrap_or(&m.path),
field,
),
));
}
Err(error(
expr.line,
format!(
"`{}` isn't exported from `{}`",
field, m.path
),
))
}
other => Err(error(
expr.line,
crate::error_messages::cant_read_field(field, other.type_name()),
)),
}
}
ExprKind::EnumConstruct {
namespace,
type_name,
variant,
payload,
} => self.eval_enum_construct(
namespace.as_deref(),
type_name,
variant,
payload,
expr.line,
),
ExprKind::StructConstruct {
namespace,
type_name,
fields,
} => {
let module_path = match namespace {
Some(ns) => {
self.validate_namespaced_type(ns, type_name, expr.line)?;
self.resolve_type_ref(Some(ns), type_name)
.unwrap_or_else(|| self.current_module.clone())
}
None => self
.resolve_type_ref(None, type_name)
.ok_or_else(|| {
error(
expr.line,
crate::error_messages::struct_not_declared(type_name),
)
})?,
};
let key = (module_path.clone(), type_name.clone());
let decl_fields = self.struct_defs.get(&key).ok_or_else(|| {
error(
expr.line,
crate::error_messages::struct_not_declared(type_name),
)
})?
.clone();
let mut seen = alloc_import::collections::BTreeSet::new();
let mut values: Vec<(String, Value)> =
Vec::with_capacity(decl_fields.len());
let mut provided: BTreeMap<String, Value> = BTreeMap::new();
for (fname, fexpr) in fields {
if !seen.insert(fname.clone()) {
return Err(error(
expr.line,
format!(
"Field `{}` specified twice in `{}` construction",
fname, type_name
),
));
}
if !decl_fields.iter().any(|d| d == fname) {
let msg = crate::error_messages::struct_has_no_field(type_name, fname);
let err = match crate::suggest::did_you_mean(
fname,
decl_fields.iter().map(|s| s.as_str()),
) {
Some(hint) => error_with_hint_at(expr.line, expr.column, msg, hint),
None => error_at(expr.line, expr.column, msg),
};
return Err(err);
}
provided.insert(fname.clone(), self.eval_expr(fexpr)?);
}
for decl in &decl_fields {
match provided.remove(decl) {
Some(v) => values.push((decl.clone(), v)),
None => {
return Err(error(
expr.line,
format!(
"Missing field `{}` in `{}` construction",
decl, type_name
),
));
}
}
}
Ok(Value::new_struct(module_path, type_name.clone(), values))
}
ExprKind::Array(elements) => {
let mut items = Vec::new();
for elem in elements {
items.push(self.eval_expr(elem)?);
}
Ok(Value::new_array(items))
}
ExprKind::Dict(entries) => {
let mut result = Vec::new();
for (key, value_expr) in entries {
let val = self.eval_expr(value_expr)?;
result.push((key.clone(), val));
}
Ok(Value::new_dict(result))
}
ExprKind::IfExpr {
condition,
then_expr,
else_expr,
} => {
if self.eval_expr(condition)?.is_truthy() {
self.eval_expr(then_expr)
} else {
self.eval_expr(else_expr)
}
}
ExprKind::Match { scrutinee, arms } => self.eval_match(scrutinee, arms, expr.line),
ExprKind::Try(inner) => self.eval_try(inner, expr.line),
}
}
fn eval_try(&mut self, inner: &Expr, line: u32) -> Result<Value, BopError> {
let value = self.eval_expr(inner)?;
if self.pending_try_return.is_some() {
return Ok(Value::None);
}
match &value {
Value::EnumVariant(ev) if ev.variant() == "Ok" => {
match ev.payload() {
EnumPayload::Tuple(items) if items.len() == 1 => Ok(items[0].clone()),
EnumPayload::Unit => Ok(Value::None),
EnumPayload::Tuple(items) => Err(error(
line,
format!(
"try: Ok variant must carry exactly one value, got {}",
items.len()
),
)),
EnumPayload::Struct(_) => Err(error(
line,
"try: Ok variant must carry a single positional value, not named fields",
)),
}
}
Value::EnumVariant(ev) if ev.variant() == "Err" => {
if self.call_depth == 0 {
return Err(error_with_hint(
line,
"try encountered Err at top-level",
"Wrap the calling code in a fn, or use `match` to handle both arms explicitly.",
));
}
self.pending_try_return = Some(value);
Err(BopError::try_return_signal(line))
}
other => Err(error(
line,
format!(
"try expected a Result-shaped value (Ok/Err variant), got {}",
other.type_name()
),
)),
}
}
fn eval_match(
&mut self,
scrutinee: &Expr,
arms: &[MatchArm],
line: u32,
) -> Result<Value, BopError> {
let value = self.eval_expr(scrutinee)?;
for arm in arms {
let mut bindings: Vec<(String, Value)> = Vec::new();
let scopes = &self.scopes;
let type_bindings = &self.type_bindings;
let module_aliases = &self.module_aliases;
let resolver = |ns: Option<&str>, tn: &str| -> Option<String> {
resolve_type_in(scopes, type_bindings, module_aliases, ns, tn)
};
if !pattern_matches(&arm.pattern, &value, &mut bindings, &resolver) {
continue;
}
self.push_scope();
for (name, v) in bindings {
self.define(name, v);
}
let guard_ok = match &arm.guard {
Some(g) => self.eval_expr(g)?.is_truthy(),
None => true,
};
if !guard_ok {
self.pop_scope();
continue;
}
let result = self.eval_expr(&arm.body);
self.pop_scope();
return result;
}
Err(error(
line,
"No match arm matched the scrutinee",
))
}
fn binary_op(
&self,
left: &Value,
op: &BinOp,
right: &Value,
line: u32,
) -> Result<Value, BopError> {
match op {
BinOp::Add => ops::add(left, right, line),
BinOp::Sub => ops::sub(left, right, line),
BinOp::Mul => ops::mul(left, right, line),
BinOp::Div => ops::div(left, right, line),
BinOp::Mod => ops::rem(left, right, line),
BinOp::Eq => Ok(ops::eq(left, right)),
BinOp::NotEq => Ok(ops::not_eq(left, right)),
BinOp::Lt => ops::lt(left, right, line),
BinOp::Gt => ops::gt(left, right, line),
BinOp::LtEq => ops::lt_eq(left, right, line),
BinOp::GtEq => ops::gt_eq(left, right, line),
BinOp::And | BinOp::Or => unreachable!("handled in eval_expr"),
}
}
fn snapshot_captures(&self) -> Vec<(String, Value)> {
let mut flat = BTreeMap::new();
for scope in &self.scopes {
for (k, v) in scope {
flat.insert(k.clone(), v.clone());
}
}
flat.into_iter().collect()
}
fn call_value(
&mut self,
callee: Value,
args: Vec<Value>,
line: u32,
name_hint: Option<&str>,
) -> Result<Value, BopError> {
match &callee {
Value::Fn(f) => {
let f = Rc::clone(f);
drop(callee);
self.call_bop_fn(&f, args, line)
}
other => match name_hint {
Some(n) => Err(error(
line,
format!(
"`{}` is a {}, not a function",
n,
other.type_name()
),
)),
None => Err(error(
line,
crate::error_messages::cant_call_a(other.type_name()),
)),
},
}
}
fn call_bop_fn(
&mut self,
func: &Rc<BopFn>,
args: Vec<Value>,
line: u32,
) -> Result<Value, BopError> {
let body = match &func.body {
FnBody::Ast(stmts) => stmts,
FnBody::Compiled(_) => {
return Err(error(
line,
"This function was compiled for another engine and can't be run in the tree-walker",
));
}
};
if args.len() != func.params.len() {
let name = func.self_name.as_deref().unwrap_or("fn");
return Err(error(
line,
format!(
"`{}` expects {} argument{}, but got {}",
name,
func.params.len(),
if func.params.len() == 1 { "" } else { "s" },
args.len()
),
));
}
if self.call_depth >= MAX_CALL_DEPTH {
return Err(error_with_hint(
line,
"Too many nested function calls (possible infinite recursion)",
"Check that your recursive function has a base case that stops calling itself.",
));
}
self.call_depth += 1;
let saved_scopes = core::mem::replace(&mut self.scopes, vec![BTreeMap::new()]);
self.type_bindings.push(BTreeMap::new());
for (name, value) in &func.captures {
self.define(name.clone(), value.clone());
}
if let Some(self_name) = &func.self_name {
self.define(self_name.clone(), Value::Fn(Rc::clone(func)));
}
for (param, arg) in func.params.iter().zip(args) {
self.define(param.clone(), arg);
}
let result = self.exec_block(body);
self.scopes = saved_scopes;
self.type_bindings.pop();
self.call_depth -= 1;
match result {
Ok(sig) => match sig {
Signal::Return(val) => Ok(val),
Signal::Break => Err(error(line, "break used outside of a loop")),
Signal::Continue => Err(error(line, "continue used outside of a loop")),
Signal::None => Ok(Value::None),
},
Err(err) => {
if err.is_try_return {
if let Some(val) = self.pending_try_return.take() {
return Ok(val);
}
}
Err(err)
}
}
}
fn builtin_try_call(
&mut self,
args: Vec<Value>,
line: u32,
) -> Result<Value, BopError> {
if args.len() != 1 {
return Err(error(
line,
format!(
"`try_call` expects 1 argument, but got {}",
args.len()
),
));
}
let callable = args.into_iter().next().unwrap();
let func = match &callable {
Value::Fn(f) => Rc::clone(f),
other => {
return Err(error(
line,
format!(
"`try_call` expects a function, got {}",
other.type_name()
),
));
}
};
drop(callable);
match self.call_bop_fn(&func, Vec::new(), line) {
Ok(value) => Ok(builtins::make_try_call_ok(value)),
Err(err) => {
if err.is_fatal {
Err(err)
} else {
Ok(builtins::make_try_call_err(&err))
}
}
}
}
fn call_function(
&mut self,
name: &str,
args: Vec<Value>,
line: u32,
) -> Result<Value, BopError> {
match name {
"range" => return builtins::builtin_range(&args, line, &mut self.rand_state),
"rand" => return builtins::builtin_rand(&args, line, &mut self.rand_state),
"print" => {
let message = args
.iter()
.map(|a| format!("{}", a))
.collect::<Vec<_>>()
.join(" ");
self.host.on_print(&message);
return Ok(Value::None);
}
"try_call" => return self.builtin_try_call(args, line),
"panic" => return builtins::builtin_panic(&args, line),
_ => {}
}
if let Some(result) = self.host.call(name, &args, line) {
return result;
}
let func = self.functions.get(name).cloned().ok_or_else(|| {
if let Some(hint) = self.callable_candidates_hint(name) {
error_with_hint(
line,
crate::error_messages::function_not_found(name),
hint,
)
} else {
let host_hint = self.host.function_hint();
if host_hint.is_empty() {
error(line, crate::error_messages::function_not_found(name))
} else {
error_with_hint(
line,
crate::error_messages::function_not_found(name),
host_hint,
)
}
}
})?;
let bop_fn = Rc::new(BopFn {
params: func.params,
captures: Vec::new(),
body: FnBody::Ast(func.body),
self_name: Some(name.to_string()),
});
self.call_bop_fn(&bop_fn, args, line)
}
fn call_method_full(
&mut self,
obj: &Value,
method: &str,
args: Vec<Value>,
line: u32,
) -> Result<Value, BopError> {
let type_key: Option<(String, String)> = match obj {
Value::Struct(s) => Some((
s.module_path().to_string(),
s.type_name().to_string(),
)),
Value::EnumVariant(e) => Some((
e.module_path().to_string(),
e.type_name().to_string(),
)),
_ => None,
};
if let Some(key) = type_key {
let user = self
.methods
.get(&key)
.and_then(|ms| ms.get(method))
.cloned();
if let Some(m) = user {
if m.params.len() != args.len() + 1 {
return Err(error(
line,
format!(
"`{}.{}` expects {} argument{} (including `self`), but got {}",
key.1,
method,
m.params.len(),
if m.params.len() == 1 { "" } else { "s" },
args.len() + 1
),
));
}
let mut full_args = Vec::with_capacity(args.len() + 1);
full_args.push(obj.clone());
full_args.extend(args);
let bop_fn = Rc::new(BopFn {
params: m.params,
captures: Vec::new(),
body: FnBody::Ast(m.body),
self_name: None,
});
return self.call_bop_fn(&bop_fn, full_args, line);
}
}
let (ret, _mutated) = self.call_method(obj, method, &args, line)?;
Ok(ret)
}
fn call_method(
&self,
obj: &Value,
method: &str,
args: &[Value],
line: u32,
) -> Result<(Value, Option<Value>), BopError> {
if let Some(result) = methods::common_method(obj, method, args, line)? {
return Ok(result);
}
if methods::is_builtin_result(obj) {
if let Some(v) = methods::result_method(obj, method, args, line)? {
return Ok((v, None));
}
}
match obj {
Value::Array(arr) => methods::array_method(arr, method, args, line),
Value::Str(s) => methods::string_method(s, method, args, line),
Value::Dict(entries) => methods::dict_method(entries, method, args, line),
Value::Int(_) | Value::Number(_) => {
methods::numeric_method(obj, method, args, line)
}
Value::Bool(_) => methods::bool_method(obj, method, args, line),
Value::Iter(_) => methods::iter_method(obj, method, args, line),
_ => Err(error(
line,
crate::error_messages::no_such_method(obj.type_name(), method),
)),
}
}
fn call_result_callable_method(
&mut self,
receiver: &Value,
kind: methods::ResultCallableKind,
method: &str,
args: Vec<Value>,
line: u32,
) -> Result<Value, BopError> {
use crate::builtins::expect_args;
use methods::{make_result_err, make_result_ok, ResultCallableKind};
expect_args(method, &args, 1, line)?;
let callable = args.into_iter().next().expect("expect_args ensured len = 1");
let variant_info = match receiver {
Value::EnumVariant(e) => e,
_ => return Err(error(line, "Result method called on non-Result")),
};
let payload = match variant_info.payload() {
crate::value::EnumPayload::Tuple(items) if items.len() == 1 => items[0].clone(),
_ => {
return Err(error(
line,
format!("malformed Result::{} payload", variant_info.variant()),
));
}
};
let is_ok = variant_info.variant() == "Ok";
match kind {
ResultCallableKind::Map => {
if is_ok {
let new_value = self.call_value(callable, vec![payload], line, Some(method))?;
Ok(make_result_ok(new_value))
} else {
Ok(make_result_err(payload))
}
}
ResultCallableKind::MapErr => {
if !is_ok {
let new_value = self.call_value(callable, vec![payload], line, Some(method))?;
Ok(make_result_err(new_value))
} else {
Ok(make_result_ok(payload))
}
}
ResultCallableKind::AndThen => {
if is_ok {
self.call_value(callable, vec![payload], line, Some(method))
} else {
Ok(make_result_err(payload))
}
}
}
}
}
enum IterStep {
Next(Value),
Done,
}
fn unwrap_iter_result(v: &Value) -> Option<IterStep> {
let e = match v {
Value::EnumVariant(e) => e,
_ => return None,
};
if e.type_name() != "Iter" {
return None;
}
match (e.variant(), e.payload()) {
("Next", crate::value::EnumPayload::Tuple(items)) if items.len() == 1 => {
Some(IterStep::Next(items[0].clone()))
}
("Done", crate::value::EnumPayload::Unit) => Some(IterStep::Done),
_ => None,
}
}
fn seed_builtin_types() -> (
BTreeMap<(String, String), Vec<String>>,
BTreeMap<(String, String), Vec<VariantDecl>>,
BTreeMap<String, String>,
) {
use crate::value::BUILTIN_MODULE_PATH;
let mut struct_defs: BTreeMap<(String, String), Vec<String>> = BTreeMap::new();
let mut enum_defs: BTreeMap<(String, String), Vec<VariantDecl>> = BTreeMap::new();
let mut type_bindings: BTreeMap<String, String> = BTreeMap::new();
struct_defs.insert(
(String::from(BUILTIN_MODULE_PATH), String::from("RuntimeError")),
crate::builtins::builtin_runtime_error_fields(),
);
type_bindings.insert(
String::from("RuntimeError"),
String::from(BUILTIN_MODULE_PATH),
);
enum_defs.insert(
(String::from(BUILTIN_MODULE_PATH), String::from("Result")),
crate::builtins::builtin_result_variants(),
);
type_bindings.insert(
String::from("Result"),
String::from(BUILTIN_MODULE_PATH),
);
enum_defs.insert(
(String::from(BUILTIN_MODULE_PATH), String::from("Iter")),
crate::builtins::builtin_iter_variants(),
);
type_bindings.insert(
String::from("Iter"),
String::from(BUILTIN_MODULE_PATH),
);
(struct_defs, enum_defs, type_bindings)
}
fn variants_equivalent(a: &[VariantDecl], b: &[VariantDecl]) -> bool {
if a.len() != b.len() {
return false;
}
for (va, vb) in a.iter().zip(b.iter()) {
if va.name != vb.name {
return false;
}
match (&va.kind, &vb.kind) {
(VariantKind::Unit, VariantKind::Unit) => {}
(VariantKind::Tuple(fa), VariantKind::Tuple(fb)) => {
if fa.len() != fb.len() {
return false;
}
}
(VariantKind::Struct(fa), VariantKind::Struct(fb)) => {
if fa != fb {
return false;
}
}
_ => return false,
}
}
true
}
pub fn resolve_type_in(
value_scopes: &[BTreeMap<String, Value>],
type_scopes: &[BTreeMap<String, String>],
module_aliases: &BTreeMap<String, Rc<crate::value::BopModule>>,
namespace: Option<&str>,
type_name: &str,
) -> Option<String> {
if let Some(ns) = namespace {
for scope in value_scopes.iter().rev() {
if let Some(Value::Module(m)) = scope.get(ns) {
if m.types.iter().any(|t| t == type_name) {
return Some(m.path.clone());
}
return None;
}
}
if let Some(m) = module_aliases.get(ns) {
if m.types.iter().any(|t| t == type_name) {
return Some(m.path.clone());
}
}
return None;
}
for scope in type_scopes.iter().rev() {
if let Some(mp) = scope.get(type_name) {
return Some(mp.clone());
}
}
None
}
pub type TypeResolveFn<'a> = &'a dyn Fn(Option<&str>, &str) -> Option<String>;
pub fn pattern_matches(
pattern: &Pattern,
value: &Value,
bindings: &mut Vec<(String, Value)>,
resolver: TypeResolveFn<'_>,
) -> bool {
match pattern {
Pattern::Wildcard => true,
Pattern::Binding(name) => {
bindings.push((name.clone(), value.clone()));
true
}
Pattern::Literal(lit) => match (lit, value) {
(LiteralPattern::Int(a), Value::Int(b)) => a == b,
(LiteralPattern::Number(a), Value::Number(b)) => a == b,
(LiteralPattern::Int(a), Value::Number(b)) => (*a as f64) == *b,
(LiteralPattern::Number(a), Value::Int(b)) => *a == (*b as f64),
(LiteralPattern::Str(a), Value::Str(b)) => a.as_str() == b.as_str(),
(LiteralPattern::Bool(a), Value::Bool(b)) => a == b,
(LiteralPattern::None, Value::None) => true,
_ => false,
},
Pattern::EnumVariant {
namespace,
type_name,
variant,
payload,
} => {
let ev = match value {
Value::EnumVariant(e) => e,
_ => return false,
};
let expected_mp = match resolver(namespace.as_deref(), type_name) {
Some(mp) => mp,
None => return false,
};
if ev.module_path() != expected_mp.as_str()
|| ev.type_name() != type_name.as_str()
|| ev.variant() != variant.as_str()
{
return false;
}
match (payload, ev.payload()) {
(VariantPatternPayload::Unit, crate::value::EnumPayload::Unit) => true,
(
VariantPatternPayload::Tuple(pats),
crate::value::EnumPayload::Tuple(items),
) => {
if pats.len() != items.len() {
return false;
}
for (p, v) in pats.iter().zip(items.iter()) {
if !pattern_matches(p, v, bindings, resolver) {
return false;
}
}
true
}
(
VariantPatternPayload::Struct { fields, rest },
crate::value::EnumPayload::Struct(entries),
) => {
match_struct_fields(fields, *rest, entries, bindings, resolver)
}
_ => false,
}
}
Pattern::Struct {
namespace,
type_name,
fields,
rest,
} => {
let st = match value {
Value::Struct(s) => s,
_ => return false,
};
let expected_mp = match resolver(namespace.as_deref(), type_name) {
Some(mp) => mp,
None => return false,
};
if st.module_path() != expected_mp.as_str()
|| st.type_name() != type_name.as_str()
{
return false;
}
match_struct_fields(fields, *rest, st.fields(), bindings, resolver)
}
Pattern::Array { elements, rest } => {
let items = match value {
Value::Array(arr) => arr,
_ => return false,
};
match rest {
None => {
if elements.len() != items.len() {
return false;
}
for (p, v) in elements.iter().zip(items.iter()) {
if !pattern_matches(p, v, bindings, resolver) {
return false;
}
}
true
}
Some(rest_kind) => {
if items.len() < elements.len() {
return false;
}
for (i, p) in elements.iter().enumerate() {
if !pattern_matches(p, &items[i], bindings, resolver) {
return false;
}
}
if let ArrayRest::Named(name) = rest_kind {
let tail: Vec<Value> =
items[elements.len()..].iter().cloned().collect();
bindings.push((name.clone(), Value::new_array(tail)));
}
true
}
}
}
Pattern::Or(alts) => {
for alt in alts {
let mut attempt: Vec<(String, Value)> = Vec::new();
if pattern_matches(alt, value, &mut attempt, resolver) {
bindings.extend(attempt);
return true;
}
}
false
}
}
}
fn match_struct_fields(
fields: &[(String, Pattern)],
rest: bool,
entries: &[(String, Value)],
bindings: &mut Vec<(String, Value)>,
resolver: TypeResolveFn<'_>,
) -> bool {
let _ = rest;
for (fname, pat) in fields {
let value = match entries.iter().find(|(k, _)| k == fname) {
Some((_, v)) => v,
None => return false,
};
if !pattern_matches(pat, value, bindings, resolver) {
return false;
}
}
true
}
pub struct ReplSession {
scopes: Vec<BTreeMap<String, Value>>,
functions: BTreeMap<String, FnDef>,
current_module: String,
struct_defs: BTreeMap<(String, String), Vec<String>>,
enum_defs: BTreeMap<(String, String), Vec<VariantDecl>>,
methods: BTreeMap<(String, String), BTreeMap<String, FnDef>>,
type_bindings: Vec<BTreeMap<String, String>>,
module_aliases: BTreeMap<String, Rc<crate::value::BopModule>>,
imports: ImportCache,
imported_here: alloc_import::collections::BTreeSet<String>,
rand_state: u64,
}
impl Default for ReplSession {
fn default() -> Self {
Self::new()
}
}
impl ReplSession {
pub fn new() -> Self {
let (struct_defs, enum_defs, builtin_bindings) = seed_builtin_types();
Self {
scopes: vec![BTreeMap::new()],
functions: BTreeMap::new(),
current_module: String::from(crate::value::ROOT_MODULE_PATH),
struct_defs,
enum_defs,
methods: BTreeMap::new(),
type_bindings: vec![builtin_bindings],
module_aliases: BTreeMap::new(),
imports: Rc::new(RefCell::new(
alloc_import::collections::BTreeMap::new(),
)),
imported_here: alloc_import::collections::BTreeSet::new(),
rand_state: 0,
}
}
pub fn get(&self, name: &str) -> Option<Value> {
for scope in self.scopes.iter().rev() {
if let Some(v) = scope.get(name) {
return Some(v.clone());
}
}
None
}
pub fn binding_names(&self) -> Vec<String> {
let mut names: Vec<String> = self
.scopes
.first()
.map(|s| s.keys().cloned().collect())
.unwrap_or_default();
for name in self.functions.keys() {
names.push(name.clone());
}
names.sort();
names.dedup();
names
}
pub fn eval<H: BopHost>(
&mut self,
source: &str,
host: &mut H,
limits: &BopLimits,
) -> Result<Option<Value>, BopError> {
let stmts = crate::parse(source)?;
self.run_stmts(&stmts, host, limits)
}
pub fn run_stmts<H: BopHost>(
&mut self,
stmts: &[Stmt],
host: &mut H,
limits: &BopLimits,
) -> Result<Option<Value>, BopError> {
let (tail_expr, body) = match stmts.last().map(|s| &s.kind) {
Some(StmtKind::ExprStmt(_)) => {
let (last, rest) = stmts.split_last().unwrap();
let expr = match &last.kind {
StmtKind::ExprStmt(e) => e.clone(),
_ => unreachable!(),
};
(Some(expr), rest)
}
_ => (None, stmts),
};
let mut eval = self.take_evaluator(host, limits.clone());
let result: Result<Option<Value>, BopError> = (|| {
let sig = eval.exec_block(body)?;
match sig {
Signal::Break => return Err(error(0, "break used outside of a loop")),
Signal::Continue => {
return Err(error(0, "continue used outside of a loop"));
}
_ => {}
}
match tail_expr {
Some(expr) => Ok(Some(eval.eval_expr(&expr)?)),
None => Ok(None),
}
})();
#[cfg(not(feature = "no_std"))]
{
for w in &eval.runtime_warnings {
eprintln!("warning: {}", w.message);
}
}
self.put_evaluator(eval);
result
}
fn take_evaluator<'h, H: BopHost>(
&mut self,
host: &'h mut H,
limits: BopLimits,
) -> Evaluator<'h, H> {
Evaluator {
scopes: core::mem::take(&mut self.scopes),
functions: core::mem::take(&mut self.functions),
current_module: core::mem::take(&mut self.current_module),
struct_defs: core::mem::take(&mut self.struct_defs),
enum_defs: core::mem::take(&mut self.enum_defs),
methods: core::mem::take(&mut self.methods),
type_bindings: core::mem::take(&mut self.type_bindings),
module_aliases: core::mem::take(&mut self.module_aliases),
imports: Rc::clone(&self.imports),
imported_here: core::mem::take(&mut self.imported_here),
rand_state: self.rand_state,
host,
steps: 0,
call_depth: 0,
limits,
pending_try_return: None,
runtime_warnings: Vec::new(),
}
}
fn put_evaluator<'h, H: BopHost>(&mut self, eval: Evaluator<'h, H>) {
let mut scopes = eval.scopes;
if scopes.len() > 1 {
scopes.truncate(1);
}
if scopes.is_empty() {
scopes.push(BTreeMap::new());
}
let mut type_bindings = eval.type_bindings;
if type_bindings.len() > 1 {
type_bindings.truncate(1);
}
if type_bindings.is_empty() {
let (_, _, builtins) = seed_builtin_types();
type_bindings.push(builtins);
}
self.scopes = scopes;
self.functions = eval.functions;
self.current_module = eval.current_module;
self.struct_defs = eval.struct_defs;
self.enum_defs = eval.enum_defs;
self.methods = eval.methods;
self.type_bindings = type_bindings;
self.module_aliases = eval.module_aliases;
self.imported_here = eval.imported_here;
self.rand_state = eval.rand_state;
}
}