Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
314 changes: 314 additions & 0 deletions aiscript-vm/src/builtins/array.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,314 @@
use aiscript_arena::Mutation;
use std::collections::HashMap;

use crate::string::InternedString;
use crate::{BuiltinMethod, Value, VmError, float_arg, vm::Context};

pub(crate) fn define_array_methods(ctx: Context) -> HashMap<InternedString, BuiltinMethod> {
[
// Basic operations
("append", BuiltinMethod(append)),
("extend", BuiltinMethod(extend)),
("insert", BuiltinMethod(insert)),
("remove", BuiltinMethod(remove)),
("pop", BuiltinMethod(pop)),
("clear", BuiltinMethod(clear)),
// Search operations
("index", BuiltinMethod(index)),
("count", BuiltinMethod(count)),
// Ordering operations
("sort", BuiltinMethod(sort)),
("reverse", BuiltinMethod(reverse)),
]
.into_iter()
.map(|(name, f)| (ctx.intern_static(name), f))
.collect()
}

// Add an item to the end of the list
fn append<'gc>(
_mc: &'gc Mutation<'gc>,
receiver: Value<'gc>,
args: Vec<Value<'gc>>,
) -> Result<Value<'gc>, VmError> {
let list = receiver.as_array()?;

if args.is_empty() {
return Err(VmError::RuntimeError("append: expected 1 argument".into()));
}

let value = args[0];
list.borrow_mut(_mc).push(value);

// Return the modified list for method chaining
Ok(receiver)
}

// Extend the list by appending all items from another list
fn extend<'gc>(
mc: &'gc Mutation<'gc>,
receiver: Value<'gc>,
args: Vec<Value<'gc>>,
) -> Result<Value<'gc>, VmError> {
let list = receiver.as_array()?;

if args.is_empty() {
return Err(VmError::RuntimeError(
"extend: expected 1 array argument".into(),
));
}

match &args[0] {
Value::List(other_list) => {
let items = &other_list.borrow().data;
let mut list_mut = list.borrow_mut(mc);
for item in items {
list_mut.push(*item);
}
Ok(receiver)
}
_ => Err(VmError::RuntimeError(
"extend: argument must be an array".into(),
)),
}
}

// Insert an item at a given position
fn insert<'gc>(
mc: &'gc Mutation<'gc>,
receiver: Value<'gc>,
args: Vec<Value<'gc>>,
) -> Result<Value<'gc>, VmError> {
let list = receiver.as_array()?;

if args.len() < 2 {
return Err(VmError::RuntimeError(
"insert: expected 2 arguments (index, value)".into(),
));
}

let index = float_arg!(&args, 0, "insert")? as usize;
let value = args[1];

let mut list_mut = list.borrow_mut(mc);

// Check if index is valid
if index > list_mut.data.len() {
return Err(VmError::RuntimeError(format!(
"insert: index {} out of range",
index
)));
}

// Insert the value at the specified position
list_mut.data.insert(index, value);

Ok(receiver)
}

// Remove the first item from the list whose value is equal to x
fn remove<'gc>(
mc: &'gc Mutation<'gc>,
receiver: Value<'gc>,
args: Vec<Value<'gc>>,
) -> Result<Value<'gc>, VmError> {
let list = receiver.as_array()?;

if args.is_empty() {
return Err(VmError::RuntimeError("remove: expected 1 argument".into()));
}

let value_to_remove = &args[0];

let mut list_mut = list.borrow_mut(mc);
if let Some(index) = list_mut
.data
.iter()
.position(|item| item.equals(value_to_remove))
{
list_mut.data.remove(index);
Ok(receiver)
} else {
Err(VmError::RuntimeError(format!(
"remove: value {} not found in list",
value_to_remove
)))
}
}

// Remove the item at the given position and return it
fn pop<'gc>(
mc: &'gc Mutation<'gc>,
receiver: Value<'gc>,
args: Vec<Value<'gc>>,
) -> Result<Value<'gc>, VmError> {
let list = receiver.as_array()?;
let mut list_mut = list.borrow_mut(mc);

// If list is empty, return an error
if list_mut.data.is_empty() {
return Err(VmError::RuntimeError(
"pop: cannot pop from empty list".into(),
));
}

let index = if args.is_empty() {
// Default to the last element if no index is provided
list_mut.data.len() - 1
} else {
float_arg!(&args, 0, "pop")? as usize
};

// Check if index is valid
if index >= list_mut.data.len() {
return Err(VmError::RuntimeError(format!(
"pop: index {} out of range",
index
)));
}

// Remove and return the value at the specified position
Ok(list_mut.data.remove(index))
}

// Remove all items from the list
fn clear<'gc>(
mc: &'gc Mutation<'gc>,
receiver: Value<'gc>,
_args: Vec<Value<'gc>>,
) -> Result<Value<'gc>, VmError> {
let list = receiver.as_array()?;

let mut list_mut = list.borrow_mut(mc);
list_mut.data.clear();

Ok(receiver)
}

// Return zero-based index of the first item with value equal to x
fn index<'gc>(
_mc: &'gc Mutation<'gc>,
receiver: Value<'gc>,
args: Vec<Value<'gc>>,
) -> Result<Value<'gc>, VmError> {
let list = receiver.as_array()?;

if args.is_empty() {
return Err(VmError::RuntimeError(
"index: expected at least 1 argument".into(),
));
}

let value_to_find = &args[0];

// Get optional start and end parameters
let start: usize = if args.len() > 1 {
float_arg!(&args, 1, "index")? as usize
} else {
0
};

let end: usize = if args.len() > 2 {
float_arg!(&args, 2, "index")? as usize
} else {
list.borrow().data.len()
};

// Validate start and end
let list_len = list.borrow().data.len();
let start = start.min(list_len);
let end = end.min(list_len);

// Search for the value in the specified range
for (i, item) in list.borrow().data[start..end].iter().enumerate() {
if item.equals(value_to_find) {
// Return relative to original list
return Ok(Value::Number((i + start) as f64));
}
}

Err(VmError::RuntimeError(format!(
"index: value {} not found in list",
value_to_find
)))
}

// Return the number of times x appears in the list
fn count<'gc>(
_mc: &'gc Mutation<'gc>,
receiver: Value<'gc>,
args: Vec<Value<'gc>>,
) -> Result<Value<'gc>, VmError> {
let list = receiver.as_array()?;

if args.is_empty() {
return Err(VmError::RuntimeError("count: expected 1 argument".into()));
}

let value_to_count = &args[0];

let count = list
.borrow()
.data
.iter()
.filter(|item| item.equals(value_to_count))
.count();

Ok(Value::Number(count as f64))
}

// Sort the items of the list in place
fn sort<'gc>(
mc: &'gc Mutation<'gc>,
receiver: Value<'gc>,
args: Vec<Value<'gc>>,
) -> Result<Value<'gc>, VmError> {
let list = receiver.as_array()?;

// Check for optional reverse parameter
let reverse = if !args.is_empty() {
args[0].as_boolean()
} else {
false
};

let mut list_mut = list.borrow_mut(mc);

// Currently only supporting numeric sorts
// This could be expanded to support custom comparators
if reverse {
list_mut.data.sort_by(|a, b| {
if let (Value::Number(x), Value::Number(y)) = (a, b) {
y.partial_cmp(x).unwrap()
} else {
// For non-numeric values, just keep their order
std::cmp::Ordering::Equal
}
});
} else {
list_mut.data.sort_by(|a, b| {
if let (Value::Number(x), Value::Number(y)) = (a, b) {
x.partial_cmp(y).unwrap()
} else {
// For non-numeric values, just keep their order
std::cmp::Ordering::Equal
}
});
}

Ok(receiver)
}

// Reverse the elements of the list in place
fn reverse<'gc>(
mc: &'gc Mutation<'gc>,
receiver: Value<'gc>,
_args: Vec<Value<'gc>>,
) -> Result<Value<'gc>, VmError> {
let list = receiver.as_array()?;

let mut list_mut = list.borrow_mut(mc);
list_mut.data.reverse();

Ok(receiver)
}
21 changes: 21 additions & 0 deletions aiscript-vm/src/builtins/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use std::{
io::{self, Write},
};

mod array;
mod convert;
mod error;
mod format;
Expand All @@ -28,6 +29,7 @@ use print::print;
#[collect(no_drop)]
pub(crate) struct BuiltinMethods<'gc> {
string: HashMap<InternedString<'gc>, BuiltinMethod<'gc>>,
array: HashMap<InternedString<'gc>, BuiltinMethod<'gc>>,
}

impl Default for BuiltinMethods<'_> {
Expand All @@ -40,11 +42,13 @@ impl<'gc> BuiltinMethods<'gc> {
pub fn new() -> Self {
BuiltinMethods {
string: HashMap::default(),
array: HashMap::default(),
}
}

pub fn init(&mut self, ctx: Context<'gc>) {
self.string = string::define_string_methods(ctx);
self.array = array::define_array_methods(ctx);
}

pub fn invoke_string_method(
Expand All @@ -63,6 +67,23 @@ impl<'gc> BuiltinMethods<'gc> {
)))
}
}

pub fn invoke_array_method(
&self,
mc: &'gc Mutation<'gc>,
name: InternedString<'gc>,
receiver: Value<'gc>,
args: Vec<Value<'gc>>,
) -> Result<Value<'gc>, VmError> {
if let Some(f) = self.array.get(&name) {
f(mc, receiver, args)
} else {
Err(VmError::RuntimeError(format!(
"Unknown array method: {}",
name
)))
}
}
}

pub(crate) fn define_builtin_functions(state: &mut State) {
Expand Down
20 changes: 20 additions & 0 deletions aiscript-vm/src/vm/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1413,6 +1413,26 @@ impl<'gc> State<'gc> {
self.push_stack(result);
Ok(())
}
Value::List(_) => {
// Array method handling
let mut args = Vec::new();

// Collect arguments
for _ in 0..args_count {
args.push(self.pop_stack());
}
args.reverse(); // Restore argument order

// Pop the receiver and keyword args
self.stack_top -= keyword_args_count as usize * 2 + 1;

// Dispatch to array method
let result = self
.builtin_methods
.invoke_array_method(self.mc, name, receiver, args)?;
self.push_stack(result);
Ok(())
}
Value::Class(class) => {
if let Some(value) = class.borrow().static_methods.get(&name) {
self.call_value(*value, args_count, keyword_args_count)
Expand Down
Loading
Loading