Skip to content

Commit

Permalink
Support Array#==, first, last.
Browse files Browse the repository at this point in the history
  • Loading branch information
sisshiki1969 committed Aug 17, 2023
1 parent 0882d7a commit e5e67b4
Show file tree
Hide file tree
Showing 2 changed files with 163 additions and 49 deletions.
114 changes: 110 additions & 4 deletions monoruby/src/executor/builtins/array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,20 @@ pub(super) fn init(globals: &mut Globals) {
globals.define_builtin_func(ARRAY_CLASS, "empty?", empty, 0);
globals.define_builtin_func(ARRAY_CLASS, "+", add, 1);
globals.define_builtin_func(ARRAY_CLASS, "*", mul, 1);
globals.define_builtin_func(ARRAY_CLASS, "shift", shift, -1);
globals.define_builtin_func(ARRAY_CLASS, "unshift", unshift, -1);
globals.define_builtin_func(ARRAY_CLASS, "prepend", unshift, -1);
globals.define_builtin_func(ARRAY_CLASS, "<<", shl, 1);
globals.define_builtin_func(ARRAY_CLASS, "==", eq, 1);
globals.define_builtin_func(ARRAY_CLASS, "[]", index, -1);
globals.define_builtin_func(ARRAY_CLASS, "[]=", index_assign, -1);
globals.define_builtin_func(ARRAY_CLASS, "shift", shift, -1);
globals.define_builtin_func(ARRAY_CLASS, "unshift", unshift, -1);
globals.define_builtin_func(ARRAY_CLASS, "prepend", unshift, -1);
globals.define_builtin_func(ARRAY_CLASS, "clear", clear, 0);
globals.define_builtin_func(ARRAY_CLASS, "fill", fill, 1);
globals.define_builtin_func(ARRAY_CLASS, "inject", inject, -1);
globals.define_builtin_func(ARRAY_CLASS, "reduce", inject, -1);
globals.define_builtin_func(ARRAY_CLASS, "join", join, -1);
globals.define_builtin_func(ARRAY_CLASS, "first", first, -1);
globals.define_builtin_func(ARRAY_CLASS, "last", last, -1);
globals.define_builtin_func(ARRAY_CLASS, "sum", sum, -1);
globals.define_builtin_func(ARRAY_CLASS, "min", min, 0);
globals.define_builtin_func(ARRAY_CLASS, "max", max, 0);
Expand Down Expand Up @@ -249,6 +252,28 @@ fn shl(_vm: &mut Executor, _globals: &mut Globals, lfp: LFP, arg: Arg) -> Result
Ok(ary.into())
}

///
/// ### Array#==
///
/// - self == other -> bool
///
/// [https://docs.ruby-lang.org/ja/latest/method/Array/i/=3d=3d.html]
#[monoruby_builtin]
fn eq(vm: &mut Executor, globals: &mut Globals, lfp: LFP, _: Arg) -> Result<Value> {
MonorubyErr::check_number_of_arguments(lfp.arg_len(), 1)?;
let lhs: Array = lfp.self_val().into();
let rhs: Array = lfp.arg(0).into();
if lhs.len() != rhs.len() {
return Ok(Value::bool(false));
}
for i in 0..lhs.len() {
if vm.ne_values_bool(globals, lhs[i], rhs[i])? {
return Ok(Value::bool(false));
}
}
Ok(Value::bool(true))
}

///
/// ### Array#[]
///
Expand Down Expand Up @@ -398,6 +423,62 @@ fn array_join(globals: &Globals, res: &mut String, aref: Array, sep: &str) {
}
}

///
/// ### Array#first
///
/// - first -> object | nil
/// - first(n) -> Array
///
/// [https://docs.ruby-lang.org/ja/latest/method/Array/i/first.html]
#[monoruby_builtin]
fn first(_: &mut Executor, globals: &mut Globals, lfp: LFP, _: Arg) -> Result<Value> {
let len = lfp.arg_len();
MonorubyErr::check_number_of_arguments_range(len, 0..=1)?;
let ary: Array = lfp.self_val().into();
if len == 0 {
Ok(ary.first().cloned().unwrap_or_default())
} else {
let n = lfp.arg(0).coerce_to_i64(globals)?;
if n < 0 {
return Err(MonorubyErr::argumenterr("must be positive."));
}
let n = if n as usize > ary.len() {
ary.len()
} else {
n as usize
};
Ok(Value::array_from_iter(ary[0..n].iter().cloned()))
}
}

///
/// ### Array#last
///
/// - last -> object | nil
/// - last(n) -> Array
///
/// [https://docs.ruby-lang.org/ja/latest/method/Array/i/last.html]
#[monoruby_builtin]
fn last(_: &mut Executor, globals: &mut Globals, lfp: LFP, _: Arg) -> Result<Value> {
let len = lfp.arg_len();
MonorubyErr::check_number_of_arguments_range(len, 0..=1)?;
let ary: Array = lfp.self_val().into();
if len == 0 {
Ok(ary.last().cloned().unwrap_or_default())
} else {
let n = lfp.arg(0).coerce_to_i64(globals)?;
if n < 0 {
return Err(MonorubyErr::argumenterr("must be positive."));
}
let n = if n as usize > ary.len() {
0
} else {
ary.len() - n as usize
};
Ok(Value::array_from_iter(ary[n..].iter().cloned()))
}
}

///
/// ### Array#sum
///
Expand Down Expand Up @@ -605,7 +686,7 @@ fn include_(vm: &mut Executor, globals: &mut Globals, lfp: LFP, arg: Arg) -> Res
let ary: Array = lfp.self_val().into();
let rhs = arg[0];
for lhs in ary.iter().cloned() {
if vm.cmp_eq_values_bool(globals, lhs, rhs)? {
if vm.eq_values_bool(globals, lhs, rhs)? {
return Ok(Value::bool(true));
};
}
Expand Down Expand Up @@ -980,6 +1061,13 @@ mod test {
run_test(r##"a = [1,2,3]; a.<<(10); a"##);
}

#[test]
fn eq() {
run_test(r##"["a","c"] == ["a","c",7]"##);
run_test(r##"["a","c",7] == ["a","c",7]"##);
run_test(r##"["a","c",7] == ["a","c","7"]"##);
}

#[test]
fn index() {
run_test(
Expand Down Expand Up @@ -1094,6 +1182,24 @@ mod test {
run_test(r##"[2, 3, 4, 5].join("-")"##);
}

#[test]
fn first() {
run_test(r##"[[0,1,2,3].first, [].first]"##);
run_test(
r##"
a = [0,1,2]
[a.first(0), a.first(1), a.first(2), a.first(3), a.first(4)]
"##,
);
run_test(r##"[[0,1,2,3].last, [].last]"##);
run_test(
r##"
a = [0,1,2]
[a.last(0), a.last(1), a.last(2), a.last(3), a.last(4)]
"##,
);
}

#[test]
fn sum() {
run_test(
Expand Down
98 changes: 53 additions & 45 deletions monoruby/src/executor/op.rs
Original file line number Diff line number Diff line change
Expand Up @@ -331,69 +331,77 @@ cmp_values!(
(lt, IdentId::_LT)
);

impl Executor {
pub(super) fn eq_values_bool(
&mut self,
globals: &mut Globals,
lhs: Value,
rhs: Value,
) -> Result<bool> {
let b = match (lhs.unpack(), rhs.unpack()) {
(RV::Nil, RV::Nil) => true,
(RV::Nil, _) => false,
(RV::Fixnum(lhs), RV::Fixnum(rhs)) => lhs.eq(&rhs),
(RV::Fixnum(lhs), RV::BigInt(rhs)) => BigInt::from(lhs).eq(&rhs),
(RV::Fixnum(lhs), RV::Float(rhs)) => (lhs as f64).eq(&rhs),
(RV::Fixnum(_), _) => false,
(RV::BigInt(lhs), RV::Fixnum(rhs)) => lhs.eq(&BigInt::from(rhs)),
(RV::BigInt(lhs), RV::BigInt(rhs)) => lhs.eq(&rhs),
(RV::BigInt(lhs), RV::Float(rhs)) => lhs.to_f64().unwrap().eq(&rhs),
(RV::BigInt(_), _) => false,
(RV::Float(lhs), RV::Fixnum(rhs)) => lhs.eq(&(rhs as f64)),
(RV::Float(lhs), RV::BigInt(rhs)) => lhs.eq(&(rhs.to_f64().unwrap())),
(RV::Float(lhs), RV::Float(rhs)) => lhs.eq(&rhs),
(RV::Float(_), _) => false,
(RV::Bool(lhs), RV::Bool(rhs)) => lhs.eq(&rhs),
(RV::Bool(_), _) => false,
(RV::Symbol(lhs), RV::Symbol(rhs)) => lhs.eq(&rhs),
(RV::Symbol(_), _) => false,
(RV::String(lhs), RV::String(rhs)) => lhs.eq(rhs),
(RV::String(_), _) => false,
_ => self
.invoke_method_inner(globals, IdentId::_EQ, lhs, &[rhs], None)?
.as_bool(),
};
Ok(b)
}

pub(super) fn ne_values_bool(
&mut self,
globals: &mut Globals,
lhs: Value,
rhs: Value,
) -> Result<bool> {
Ok(!self.eq_values_bool(globals, lhs, rhs)?)
}
}

macro_rules! eq_values {
(($op:ident, $op_str:expr)) => {
($op:ident) => {
paste! {
pub(super) extern "C" fn [<cmp_ $op _values>](
vm: &mut Executor,
globals: &mut Globals,
lhs: Value,
rhs: Value
) -> Option<Value> {
match vm.[<cmp_ $op _values_bool>](globals, lhs, rhs) {
match vm.[<$op _values_bool>](globals, lhs, rhs) {
Ok(b) => Some(Value::bool(b)),
Err(err) => {
vm.set_error(err);
None
}
}
}

impl Executor {
#[allow(unused)]
pub(super) fn [<cmp_ $op _values_bool>](
&mut self,
globals: &mut Globals,
lhs: Value,
rhs: Value
) -> Result<bool> {
let b = match (lhs.unpack(), rhs.unpack()) {
(RV::Nil, RV::Nil) => true.$op(&true),
(RV::Nil, _) => false.$op(&true),
(RV::Fixnum(lhs), RV::Fixnum(rhs)) => lhs.$op(&rhs),
(RV::Fixnum(lhs), RV::BigInt(rhs)) => BigInt::from(lhs).$op(&rhs),
(RV::Fixnum(lhs), RV::Float(rhs)) => (lhs as f64).$op(&rhs),
(RV::Fixnum(lhs), _) => false.$op(&true),
(RV::BigInt(lhs), RV::Fixnum(rhs)) => lhs.$op(&BigInt::from(rhs)),
(RV::BigInt(lhs), RV::BigInt(rhs)) => lhs.$op(&rhs),
(RV::BigInt(lhs), RV::Float(rhs)) => lhs.to_f64().unwrap().$op(&rhs),
(RV::BigInt(lhs), _) => false.$op(&true),
(RV::Float(lhs), RV::Fixnum(rhs)) => lhs.$op(&(rhs as f64)),
(RV::Float(lhs), RV::BigInt(rhs)) => lhs.$op(&(rhs.to_f64().unwrap())),
(RV::Float(lhs), RV::Float(rhs)) => lhs.$op(&rhs),
(RV::Float(lhs), _) => false.$op(&true),
(RV::Bool(lhs), RV::Bool(rhs)) => lhs.$op(&rhs),
(RV::Bool(lhs), _) => false.$op(&true),
(RV::Symbol(lhs), RV::Symbol(rhs)) => lhs.$op(&rhs),
(RV::Symbol(lhs), _) => false.$op(&true),
(RV::String(lhs), RV::String(rhs)) => lhs.$op(rhs),
(RV::String(lhs), _) => false.$op(&true),
_ => {
self.invoke_method_inner(globals, $op_str, lhs, &[rhs], None)?.as_bool()
}
};
Ok(b)
}
}
}
};
(($op1:ident, $op_str1:expr), $(($op2:ident, $op_str2:expr)),+) => {
eq_values!(($op1, $op_str1));
eq_values!($(($op2, $op_str2)),+);
($op1:ident, $($op2:ident),+) => {
eq_values!($op1);
eq_values!($($op2),+);
};
}

eq_values!((eq, IdentId::_EQ), (ne, IdentId::_NEQ));
eq_values!(eq, ne);

#[test]
fn cmp_values() {
Expand Down Expand Up @@ -429,11 +437,11 @@ fn cmp_values() {
for (lhs, rhs, ans) in pairs {
assert_eq!(
ans,
Executor::cmp_eq_values_bool(&mut vm, &mut globals, lhs, rhs).unwrap()
Executor::eq_values_bool(&mut vm, &mut globals, lhs, rhs).unwrap()
);
assert_eq!(
ans,
!Executor::cmp_ne_values_bool(&mut vm, &mut globals, lhs, rhs).unwrap()
!Executor::ne_values_bool(&mut vm, &mut globals, lhs, rhs).unwrap()
);
}
}
Expand Down

0 comments on commit e5e67b4

Please sign in to comment.