Skip to content

Commit 7979470

Browse files
authored
Add builtin array methods (#22)
1 parent c93bcbe commit 7979470

File tree

5 files changed

+450
-3
lines changed

5 files changed

+450
-3
lines changed

aiscript-vm/src/builtins/array.rs

Lines changed: 314 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,314 @@
1+
use aiscript_arena::Mutation;
2+
use std::collections::HashMap;
3+
4+
use crate::string::InternedString;
5+
use crate::{BuiltinMethod, Value, VmError, float_arg, vm::Context};
6+
7+
pub(crate) fn define_array_methods(ctx: Context) -> HashMap<InternedString, BuiltinMethod> {
8+
[
9+
// Basic operations
10+
("append", BuiltinMethod(append)),
11+
("extend", BuiltinMethod(extend)),
12+
("insert", BuiltinMethod(insert)),
13+
("remove", BuiltinMethod(remove)),
14+
("pop", BuiltinMethod(pop)),
15+
("clear", BuiltinMethod(clear)),
16+
// Search operations
17+
("index", BuiltinMethod(index)),
18+
("count", BuiltinMethod(count)),
19+
// Ordering operations
20+
("sort", BuiltinMethod(sort)),
21+
("reverse", BuiltinMethod(reverse)),
22+
]
23+
.into_iter()
24+
.map(|(name, f)| (ctx.intern_static(name), f))
25+
.collect()
26+
}
27+
28+
// Add an item to the end of the list
29+
fn append<'gc>(
30+
_mc: &'gc Mutation<'gc>,
31+
receiver: Value<'gc>,
32+
args: Vec<Value<'gc>>,
33+
) -> Result<Value<'gc>, VmError> {
34+
let list = receiver.as_array()?;
35+
36+
if args.is_empty() {
37+
return Err(VmError::RuntimeError("append: expected 1 argument".into()));
38+
}
39+
40+
let value = args[0];
41+
list.borrow_mut(_mc).push(value);
42+
43+
// Return the modified list for method chaining
44+
Ok(receiver)
45+
}
46+
47+
// Extend the list by appending all items from another list
48+
fn extend<'gc>(
49+
mc: &'gc Mutation<'gc>,
50+
receiver: Value<'gc>,
51+
args: Vec<Value<'gc>>,
52+
) -> Result<Value<'gc>, VmError> {
53+
let list = receiver.as_array()?;
54+
55+
if args.is_empty() {
56+
return Err(VmError::RuntimeError(
57+
"extend: expected 1 array argument".into(),
58+
));
59+
}
60+
61+
match &args[0] {
62+
Value::List(other_list) => {
63+
let items = &other_list.borrow().data;
64+
let mut list_mut = list.borrow_mut(mc);
65+
for item in items {
66+
list_mut.push(*item);
67+
}
68+
Ok(receiver)
69+
}
70+
_ => Err(VmError::RuntimeError(
71+
"extend: argument must be an array".into(),
72+
)),
73+
}
74+
}
75+
76+
// Insert an item at a given position
77+
fn insert<'gc>(
78+
mc: &'gc Mutation<'gc>,
79+
receiver: Value<'gc>,
80+
args: Vec<Value<'gc>>,
81+
) -> Result<Value<'gc>, VmError> {
82+
let list = receiver.as_array()?;
83+
84+
if args.len() < 2 {
85+
return Err(VmError::RuntimeError(
86+
"insert: expected 2 arguments (index, value)".into(),
87+
));
88+
}
89+
90+
let index = float_arg!(&args, 0, "insert")? as usize;
91+
let value = args[1];
92+
93+
let mut list_mut = list.borrow_mut(mc);
94+
95+
// Check if index is valid
96+
if index > list_mut.data.len() {
97+
return Err(VmError::RuntimeError(format!(
98+
"insert: index {} out of range",
99+
index
100+
)));
101+
}
102+
103+
// Insert the value at the specified position
104+
list_mut.data.insert(index, value);
105+
106+
Ok(receiver)
107+
}
108+
109+
// Remove the first item from the list whose value is equal to x
110+
fn remove<'gc>(
111+
mc: &'gc Mutation<'gc>,
112+
receiver: Value<'gc>,
113+
args: Vec<Value<'gc>>,
114+
) -> Result<Value<'gc>, VmError> {
115+
let list = receiver.as_array()?;
116+
117+
if args.is_empty() {
118+
return Err(VmError::RuntimeError("remove: expected 1 argument".into()));
119+
}
120+
121+
let value_to_remove = &args[0];
122+
123+
let mut list_mut = list.borrow_mut(mc);
124+
if let Some(index) = list_mut
125+
.data
126+
.iter()
127+
.position(|item| item.equals(value_to_remove))
128+
{
129+
list_mut.data.remove(index);
130+
Ok(receiver)
131+
} else {
132+
Err(VmError::RuntimeError(format!(
133+
"remove: value {} not found in list",
134+
value_to_remove
135+
)))
136+
}
137+
}
138+
139+
// Remove the item at the given position and return it
140+
fn pop<'gc>(
141+
mc: &'gc Mutation<'gc>,
142+
receiver: Value<'gc>,
143+
args: Vec<Value<'gc>>,
144+
) -> Result<Value<'gc>, VmError> {
145+
let list = receiver.as_array()?;
146+
let mut list_mut = list.borrow_mut(mc);
147+
148+
// If list is empty, return an error
149+
if list_mut.data.is_empty() {
150+
return Err(VmError::RuntimeError(
151+
"pop: cannot pop from empty list".into(),
152+
));
153+
}
154+
155+
let index = if args.is_empty() {
156+
// Default to the last element if no index is provided
157+
list_mut.data.len() - 1
158+
} else {
159+
float_arg!(&args, 0, "pop")? as usize
160+
};
161+
162+
// Check if index is valid
163+
if index >= list_mut.data.len() {
164+
return Err(VmError::RuntimeError(format!(
165+
"pop: index {} out of range",
166+
index
167+
)));
168+
}
169+
170+
// Remove and return the value at the specified position
171+
Ok(list_mut.data.remove(index))
172+
}
173+
174+
// Remove all items from the list
175+
fn clear<'gc>(
176+
mc: &'gc Mutation<'gc>,
177+
receiver: Value<'gc>,
178+
_args: Vec<Value<'gc>>,
179+
) -> Result<Value<'gc>, VmError> {
180+
let list = receiver.as_array()?;
181+
182+
let mut list_mut = list.borrow_mut(mc);
183+
list_mut.data.clear();
184+
185+
Ok(receiver)
186+
}
187+
188+
// Return zero-based index of the first item with value equal to x
189+
fn index<'gc>(
190+
_mc: &'gc Mutation<'gc>,
191+
receiver: Value<'gc>,
192+
args: Vec<Value<'gc>>,
193+
) -> Result<Value<'gc>, VmError> {
194+
let list = receiver.as_array()?;
195+
196+
if args.is_empty() {
197+
return Err(VmError::RuntimeError(
198+
"index: expected at least 1 argument".into(),
199+
));
200+
}
201+
202+
let value_to_find = &args[0];
203+
204+
// Get optional start and end parameters
205+
let start: usize = if args.len() > 1 {
206+
float_arg!(&args, 1, "index")? as usize
207+
} else {
208+
0
209+
};
210+
211+
let end: usize = if args.len() > 2 {
212+
float_arg!(&args, 2, "index")? as usize
213+
} else {
214+
list.borrow().data.len()
215+
};
216+
217+
// Validate start and end
218+
let list_len = list.borrow().data.len();
219+
let start = start.min(list_len);
220+
let end = end.min(list_len);
221+
222+
// Search for the value in the specified range
223+
for (i, item) in list.borrow().data[start..end].iter().enumerate() {
224+
if item.equals(value_to_find) {
225+
// Return relative to original list
226+
return Ok(Value::Number((i + start) as f64));
227+
}
228+
}
229+
230+
Err(VmError::RuntimeError(format!(
231+
"index: value {} not found in list",
232+
value_to_find
233+
)))
234+
}
235+
236+
// Return the number of times x appears in the list
237+
fn count<'gc>(
238+
_mc: &'gc Mutation<'gc>,
239+
receiver: Value<'gc>,
240+
args: Vec<Value<'gc>>,
241+
) -> Result<Value<'gc>, VmError> {
242+
let list = receiver.as_array()?;
243+
244+
if args.is_empty() {
245+
return Err(VmError::RuntimeError("count: expected 1 argument".into()));
246+
}
247+
248+
let value_to_count = &args[0];
249+
250+
let count = list
251+
.borrow()
252+
.data
253+
.iter()
254+
.filter(|item| item.equals(value_to_count))
255+
.count();
256+
257+
Ok(Value::Number(count as f64))
258+
}
259+
260+
// Sort the items of the list in place
261+
fn sort<'gc>(
262+
mc: &'gc Mutation<'gc>,
263+
receiver: Value<'gc>,
264+
args: Vec<Value<'gc>>,
265+
) -> Result<Value<'gc>, VmError> {
266+
let list = receiver.as_array()?;
267+
268+
// Check for optional reverse parameter
269+
let reverse = if !args.is_empty() {
270+
args[0].as_boolean()
271+
} else {
272+
false
273+
};
274+
275+
let mut list_mut = list.borrow_mut(mc);
276+
277+
// Currently only supporting numeric sorts
278+
// This could be expanded to support custom comparators
279+
if reverse {
280+
list_mut.data.sort_by(|a, b| {
281+
if let (Value::Number(x), Value::Number(y)) = (a, b) {
282+
y.partial_cmp(x).unwrap()
283+
} else {
284+
// For non-numeric values, just keep their order
285+
std::cmp::Ordering::Equal
286+
}
287+
});
288+
} else {
289+
list_mut.data.sort_by(|a, b| {
290+
if let (Value::Number(x), Value::Number(y)) = (a, b) {
291+
x.partial_cmp(y).unwrap()
292+
} else {
293+
// For non-numeric values, just keep their order
294+
std::cmp::Ordering::Equal
295+
}
296+
});
297+
}
298+
299+
Ok(receiver)
300+
}
301+
302+
// Reverse the elements of the list in place
303+
fn reverse<'gc>(
304+
mc: &'gc Mutation<'gc>,
305+
receiver: Value<'gc>,
306+
_args: Vec<Value<'gc>>,
307+
) -> Result<Value<'gc>, VmError> {
308+
let list = receiver.as_array()?;
309+
310+
let mut list_mut = list.borrow_mut(mc);
311+
list_mut.data.reverse();
312+
313+
Ok(receiver)
314+
}

aiscript-vm/src/builtins/mod.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use std::{
99
io::{self, Write},
1010
};
1111

12+
mod array;
1213
mod convert;
1314
mod error;
1415
mod format;
@@ -28,6 +29,7 @@ use print::print;
2829
#[collect(no_drop)]
2930
pub(crate) struct BuiltinMethods<'gc> {
3031
string: HashMap<InternedString<'gc>, BuiltinMethod<'gc>>,
32+
array: HashMap<InternedString<'gc>, BuiltinMethod<'gc>>,
3133
}
3234

3335
impl Default for BuiltinMethods<'_> {
@@ -40,11 +42,13 @@ impl<'gc> BuiltinMethods<'gc> {
4042
pub fn new() -> Self {
4143
BuiltinMethods {
4244
string: HashMap::default(),
45+
array: HashMap::default(),
4346
}
4447
}
4548

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

5054
pub fn invoke_string_method(
@@ -63,6 +67,23 @@ impl<'gc> BuiltinMethods<'gc> {
6367
)))
6468
}
6569
}
70+
71+
pub fn invoke_array_method(
72+
&self,
73+
mc: &'gc Mutation<'gc>,
74+
name: InternedString<'gc>,
75+
receiver: Value<'gc>,
76+
args: Vec<Value<'gc>>,
77+
) -> Result<Value<'gc>, VmError> {
78+
if let Some(f) = self.array.get(&name) {
79+
f(mc, receiver, args)
80+
} else {
81+
Err(VmError::RuntimeError(format!(
82+
"Unknown array method: {}",
83+
name
84+
)))
85+
}
86+
}
6687
}
6788

6889
pub(crate) fn define_builtin_functions(state: &mut State) {

aiscript-vm/src/vm/state.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1413,6 +1413,26 @@ impl<'gc> State<'gc> {
14131413
self.push_stack(result);
14141414
Ok(())
14151415
}
1416+
Value::List(_) => {
1417+
// Array method handling
1418+
let mut args = Vec::new();
1419+
1420+
// Collect arguments
1421+
for _ in 0..args_count {
1422+
args.push(self.pop_stack());
1423+
}
1424+
args.reverse(); // Restore argument order
1425+
1426+
// Pop the receiver and keyword args
1427+
self.stack_top -= keyword_args_count as usize * 2 + 1;
1428+
1429+
// Dispatch to array method
1430+
let result = self
1431+
.builtin_methods
1432+
.invoke_array_method(self.mc, name, receiver, args)?;
1433+
self.push_stack(result);
1434+
Ok(())
1435+
}
14161436
Value::Class(class) => {
14171437
if let Some(value) = class.borrow().static_methods.get(&name) {
14181438
self.call_value(*value, args_count, keyword_args_count)

0 commit comments

Comments
 (0)