Annotation of parser3/src/classes/array.C, revision 1.5

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

E-mail: