Annotation of parser3/src/classes/array.C, revision 1.7
1.1 moko 1: /** @file
2: Parser: @b array parser class.
3:
4: Copyright (c) 2001-2023 Art. Lebedev Studio (http://www.artlebedev.com)
5: Authors: Konstantin Morshnev <moko@design.ru>, Alexandr Petrosian <paf@design.ru>
6: */
7:
8: #include "classes.h"
9: #include "pa_vmethod_frame.h"
10:
11: #include "pa_request.h"
12: #include "pa_charsets.h"
13: #include "pa_varray.h"
14: #include "pa_vvoid.h"
15: #include "pa_sql_connection.h"
16: #include "pa_vtable.h"
17: #include "pa_vbool.h"
18: #include "pa_vmethod_frame.h"
19:
1.7 ! moko 20: volatile const char * IDENT_ARRAY_C="$Id: array.C,v 1.6 2024/09/21 01:36:32 moko Exp $";
1.1 moko 21:
22: // class
23:
24: class MArray: public Methoded {
25: public: // VStateless_class
26: Value* create_new_value(Pool&) { return new VArray; }
27:
28: public:
29: MArray();
30: };
31:
32: // global variable
33:
34: DECLARE_CLASS_VAR(array, new MArray);
35:
1.5 moko 36: const char* const PARAM_ARRAY_OR_HASH = "param must be array or hash";
1.7 ! moko 37: const char* const PARAM_INDEX = "index must be integer";
1.5 moko 38:
1.1 moko 39: // methods
40:
41: static void _create_or_add(Request& r, MethodParams& params) {
42: if(params.count()) {
1.5 moko 43: Value& vsrc=params.as_no_junction(0, PARAM_ARRAY_OR_HASH);
1.1 moko 44: VArray& self=GET_SELF(r, VArray);
45: ArrayValue& self_array=self.array();
46:
1.3 moko 47: if(VArray* src=dynamic_cast<VArray*>(&vsrc)) {
1.6 moko 48: if(src==&self)
49: throw Exception(PARSER_RUNTIME, 0, "source and destination are the same array");
1.5 moko 50: self_array.append(src->array());
1.1 moko 51: } else {
52: HashStringValue* src_hash=vsrc.get_hash();
1.5 moko 53: if(!src_hash)
54: return;
55: for(HashStringValue::Iterator i(*src_hash); i; i.next()){
1.7 ! moko 56: self_array.put(VArray::index(i.key()), i.value());
1.5 moko 57: }
1.1 moko 58: }
1.4 moko 59: self.invalidate();
1.1 moko 60: }
61: }
62:
1.6 moko 63: static ArrayValue::Action_options get_action_options(Request& r, MethodParams& params, size_t options_index) {
64: ArrayValue::Action_options result;
65: if(params.count() <= options_index)
66: return result;
67:
68: HashStringValue* options=params.as_hash(options_index);
69: if(!options)
70: return result;
71:
72: result.defined=true;
73: int valid_options=0;
74:
75: if(Value* voffset=options->get(sql_offset_name)) {
76: valid_options++;
77: result.offset=r.process(*voffset).as_int();
78: }
79: if(Value* vlimit=options->get(sql_limit_name)) {
80: valid_options++;
81: result.limit=r.process(*vlimit).as_int();
82: }
83:
84: if(valid_options!=options->count())
85: throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION);
86:
87: return result;
88: }
89:
90: static void _join(Request& r, MethodParams& params) {
1.7 ! moko 91: Value& vsrc=params.as_no_junction(0, PARAM_ARRAY_OR_HASH);
1.6 moko 92: ArrayValue::Action_options o=get_action_options(r, params, 1);
93:
94: VArray& self=GET_SELF(r, VArray);
95: ArrayValue& self_array=self.array();
96:
97: if(VArray* src=dynamic_cast<VArray*>(&vsrc)) {
98: if(src==&self)
99: throw Exception(PARSER_RUNTIME, 0, "source and destination are the same array");
100:
101: if(o.defined){
102: for(ArrayValue::Iterator i(src->array()); i; i.next()){
103: if(i.value()){
104: if(o.offset > 0){
105: o.offset--;
106: continue;
107: }
108: if(o.limit-- == 0)
109: break;
110: self_array+=i.value();
111: }
112: }
113: } else {
114: for(ArrayValue::Iterator i(src->array()); i; i.next()){
115: if(i.value())
116: self_array+=i.value();
117: }
118: }
1.7 ! moko 119: } else {
! 120: HashStringValue* src_hash=vsrc.get_hash();
! 121: if(!src_hash)
! 122: return;
! 123: if(o.defined){
! 124: for(HashStringValue::Iterator i(*src_hash); i; i.next()){
! 125: if(o.offset > 0){
! 126: o.offset--;
! 127: continue;
! 128: }
! 129: if(o.limit-- == 0)
! 130: break;
! 131: self_array+=i.value();
! 132: }
! 133: } else {
! 134: for(HashStringValue::Iterator i(*src_hash); i; i.next()){
! 135: self_array+=i.value();
! 136: }
! 137: }
! 138: }
! 139: self.invalidate();
1.6 moko 140: }
141:
1.1 moko 142: static void _sql(Request& r, MethodParams& params) {}
143:
144: static void _sub(Request& r, MethodParams& params) {}
145:
146: static void _union(Request& r, MethodParams& params) {}
147:
148: static void _intersection(Request& r, MethodParams& params) {}
149:
150: static void _intersects(Request& r, MethodParams& params) {}
151:
152: static void _keys(Request& r, MethodParams& params) {
153: const String* keys_column_name;
154: if(params.count()>0)
155: keys_column_name=¶ms.as_string(0, COLUMN_NAME_MUST_BE_STRING);
156: else
157: keys_column_name=new String("key");
158:
159: Table::columns_type columns(new ArrayString(1));
160: *columns+=keys_column_name;
161: Table* table=new Table(columns);
162:
163: ArrayValue& array=GET_SELF(r, VArray).array();
164: for(ArrayValue::Iterator i(array); i; i.next()){
165: if(i.value()){
166: Table::element_type row(new ArrayString(1));
167: *row+=new String(i.key(), String::L_TAINTED);
168: *table+=row;
169: }
170: }
171:
172: r.write(*new VTable(table));
173: }
174:
1.5 moko 175: static void _count(Request& r, MethodParams& params) {
176: ArrayValue& array=GET_SELF(r, VArray).array();
177: if(params.count()>0){
178: const String& what=params.as_string(0, PARAMETER_MUST_BE_STRING);
179: if(!what.is_empty()){
180: if(what != "all")
181: throw Exception(PARSER_RUNTIME, &what, "param must be empty or 'all'");
182: return r.write(*new VInt(array.count()));
183: }
184: }
185: r.write(*new VInt(array.used()));
1.1 moko 186: }
187:
1.2 moko 188: static void _append(Request& r, MethodParams& params) {
189: VArray& self=GET_SELF(r, VArray);
190: ArrayValue& array=self.array();
191:
192: int count=params.count();
193:
194: for(int i=0; i<count; i++){
195: array+=&r.process(params[i]);
196: }
1.4 moko 197: self.invalidate();
1.2 moko 198: }
199:
200: static void _insert(Request& r, MethodParams& params) {
201: VArray& self=GET_SELF(r, VArray);
202: ArrayValue& array=self.array();
203:
204: int count=params.count();
1.7 ! moko 205: size_t index=VArray::index(params.as_int(0, PARAM_INDEX, r));
1.2 moko 206:
207: for(int i=1; i<count; i++){
208: array.insert(index+i-1, &r.process(params[i]));
209: }
1.4 moko 210: self.invalidate();
1.2 moko 211: }
212:
1.1 moko 213: static void _delete(Request& r, MethodParams& params) {
1.7 ! moko 214: VArray& self=GET_SELF(r, VArray);
1.1 moko 215: if(params.count()>0)
1.7 ! moko 216: self.array().clear(VArray::index(params.as_int(0, PARAM_INDEX, r)));
1.1 moko 217: else
1.7 ! moko 218: self.array().clear();
! 219: self.invalidate();
! 220: }
! 221:
! 222: static void _remove(Request& r, MethodParams& params) {
! 223: VArray& self=GET_SELF(r, VArray);
! 224: self.array().remove(VArray::index(params.as_int(0, PARAM_INDEX, r)));
! 225: self.invalidate();
1.1 moko 226: }
227:
228: static void _contains(Request& r, MethodParams& params) {
229: VArray& self=GET_SELF(r, VArray);
1.7 ! moko 230: bool result=self.contains(VArray::index(params.as_int(0, PARAM_INDEX, r)));
1.1 moko 231: r.write(VBool::get(result));
232: }
233:
1.5 moko 234: static void _for(Request& r, MethodParams& params) {
235: InCycle temp(r);
236:
1.7 ! moko 237: const String* key_var_name=¶ms.as_string(0, "key-var name must be string");
! 238: const String* value_var_name=¶ms.as_string(1, "value-var name must be string");
! 239: Value* body_code=¶ms.as_junction(2, "body must be code");
! 240: Value* delim_maybe_code=params.count()>3 ? ¶ms[3] : 0;
1.5 moko 241: Value& caller=*r.get_method_frame()->caller();
242:
1.7 ! moko 243: if(key_var_name->is_empty()) key_var_name=0;
1.5 moko 244: if(value_var_name->is_empty()) value_var_name=0;
245:
246: ArrayValue& array=GET_SELF(r, VArray).array();
247:
248: if(delim_maybe_code){ // delimiter set
249: bool need_delim=false;
250: for(ArrayValue::Iterator i(array); i; i.next()){
1.7 ! moko 251: if(key_var_name){
! 252: VString* vkey=new VString(*new String(i.key(), String::L_TAINTED));
! 253: r.put_element(caller, *key_var_name, vkey);
! 254: }
! 255:
1.5 moko 256: if(value_var_name)
257: r.put_element(caller, *value_var_name, i.value() ? i.value() : VVoid::get());
258:
259: Value& sv_processed=r.process(*body_code);
260: TempSkip4Delimiter skip(r);
261:
262: const String* s_processed=sv_processed.get_string();
263: if(s_processed && !s_processed->is_empty()) { // we have body
264: if(need_delim) // need delim & iteration produced string?
265: r.write(r.process(*delim_maybe_code));
266: else
267: need_delim=true;
268: }
269:
270: r.write(sv_processed);
271:
272: if(skip.check_break())
273: break;
274: }
275: } else {
276: for(ArrayValue::Iterator i(array); i; i.next()){
1.7 ! moko 277: if(key_var_name){
! 278: VString* vkey=new VString(*new String(i.key(), String::L_TAINTED));
! 279: r.put_element(caller, *key_var_name, vkey);
! 280: }
! 281:
1.5 moko 282: if(value_var_name)
283: r.put_element(caller, *value_var_name, i.value() ? i.value() : VVoid::get());
284:
285: r.process_write(*body_code);
286:
287: if(r.check_skip_break())
288: break;
289: }
290: }
291: }
292:
1.1 moko 293: static void _foreach(Request& r, MethodParams& params) {
294: InCycle temp(r);
295:
296: const String* key_var_name=¶ms.as_string(0, "key-var name must be string");
297: const String* value_var_name=¶ms.as_string(1, "value-var name must be string");
298: Value* body_code=¶ms.as_junction(2, "body must be code");
1.7 ! moko 299: Value* delim_maybe_code=params.count()>3 ? ¶ms[3] : 0;
1.1 moko 300: Value& caller=*r.get_method_frame()->caller();
301:
302: if(key_var_name->is_empty()) key_var_name=0;
303: if(value_var_name->is_empty()) value_var_name=0;
304:
305: ArrayValue& array=GET_SELF(r, VArray).array();
306:
307: if(delim_maybe_code){ // delimiter set
308: bool need_delim=false;
309: for(ArrayValue::Iterator i(array); i; i.next()){
310: if(i.value()){
311: if(key_var_name){
312: VString* vkey=new VString(*new String(i.key(), String::L_TAINTED));
313: r.put_element(caller, *key_var_name, vkey);
314: }
315:
316: if(value_var_name)
317: r.put_element(caller, *value_var_name, i.value());
318:
319: Value& sv_processed=r.process(*body_code);
320: TempSkip4Delimiter skip(r);
321:
322: const String* s_processed=sv_processed.get_string();
323: if(s_processed && !s_processed->is_empty()) { // we have body
324: if(need_delim) // need delim & iteration produced string?
325: r.write(r.process(*delim_maybe_code));
326: else
327: need_delim=true;
328: }
329:
330: r.write(sv_processed);
331:
332: if(skip.check_break())
333: break;
334: }
335: }
336: } else {
337: for(ArrayValue::Iterator i(array); i; i.next()){
338: if(i.value()){
339: if(key_var_name){
340: VString* vkey=new VString(*new String(i.key(), String::L_TAINTED));
341: r.put_element(caller, *key_var_name, vkey);
342: }
343:
344: if(value_var_name)
345: r.put_element(caller, *value_var_name, i.value());
346:
347: r.process_write(*body_code);
348:
349: if(r.check_skip_break())
350: break;
351: }
352: }
353: }
354: }
355:
356: #ifndef DOXYGEN
357: struct Array_seq_item : public PA_Allocated {
358: Value *array_data;
359: union {
360: const char *c_str;
361: double d;
362: } value;
363: };
364: #endif
365:
366: static int sort_cmp_string(const void *a, const void *b) {
367: return strcmp(
368: static_cast<const Array_seq_item *>(a)->value.c_str,
369: static_cast<const Array_seq_item *>(b)->value.c_str
370: );
371: }
372: static int sort_cmp_double(const void *a, const void *b) {
373: double va=static_cast<const Array_seq_item *>(a)->value.d;
374: double vb=static_cast<const Array_seq_item *>(b)->value.d;
375: if(va<vb)
376: return -1;
377: else if(va>vb)
378: return +1;
379: else
380: return 0;
381: }
382:
383: static void _sort(Request& r, MethodParams& params){
384: const String& key_var_name=params.as_string(0, "key-var name must be string");
385: const String& value_var_name=params.as_string(1, "value-var name must be string");
386: Value& key_maker=params.as_junction(2, "key-maker must be code");
387: bool reverse=params.count()>3 && params.as_no_junction(3, "order must not be code").as_string()=="desc"; // default=asc
388:
389: const String* key_var=key_var_name.is_empty()? 0 : &key_var_name;
390: const String* value_var=value_var_name.is_empty()? 0 : &value_var_name;
391: VMethodFrame* context=r.get_method_frame()->caller();
392:
393: VArray& self=GET_SELF(r, VArray);
394: ArrayValue& array=self.array();
1.4 moko 395: int count=array.used(); // not array.count()
1.1 moko 396:
397: Array_seq_item* seq=new Array_seq_item[count];
398: int pos=0;
399: bool key_values_are_strings=true;
400:
401: for(ArrayValue::Iterator i(array); i; i.next() ){
402: if(i.value()){
403: if(key_var)
404: r.put_element(*context, *key_var, new VString(*new String(i.key(), String::L_TAINTED)));
405: if(value_var)
406: r.put_element(*context, *value_var, i.value());
407:
408: Value& value=r.process(key_maker);
409: if(pos==0) // determining key values type by first one
410: key_values_are_strings=value.is_string();
411:
412: seq[pos].array_data=i.value();
413: if(key_values_are_strings)
414: seq[pos++].value.c_str=value.as_string().cstr();
415: else
416: seq[pos++].value.d=value.as_expr_result().as_double();
417: }
418: }
419:
420: // @todo: handle this elsewhere
421: if(r.charsets.source().NAME()=="KOI8-R" && key_values_are_strings)
422: for(pos=0; pos<count; pos++)
423: if(*seq[pos].value.c_str)
424: seq[pos].value.c_str=Charset::transcode(seq[pos].value.c_str, r.charsets.source(), pa_UTF8_charset).cstr();
425:
426: // sort keys
427: qsort(seq, count, sizeof(Array_seq_item), key_values_are_strings ? sort_cmp_string : sort_cmp_double);
428:
429: // reorder array as required in 'seq'
430: array.clear();
431: if(reverse)
432: for(pos=count-1; pos>=0; pos--)
433: array+=seq[pos].array_data;
434: else
435: for(pos=0; pos<count; pos++)
436: array+=seq[pos].array_data;
437:
438: delete[] seq;
439: }
440:
1.5 moko 441: enum AtResultType {
442: AtResultTypeValue = 0,
443: AtResultTypeKey = 1,
444: AtResultTypeHash = 2
445: };
446:
447: inline Value& SingleElementHash(String::Body akey, Value* avalue) {
448: Value& result=*new VHash;
449: result.put_element(*new String(akey, String::L_TAINTED), avalue);
450: return result;
451: }
452:
1.1 moko 453: static void _at(Request& r, MethodParams& params) {
454: VArray& self=GET_SELF(r, VArray);
455: ArrayValue& array=self.array();
1.5 moko 456: size_t count=array.used(); // not array.count()
1.1 moko 457:
458: int pos=0;
459:
460: AtResultType result_type=AtResultTypeValue;
461: if(params.count() > 1) {
462: const String& stype=params.as_string(1, "type must be string");
463: if(stype == "key")
464: result_type=AtResultTypeKey;
465: else if(stype == "hash")
466: result_type=AtResultTypeHash;
467: else if(stype != "value")
468: throw Exception(PARSER_RUNTIME, &stype, "type must be 'key', 'value' or 'hash'");
469: }
470:
471: Value& vwhence=params[0];
472: if(vwhence.is_string()) {
473: const String& swhence=*vwhence.get_string();
474: if(swhence == "last")
475: pos=count-1;
476: else if(swhence != "first")
477: throw Exception(PARSER_RUNTIME, &swhence, "whence must be 'first', 'last' or expression");
478: } else {
479: pos=r.process(vwhence).as_int();
480: if(pos < 0)
481: pos+=count;
482: }
483:
484: if(count && pos >= 0 && (size_t)pos < count){
485: switch(result_type) {
486: case AtResultTypeKey:
487: {
488: for(ArrayValue::Iterator i(array); i; i.next() ){
489: if(i.value() && !(pos--)){
490: r.write(*new VString(*new String(i.key(), String::L_TAINTED)));
491: break;
492: }
493: }
494: break;
495: }
496: case AtResultTypeValue:
497: {
498: for(ArrayValue::Iterator i(array); i; i.next() )
499: if(i.value() &&!(pos--)){
500: r.write(*i.value());
501: break;
502: }
503: break;
504: }
505: case AtResultTypeHash:
506: {
507: for(ArrayValue::Iterator i(array); i; i.next() )
508: if(i.value() &&!(pos--)){
509: r.write(SingleElementHash(i.key(), i.value()));
510: break;
511: }
512: break;
513: }
514: }
515: }
516: }
517:
518:
519: extern String table_reverse_name;
520:
521: static void _select(Request& r, MethodParams& params) {
522: InCycle temp(r);
523: const String* key_var_name=¶ms.as_string(0, "key-var name must be string");
524: const String* value_var_name=¶ms.as_string(1, "value-var name must be string");
525: Value& vcondition=params.as_expression(2, "condition must be number, bool or expression");
526:
527: if(key_var_name->is_empty()) key_var_name=0;
528: if(value_var_name->is_empty()) value_var_name=0;
529:
530: ArrayValue& source_array=GET_SELF(r, VArray).array();
531: Value& caller=*r.get_method_frame()->caller();
532:
533: int limit=source_array.count();
534: bool reverse=false;
535:
536: if(params.count()>3)
537: if(HashStringValue* options=params.as_hash(3)) {
538: int valid_options=0;
539: if(Value* vlimit=options->get(sql_limit_name)) {
540: valid_options++;
541: limit=r.process(*vlimit).as_int();
542: }
543: if(Value* vreverse=options->get(table_reverse_name)) {
544: valid_options++;
545: reverse=r.process(*vreverse).as_bool();
546: }
547: if(valid_options!=options->count())
548: throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION);
549: }
550:
551: VArray *result=new VArray;
552: ArrayValue& result_array=result->array();
553:
554: if(limit>0){
555: if(reverse){
556: for(ArrayValue::ReverseIterator i(source_array); i; ){
1.5 moko 557: if(Value *value=i.prev()){ // here for correct i.key()
558: if(key_var_name)
559: r.put_element(caller, *key_var_name, new VString(*new String(i.key(), String::L_TAINTED)));
560: if(value_var_name)
561: r.put_element(caller, *value_var_name, value);
1.1 moko 562:
1.5 moko 563: bool condition=r.process(vcondition).as_bool();
1.1 moko 564:
1.5 moko 565: if(r.check_skip_break())
566: break;
1.1 moko 567:
1.5 moko 568: if(condition){
569: result_array+=value;
570: if(!--limit)
571: break;
572: }
1.1 moko 573: }
574: }
575: } else {
576: for(ArrayValue::Iterator i(source_array); i; i.next() ){
1.5 moko 577: if(Value *value=i.value()){
1.1 moko 578: if(key_var_name)
579: r.put_element(caller, *key_var_name, new VString(*new String(i.key(), String::L_TAINTED)));
580: if(value_var_name)
581: r.put_element(caller, *value_var_name, value);
582:
583: bool condition=r.process(vcondition).as_bool();
584:
585: if(r.check_skip_break())
586: break;
587:
588: if(condition){
589: result_array+=value;
590: if(!--limit)
591: break;
592: }
593: }
594: }
595: }
596: }
597:
598: r.write(*result);
599: }
600:
601: static void _reverse(Request& r, MethodParams& params) {
1.5 moko 602: ArrayValue& source_array=GET_SELF(r, VArray).array();
1.1 moko 603:
1.5 moko 604: VArray& result=*new VArray(source_array.count());
1.1 moko 605: ArrayValue& result_array=result.array();
606:
1.4 moko 607: for(ArrayValue::ReverseIterator i(source_array); i; ){
608: result_array+=i.prev();
1.1 moko 609: }
610:
611: r.write(result);
612: }
613:
614:
615: // constructor
616:
617: MArray::MArray(): Methoded(VARRAY_TYPE) {
618:
619: // ^array::create[[copy_from]]
620: add_native_method("create", Method::CT_DYNAMIC, _create_or_add, 0, 1);
621: // ^array.add[add_from]
622: add_native_method("add", Method::CT_DYNAMIC, _create_or_add, 1, 1);
1.6 moko 623: // ^array.join[join_from[;options]]
624: add_native_method("join", Method::CT_DYNAMIC, _join, 1, 2);
1.1 moko 625:
626: // ^array.sub[sub_from]
627: add_native_method("sub", Method::CT_DYNAMIC, _sub, 1, 1);
1.2 moko 628: // ^array.union[b] = array
1.1 moko 629: add_native_method("union", Method::CT_DYNAMIC, _union, 1, 1);
1.2 moko 630: // ^array.intersection[b][options array] = array
1.1 moko 631: add_native_method("intersection", Method::CT_DYNAMIC, _intersection, 1, 2);
1.2 moko 632: // ^array.intersects[b] = bool
1.1 moko 633: add_native_method("intersects", Method::CT_DYNAMIC, _intersects, 1, 1);
634:
1.2 moko 635: // ^array.append[value;value]
636: add_native_method("append", Method::CT_DYNAMIC, _append, 1, 10000);
637: // ^array.insert[index;value...]
638: add_native_method("insert", Method::CT_DYNAMIC, _insert, 2, 10000);
639:
640: // ^array.delete[index]
1.1 moko 641: add_native_method("delete", Method::CT_DYNAMIC, _delete, 0, 1);
1.7 ! moko 642: // ^array.remove[index]
! 643: add_native_method("remove", Method::CT_DYNAMIC, _remove, 1, 1);
1.1 moko 644:
1.2 moko 645: // ^array.contains[index]
1.1 moko 646: add_native_method("contains", Method::CT_DYNAMIC, _contains, 1, 1);
647:
648: // ^array::sql[query][options array]
649: add_native_method("sql", Method::CT_DYNAMIC, _sql, 1, 2);
650:
651: // ^array._keys[[column name]]
652: add_native_method("_keys", Method::CT_DYNAMIC, _keys, 0, 1);
653:
1.5 moko 654: // ^array._count[[all]]
655: add_native_method("_count", Method::CT_DYNAMIC, _count, 0, 1);
1.1 moko 656:
1.7 ! moko 657: // ^array.for[index;value]{code}[delim]
! 658: add_native_method("for", Method::CT_DYNAMIC, _for, 3, 3+1);
1.2 moko 659: // ^array.foreach[index;value]{code}[delim]
1.7 ! moko 660: add_native_method("foreach", Method::CT_DYNAMIC, _foreach, 3, 3+1);
1.1 moko 661:
1.2 moko 662: // ^array.sort[index;value]{string-key-maker}[[asc|desc]]
663: // ^array.sort[index;value](numeric-key-maker)[[asc|desc]]
1.1 moko 664: add_native_method("sort", Method::CT_DYNAMIC, _sort, 3, 4);
665:
1.2 moko 666: // ^array.select[index;value](bool-condition)[options hash]
1.1 moko 667: add_native_method("select", Method::CT_DYNAMIC, _select, 3, 4);
668:
669: // ^array.reverse[]
670: add_native_method("reverse", Method::CT_DYNAMIC, _reverse, 0, 0);
671:
1.2 moko 672: // ^array._at[first|last[;'key'|'value'|'hash']]
673: // ^array._at([-+]offset)[['key'|'value'|'hash']]
1.1 moko 674: add_native_method("_at", Method::CT_DYNAMIC, _at, 1, 2);
675:
676: #ifdef FEATURE_GET_ELEMENT4CALL
677: // aliases without "_"
678: add_native_method("keys", Method::CT_DYNAMIC, _keys, 0, 1);
679: add_native_method("count", Method::CT_DYNAMIC, _count, 0, 0);
680: add_native_method("at", Method::CT_DYNAMIC, _at, 1, 2);
681: #endif
682:
683: }
E-mail: