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