1/**********************************************************************
2
3  compar.c -
4
5  $Author: nagachika $
6  created at: Thu Aug 26 14:39:48 JST 1993
7
8  Copyright (C) 1993-2007 Yukihiro Matsumoto
9
10**********************************************************************/
11
12#include "ruby/ruby.h"
13
14VALUE rb_mComparable;
15
16static ID cmp;
17
18void
19rb_cmperr(VALUE x, VALUE y)
20{
21    const char *classname;
22
23    if (SPECIAL_CONST_P(y)) {
24	y = rb_inspect(y);
25	classname = StringValuePtr(y);
26    }
27    else {
28	classname = rb_obj_classname(y);
29    }
30    rb_raise(rb_eArgError, "comparison of %s with %s failed",
31	     rb_obj_classname(x), classname);
32}
33
34static VALUE
35invcmp_recursive(VALUE x, VALUE y, int recursive)
36{
37    if (recursive) return Qnil;
38    return rb_check_funcall(y, cmp, 1, &x);
39}
40
41VALUE
42rb_invcmp(VALUE x, VALUE y)
43{
44    VALUE invcmp = rb_exec_recursive(invcmp_recursive, x, y);
45    if (invcmp == Qundef || NIL_P(invcmp)) {
46	return Qnil;
47    }
48    else {
49	int result = -rb_cmpint(invcmp, x, y);
50	return INT2FIX(result);
51    }
52}
53
54static VALUE
55cmp_eq_recursive(VALUE arg1, VALUE arg2, int recursive)
56{
57    if (recursive) return Qfalse;
58    return rb_funcall(arg1, cmp, 1, arg2);
59}
60
61static VALUE
62cmp_eq(VALUE *a)
63{
64    VALUE c = rb_exec_recursive_paired_outer(cmp_eq_recursive, a[0], a[1], a[1]);
65
66    if (NIL_P(c)) return Qfalse;
67    if (rb_cmpint(c, a[0], a[1]) == 0) return Qtrue;
68    return Qfalse;
69}
70
71static VALUE
72cmp_failed(void)
73{
74    return Qfalse;
75}
76
77/*
78 *  call-seq:
79 *     obj == other    -> true or false
80 *
81 *  Compares two objects based on the receiver's <code><=></code>
82 *  method, returning true if it returns 0. Also returns true if
83 *  _obj_ and _other_ are the same object.
84 *
85 *  Even if _obj_ <=> _other_ raised an exception, the exception
86 *  is ignoread and returns false.
87 */
88
89static VALUE
90cmp_equal(VALUE x, VALUE y)
91{
92    VALUE a[2];
93
94    if (x == y) return Qtrue;
95
96    a[0] = x; a[1] = y;
97    return rb_rescue(cmp_eq, (VALUE)a, cmp_failed, 0);
98}
99
100/*
101 *  call-seq:
102 *     obj > other    -> true or false
103 *
104 *  Compares two objects based on the receiver's <code><=></code>
105 *  method, returning true if it returns 1.
106 */
107
108static VALUE
109cmp_gt(VALUE x, VALUE y)
110{
111    VALUE c = rb_funcall(x, cmp, 1, y);
112
113    if (rb_cmpint(c, x, y) > 0) return Qtrue;
114    return Qfalse;
115}
116
117/*
118 *  call-seq:
119 *     obj >= other    -> true or false
120 *
121 *  Compares two objects based on the receiver's <code><=></code>
122 *  method, returning true if it returns 0 or 1.
123 */
124
125static VALUE
126cmp_ge(VALUE x, VALUE y)
127{
128    VALUE c = rb_funcall(x, cmp, 1, y);
129
130    if (rb_cmpint(c, x, y) >= 0) return Qtrue;
131    return Qfalse;
132}
133
134/*
135 *  call-seq:
136 *     obj < other    -> true or false
137 *
138 *  Compares two objects based on the receiver's <code><=></code>
139 *  method, returning true if it returns -1.
140 */
141
142static VALUE
143cmp_lt(VALUE x, VALUE y)
144{
145    VALUE c = rb_funcall(x, cmp, 1, y);
146
147    if (rb_cmpint(c, x, y) < 0) return Qtrue;
148    return Qfalse;
149}
150
151/*
152 *  call-seq:
153 *     obj <= other    -> true or false
154 *
155 *  Compares two objects based on the receiver's <code><=></code>
156 *  method, returning true if it returns -1 or 0.
157 */
158
159static VALUE
160cmp_le(VALUE x, VALUE y)
161{
162    VALUE c = rb_funcall(x, cmp, 1, y);
163
164    if (rb_cmpint(c, x, y) <= 0) return Qtrue;
165    return Qfalse;
166}
167
168/*
169 *  call-seq:
170 *     obj.between?(min, max)    -> true or false
171 *
172 *  Returns <code>false</code> if <i>obj</i> <code><=></code>
173 *  <i>min</i> is less than zero or if <i>anObject</i> <code><=></code>
174 *  <i>max</i> is greater than zero, <code>true</code> otherwise.
175 *
176 *     3.between?(1, 5)               #=> true
177 *     6.between?(1, 5)               #=> false
178 *     'cat'.between?('ant', 'dog')   #=> true
179 *     'gnu'.between?('ant', 'dog')   #=> false
180 *
181 */
182
183static VALUE
184cmp_between(VALUE x, VALUE min, VALUE max)
185{
186    if (RTEST(cmp_lt(x, min))) return Qfalse;
187    if (RTEST(cmp_gt(x, max))) return Qfalse;
188    return Qtrue;
189}
190
191/*
192 *  The <code>Comparable</code> mixin is used by classes whose objects
193 *  may be ordered. The class must define the <code><=></code> operator,
194 *  which compares the receiver against another object, returning -1, 0,
195 *  or +1 depending on whether the receiver is less than, equal to, or
196 *  greater than the other object. If the other object is not comparable
197 *  then the <code><=></code> operator should return nil.
198 *  <code>Comparable</code> uses
199 *  <code><=></code> to implement the conventional comparison operators
200 *  (<code><</code>, <code><=</code>, <code>==</code>, <code>>=</code>,
201 *  and <code>></code>) and the method <code>between?</code>.
202 *
203 *     class SizeMatters
204 *       include Comparable
205 *       attr :str
206 *       def <=>(anOther)
207 *         str.size <=> anOther.str.size
208 *       end
209 *       def initialize(str)
210 *         @str = str
211 *       end
212 *       def inspect
213 *         @str
214 *       end
215 *     end
216 *
217 *     s1 = SizeMatters.new("Z")
218 *     s2 = SizeMatters.new("YY")
219 *     s3 = SizeMatters.new("XXX")
220 *     s4 = SizeMatters.new("WWWW")
221 *     s5 = SizeMatters.new("VVVVV")
222 *
223 *     s1 < s2                       #=> true
224 *     s4.between?(s1, s3)           #=> false
225 *     s4.between?(s3, s5)           #=> true
226 *     [ s3, s2, s5, s4, s1 ].sort   #=> [Z, YY, XXX, WWWW, VVVVV]
227 *
228 */
229
230void
231Init_Comparable(void)
232{
233#undef rb_intern
234#define rb_intern(str) rb_intern_const(str)
235
236    rb_mComparable = rb_define_module("Comparable");
237    rb_define_method(rb_mComparable, "==", cmp_equal, 1);
238    rb_define_method(rb_mComparable, ">", cmp_gt, 1);
239    rb_define_method(rb_mComparable, ">=", cmp_ge, 1);
240    rb_define_method(rb_mComparable, "<", cmp_lt, 1);
241    rb_define_method(rb_mComparable, "<=", cmp_le, 1);
242    rb_define_method(rb_mComparable, "between?", cmp_between, 2);
243
244    cmp = rb_intern("<=>");
245}
246