|
|
1.1 moko 1: /** @file
2: Parser: @b array parser class.
3:
1.43 ! moko 4: Copyright (c) 2001-2026 Art. Lebedev Studio (https://www.artlebedev.com)
1.1 moko 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.43 ! moko 20: volatile const char * IDENT_ARRAY_C="$Id: array.C,v 1.42 2025/10/05 19:41:27 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:
1.25 moko 41: static void _copy_or_add(Request& r, MethodParams& params) {
1.1 moko 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)
1.14 moko 49: return;
50: if(self_array.count()){
51: for(ArrayValue::Iterator i(src->array()); i; i.next()){
52: if(i.value())
53: self_array.put(i.index(), i.value());
54: }
55: } else {
1.33 moko 56: self_array.copy(src->array());
1.34 moko 57: return;
1.14 moko 58: }
1.1 moko 59: } else {
1.15 moko 60: HashStringValue* src_hash=vsrc.as_hash("param must be array or");
1.5 moko 61: if(!src_hash)
62: return;
63: for(HashStringValue::Iterator i(*src_hash); i; i.next()){
1.7 moko 64: self_array.put(VArray::index(i.key()), i.value());
1.5 moko 65: }
1.1 moko 66: }
1.23 moko 67: self_array.invalidate();
1.1 moko 68: }
69: }
70:
1.6 moko 71: static ArrayValue::Action_options get_action_options(Request& r, MethodParams& params, size_t options_index) {
72: ArrayValue::Action_options result;
73: if(params.count() <= options_index)
74: return result;
75:
76: HashStringValue* options=params.as_hash(options_index);
77: if(!options)
78: return result;
79:
80: result.defined=true;
81: int valid_options=0;
82:
83: if(Value* voffset=options->get(sql_offset_name)) {
84: valid_options++;
1.8 moko 85: int offset=r.process(*voffset).as_int();
86: result.offset=offset < 0 ? 0 : offset;
1.6 moko 87: }
88: if(Value* vlimit=options->get(sql_limit_name)) {
89: valid_options++;
1.8 moko 90: int limit=r.process(*vlimit).as_int();
91: result.limit=limit < 0 ? 0: limit;
1.6 moko 92: }
93:
94: if(valid_options!=options->count())
95: throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION);
96:
97: return result;
98: }
99:
100: static void _join(Request& r, MethodParams& params) {
1.7 moko 101: Value& vsrc=params.as_no_junction(0, PARAM_ARRAY_OR_HASH);
1.6 moko 102: ArrayValue::Action_options o=get_action_options(r, params, 1);
103:
104: VArray& self=GET_SELF(r, VArray);
105: ArrayValue& self_array=self.array();
106:
107: if(VArray* src=dynamic_cast<VArray*>(&vsrc)) {
108: if(src==&self)
109: throw Exception(PARSER_RUNTIME, 0, "source and destination are the same array");
110:
111: if(o.defined){
112: for(ArrayValue::Iterator i(src->array()); i; i.next()){
113: if(i.value()){
114: if(o.offset > 0){
115: o.offset--;
116: continue;
117: }
118: if(o.limit-- == 0)
119: break;
120: self_array+=i.value();
121: }
122: }
123: } else {
124: for(ArrayValue::Iterator i(src->array()); i; i.next()){
125: if(i.value())
126: self_array+=i.value();
127: }
128: }
1.7 moko 129: } else {
1.15 moko 130: HashStringValue* src_hash=vsrc.as_hash("param must be array or");
1.7 moko 131: if(!src_hash)
132: return;
133: if(o.defined){
134: for(HashStringValue::Iterator i(*src_hash); i; i.next()){
135: if(o.offset > 0){
136: o.offset--;
137: continue;
138: }
139: if(o.limit-- == 0)
140: break;
141: self_array+=i.value();
142: }
143: } else {
144: for(HashStringValue::Iterator i(*src_hash); i; i.next()){
145: self_array+=i.value();
146: }
147: }
148: }
1.23 moko 149: self_array.invalidate();
1.6 moko 150: }
151:
1.11 moko 152: #ifndef DOXYGEN
153:
154: #define STRING(str) ((str) ? *new String(str, String::L_TAINTED /* no length as 0x00 can be inside */) : String::Empty)
155:
156: class SparseArray_sql_event_handlers: public SQL_Driver_query_event_handlers {
157: bool distinct;
158: ArrayValue& result;
159: Value* row_value;
160: int column_index;
1.12 moko 161: ArrayString* columns;
1.11 moko 162: bool one_bool_column;
163: Table2hash_value_type value_type;
164: int columns_count;
165: public:
166: Table* empty;
167: public:
168: SparseArray_sql_event_handlers(bool adistinct, ArrayValue& aresult, Table2hash_value_type avalue_type):
169: distinct(adistinct),
170: result(aresult),
171: row_value(0),
172: column_index(0),
1.12 moko 173: columns(new ArrayString),
1.11 moko 174: one_bool_column(false),
175: value_type(avalue_type),
176: empty(0) {
177: }
178:
179: bool add_column(SQL_Error& error, const char* str, size_t ) {
180: try {
1.12 moko 181: if(columns_count){
182: // another query in multi_statements mode
183: columns=new ArrayString;
184: columns_count=0;
185: }
186: *columns+=&STRING(str);
1.11 moko 187: return false;
188: } catch(...) {
189: error=SQL_Error("exception occurred in Hash_sql_event_handlers::add_column");
190: return true;
191: }
192: }
193:
194: bool before_rows(SQL_Error& error) {
1.12 moko 195: columns_count=columns->count();
196: if(columns_count<1) {
1.11 moko 197: error=SQL_Error("no columns");
198: return true;
199: }
1.12 moko 200: if(columns_count==1) {
1.11 moko 201: one_bool_column=true;
202: } else {
203: switch(value_type){
204: case C_STRING: {
1.12 moko 205: if(columns_count>2){
206: error=SQL_Error("only 2 columns allowed for $.type[string] and $.sparse(true)");
1.11 moko 207: return true;
208: }
209: break;
210: }
211: case C_TABLE: {
212: // create empty table which we'll copy later
1.12 moko 213: empty=new Table(columns);
1.11 moko 214: break;
215: }
216: }
217: }
218: return false;
219: }
220:
221: bool add_row(SQL_Error& /*error*/) {
222: column_index=0;
223: return false;
224: }
225:
226: bool add_row_cell(SQL_Error& error, const char *str, size_t ) {
227: try {
1.12 moko 228: if(column_index==columns_count){
229: // should never happen, buggy driver case
230: error=SQL_Error("columns index exceed the columns count");
231: return true;
232: }
233:
1.11 moko 234: bool duplicate=false;
235: if(one_bool_column) {
236: size_t index=str ? pa_atoui(str) : 0;
237: duplicate=result.put_dont_replace(index, &VBool::get(true)); // put. existed?
238: } else if(column_index==0) {
239: size_t index=str ? pa_atoui(str) : 0;
240: switch(value_type){
241: case C_HASH: {
242: VHash* row_vhash=new VHash;
243: row_value=row_vhash;
244: duplicate=result.put_dont_replace(index, row_vhash); // put. existed?
245: break;
246: }
247: case C_STRING: {
248: VString* row_vstring=new VString();
249: row_value=row_vstring;
250: duplicate=result.put_dont_replace(index, row_vstring); // put. existed?
251: break;
252: }
253: case C_TABLE: {
254: VTable* vtable=(VTable*)result.get(index);
255:
256: if(vtable) { // table with this key exist?
257: if(!distinct) {
258: duplicate=true;
259: break;
260: }
261: } else {
262: // no? creating table of same structure as source
263: Table::Action_options table_options(0, 0);
264: vtable=new VTable(new Table(*empty, table_options/*no rows, just structure*/));
265: result.put(index, vtable); // put
266: }
267: ArrayString* row=new ArrayString(columns_count);
268: *row+=&STRING(str);
269: *vtable->get_table()+=row;
1.12 moko 270: row_value=(Value*)row;
1.11 moko 271: break;
272: }
273: }
274: } else {
275: const String& cell=STRING(str);
276: switch(value_type) {
277: case C_HASH: {
1.12 moko 278: row_value->get_hash()->put(*columns->get(column_index), new VString(cell));
1.11 moko 279: break;
280: }
281: case C_STRING: {
282: VString* row_string=(VString*)row_value;
283: row_string->set_string(cell);
284: break;
285: }
286: case C_TABLE: {
287: ArrayString* row=(ArrayString*)row_value;
288: *row+=&cell;
289: break;
290: }
291: }
292: }
293:
294: if(duplicate & !distinct) {
295: error=SQL_Error("duplicate key");
296: return true;
297: }
298:
299: column_index++;
300: return false;
301: } catch(const Exception& e) {
302: error=SQL_Error(e.type(), e.comment());
303: return true;
304: } catch(...) {
305: error=SQL_Error("exception occurred in Hash_sql_event_handlers::add_row_cell");
306: return true;
307: }
308: }
309: };
310:
311: class Array_sql_event_handlers: public SQL_Driver_query_event_handlers {
312: ArrayValue& result;
313: Value* row_value;
314: int column_index;
1.12 moko 315: ArrayString* columns;
1.11 moko 316: Table2hash_value_type value_type;
317: int columns_count;
318: public:
319: Table* empty;
320: public:
321: Array_sql_event_handlers(ArrayValue& aresult, Table2hash_value_type avalue_type):
322: result(aresult),
323: row_value(0),
324: column_index(0),
1.12 moko 325: columns(new ArrayString),
1.11 moko 326: value_type(avalue_type),
327: empty(0) {
328: }
329:
330: bool add_column(SQL_Error& error, const char* str, size_t ) {
331: try {
1.12 moko 332: if(columns_count){
333: // another query in multi_statements mode
334: columns=new ArrayString;
335: columns_count=0;
336: }
337: *columns+=&STRING(str);
1.11 moko 338: return false;
339: } catch(...) {
340: error=SQL_Error("exception occurred in Hash_sql_event_handlers::add_column");
341: return true;
342: }
343: }
344:
345: bool before_rows(SQL_Error& error) {
1.12 moko 346: columns_count=columns->count();
347: if(columns_count<1) {
1.11 moko 348: error=SQL_Error("no columns");
349: return true;
350: }
351: switch(value_type){
352: case C_STRING: {
1.12 moko 353: if(columns_count>1){
354: error=SQL_Error("only one column allowed for $.type[string]");
1.11 moko 355: return true;
356: }
357: break;
358: }
359: case C_TABLE: {
360: // create empty table which we'll copy later
1.12 moko 361: empty=new Table(columns);
1.11 moko 362: break;
363: }
364: }
365: return false;
366: }
367:
368: bool add_row(SQL_Error& /*error*/) {
369: column_index=0;
370: return false;
371: }
372:
373: bool add_row_cell(SQL_Error& error, const char *str, size_t ) {
374: try {
1.12 moko 375: if(column_index==columns_count){
376: // should never happen, buggy driver case
377: error=SQL_Error("columns index exceed the columns count");
378: return true;
379: }
380:
1.11 moko 381: if(column_index==0) {
382: switch(value_type){
383: case C_HASH: {
384: VHash* row_vhash=new VHash;
385: row_value=row_vhash;
386: result+=row_vhash;
387: break;
388: }
389: case C_STRING: {
390: VString* row_vstring=new VString();
391: row_value=row_vstring;
392: result+=row_vstring;
393: break;
394: }
395: case C_TABLE: {
396: // creating table of same structure as source
397: Table::Action_options table_options(0, 0);
398: VTable* vtable=new VTable(new Table(*empty, table_options/*no rows, just structure*/));
399: ArrayString* row=new ArrayString(columns_count);
400: *vtable->get_table()+=row;
1.12 moko 401: row_value=(Value*)row;
402: result+=vtable;
1.11 moko 403: break;
404: }
405: }
406: }
407:
408: const String& cell=STRING(str);
409: switch(value_type) {
410: case C_HASH: {
1.12 moko 411: row_value->get_hash()->put(*columns->get(column_index), new VString(cell));
1.11 moko 412: break;
413: }
414: case C_STRING: {
415: VString* row_string=(VString*)row_value;
416: row_string->set_string(cell);
417: break;
418: }
419: case C_TABLE: {
420: ArrayString* row=(ArrayString*)row_value;
421: *row+=&cell;
422: break;
423: }
424: }
425:
426: column_index++;
427: return false;
428: } catch(const Exception& e) {
429: error=SQL_Error(e.type(), e.comment());
430: return true;
431: } catch(...) {
432: error=SQL_Error("exception occurred in Hash_sql_event_handlers::add_row_cell");
433: return true;
434: }
435: }
436: };
437:
438: #endif
439:
440: extern Table2hash_value_type get_value_type(Value& vvalue_type);
441: extern int marshal_binds(HashStringValue& hash, SQL_Driver::Placeholder*& placeholders);
442: extern void unmarshal_bind_updates(HashStringValue& hash, int placeholder_count, SQL_Driver::Placeholder* placeholders);
443:
444: static void _sql(Request& r, MethodParams& params) {
445: Value& statement=params.as_junction(0, "statement must be code");
446:
447: HashStringValue* bind=0;
448: ulong limit=SQL_NO_LIMIT;
449: ulong offset=0;
450: bool distinct=false;
451: bool sparse=false;
452: Table2hash_value_type value_type=C_HASH;
453: if(params.count()>1)
454: if(HashStringValue* options=params.as_hash(1, "sql options")) {
1.13 moko 455: bool distinct_specified=false;
1.11 moko 456: for(HashStringValue::Iterator i(*options); i; i.next() ){
457: String::Body key=i.key();
458: Value* value=i.value();
459: if(key == sql_bind_name) {
460: bind=value->get_hash();
461: } else if(key == sql_limit_name) {
462: limit=(ulong)r.process(*value).as_double();
463: } else if(key == sql_offset_name) {
464: offset=(ulong)r.process(*value).as_double();
465: } else if (key == sql_distinct_name) {
466: distinct=r.process(*value).as_bool();
1.13 moko 467: distinct_specified=true;
1.11 moko 468: } else if (key == sql_value_type_name) {
1.12 moko 469: value_type=get_value_type(r.process(*value));
1.11 moko 470: } else if (key == "sparse") {
1.12 moko 471: sparse=r.process(*value).as_bool();
1.42 moko 472: } else
473: throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION);
1.11 moko 474: }
1.13 moko 475: if(distinct_specified && !sparse)
476: throw Exception(PARSER_RUNTIME, 0, "'distinct' option can only be used when $.sparse(true) is specified");
1.11 moko 477: }
478:
479: SQL_Driver::Placeholder* placeholders=0;
480: uint placeholders_count=0;
481: if(bind)
482: placeholders_count=marshal_binds(*bind, placeholders);
483:
484: const String& statement_string=r.process_to_string(statement);
485: const char* statement_cstr=statement_string.untaint_cstr(String::L_SQL, r.connection());
486:
487: VArray& self=GET_SELF(r, VArray);
1.17 moko 488: ArrayValue& array=self.array();
1.13 moko 489:
1.23 moko 490: if(array.count()){
491: array.clear(); array.invalidate(); // just in case if called as method
492: }
1.11 moko 493:
494: if(sparse){
1.17 moko 495: SparseArray_sql_event_handlers handlers(distinct, array, value_type);
1.11 moko 496: r.connection()->query(statement_cstr, placeholders_count, placeholders, offset, limit, handlers, statement_string);
497: } else {
1.17 moko 498: Array_sql_event_handlers handlers(array, value_type);
1.11 moko 499: r.connection()->query(statement_cstr, placeholders_count, placeholders, offset, limit, handlers, statement_string);
500: }
501:
1.17 moko 502: array.confirm_all_used();
503:
1.11 moko 504: if(bind)
505: unmarshal_bind_updates(*bind, placeholders_count, placeholders);
506: }
507:
1.1 moko 508:
1.10 moko 509: static void mid(Request& r, size_t offset=0, size_t limit=ARRAY_OPTION_LIMIT_ALL) {
1.9 moko 510: ArrayValue& array=GET_SELF(r, VArray).array();
1.10 moko 511: if(limit>0){
1.9 moko 512: VArray *result=new VArray;
513: ArrayValue& result_array=result->array();
514: for(ArrayValue::Iterator i(array); i; i.next()){
515: if(i.value()){
1.10 moko 516: if(offset > 0){
517: offset--;
1.9 moko 518: continue;
519: }
1.10 moko 520: if(limit-- == 0)
1.9 moko 521: break;
522: result_array+=i.value();
523: }
524: }
1.17 moko 525: result_array.confirm_all_used();
1.9 moko 526: r.write(*result);
527: } else {
528: r.write(*new VArray);
529: }
530: }
1.1 moko 531:
1.9 moko 532: static void _left(Request& r, MethodParams& params) {
533: int sn=params.as_int(0, "n must be int", r);
1.10 moko 534: mid(r, 0, sn < 0 ? 0 : sn);
1.9 moko 535: }
536:
537: static void _right(Request& r, MethodParams& params) {
538: int sn=params.as_int(0, "n must be int", r);
539:
540: if(sn>0){
541: size_t used=GET_SELF(r, VArray).array().used();
1.20 moko 542: if((size_t)sn<used){
1.10 moko 543: mid(r, used-sn, sn);
1.9 moko 544: } else {
1.10 moko 545: mid(r);
1.9 moko 546: }
547: } else {
1.10 moko 548: mid(r, 0, 0);
1.9 moko 549: }
550: }
551:
552: static void _mid(Request& r, MethodParams& params) {
1.19 moko 553: int begin=params.as_int(0, "p must be int", r);
1.9 moko 554:
555: if(begin<0)
556: throw Exception(PARSER_RUNTIME, 0, "p(%d) must be >=0", begin);
557:
558: if(params.count()>1) {
559: int n=params.as_int(1, "n must be int", r);
560: if(n<0)
561: throw Exception(PARSER_RUNTIME, 0, "n(%d) must be >=0", n);
1.10 moko 562: mid(r, begin, n);
1.9 moko 563: } else {
1.10 moko 564: mid(r, begin);
1.9 moko 565: }
566: }
1.1 moko 567:
568: static void _keys(Request& r, MethodParams& params) {
569: const String* keys_column_name;
570: if(params.count()>0)
571: keys_column_name=¶ms.as_string(0, COLUMN_NAME_MUST_BE_STRING);
572: else
573: keys_column_name=new String("key");
574:
575: Table::columns_type columns(new ArrayString(1));
576: *columns+=keys_column_name;
577: Table* table=new Table(columns);
578:
579: ArrayValue& array=GET_SELF(r, VArray).array();
580: for(ArrayValue::Iterator i(array); i; i.next()){
581: if(i.value()){
582: Table::element_type row(new ArrayString(1));
1.41 moko 583: *row+=new String(pa_uitoa(i.index()));
1.1 moko 584: *table+=row;
585: }
586: }
587:
588: r.write(*new VTable(table));
589: }
590:
1.5 moko 591: static void _count(Request& r, MethodParams& params) {
592: ArrayValue& array=GET_SELF(r, VArray).array();
593: if(params.count()>0){
594: const String& what=params.as_string(0, PARAMETER_MUST_BE_STRING);
595: if(!what.is_empty()){
596: if(what != "all")
597: throw Exception(PARSER_RUNTIME, &what, "param must be empty or 'all'");
598: return r.write(*new VInt(array.count()));
599: }
600: }
601: r.write(*new VInt(array.used()));
1.1 moko 602: }
603:
1.27 moko 604: static void _create_or_append_or_push(Request& r, MethodParams& params) {
1.23 moko 605: ArrayValue& array=GET_SELF(r, VArray).array();
1.2 moko 606:
607: int count=params.count();
608:
1.26 moko 609: if(array.count()){
1.28 moko 610: for(int i=0; i<count; i++){
1.26 moko 611: array+=&r.process(params[i]);
1.28 moko 612: array.change_used(+1); // after each element, since an exception can occur
613: }
1.26 moko 614: } else {
615: for(int i=0; i<count; i++)
616: array+=&r.process(params[i]);
617: array.confirm_all_used();
1.2 moko 618: }
619: }
620:
621: static void _insert(Request& r, MethodParams& params) {
1.23 moko 622: ArrayValue& array=GET_SELF(r, VArray).array();
1.2 moko 623:
624: int count=params.count();
1.7 moko 625: size_t index=VArray::index(params.as_int(0, PARAM_INDEX, r));
1.2 moko 626:
627: for(int i=1; i<count; i++){
1.8 moko 628: array.insert(index++, &r.process(params[i]));
1.28 moko 629: array.change_used(+1); // after each element, since an exception can occur
1.2 moko 630: }
631: }
632:
1.1 moko 633: static void _delete(Request& r, MethodParams& params) {
1.23 moko 634: ArrayValue& array=GET_SELF(r, VArray).array();
1.29 moko 635: if(params.count()>0) {
636: if(params[0].is_string()) {
637: array.clear(VArray::index(*params[0].get_string()));
638: } else {
639: array.clear(VArray::index(params.as_int(0, PARAM_INDEX, r)));
640: }
641: } else
1.23 moko 642: array.clear();
643: array.invalidate();
1.7 moko 644: }
645:
646: static void _remove(Request& r, MethodParams& params) {
1.23 moko 647: ArrayValue& array=GET_SELF(r, VArray).array();
648: array.remove(VArray::index(params.as_int(0, PARAM_INDEX, r)));
649: array.invalidate();
1.1 moko 650: }
651:
1.37 moko 652: static void _pop(Request& r, MethodParams&) {
1.27 moko 653: ArrayValue& array=GET_SELF(r, VArray).array();
654: Value *result=array.pop();
655: if(result){
656: r.write(*result);
1.28 moko 657: array.change_used(-1);
1.27 moko 658: } else {
659: r.write(*VVoid::get());
660: }
661: }
662:
1.1 moko 663: static void _contains(Request& r, MethodParams& params) {
664: VArray& self=GET_SELF(r, VArray);
1.31 moko 665:
666: bool result=self.contains(params[0].is_string() ? VArray::index(*params[0].get_string()) : VArray::index(params.as_int(0, PARAM_INDEX, r)));
1.1 moko 667: r.write(VBool::get(result));
668: }
669:
1.5 moko 670: static void _for(Request& r, MethodParams& params) {
671: InCycle temp(r);
672:
1.7 moko 673: const String* key_var_name=¶ms.as_string(0, "key-var name must be string");
674: const String* value_var_name=¶ms.as_string(1, "value-var name must be string");
675: Value* body_code=¶ms.as_junction(2, "body must be code");
676: Value* delim_maybe_code=params.count()>3 ? ¶ms[3] : 0;
1.5 moko 677: Value& caller=*r.get_method_frame()->caller();
678:
1.7 moko 679: if(key_var_name->is_empty()) key_var_name=0;
1.5 moko 680: if(value_var_name->is_empty()) value_var_name=0;
681:
682: ArrayValue& array=GET_SELF(r, VArray).array();
683:
684: if(delim_maybe_code){ // delimiter set
685: bool need_delim=false;
1.36 moko 686: for(ArrayValue::RobustIterator i(array); i; i.next()){
1.40 moko 687: if(key_var_name)
688: r.put_element(caller, *key_var_name, VString::uitoa(i.index()));
1.5 moko 689: if(value_var_name)
690: r.put_element(caller, *value_var_name, i.value() ? i.value() : VVoid::get());
691:
692: Value& sv_processed=r.process(*body_code);
693: TempSkip4Delimiter skip(r);
694:
695: const String* s_processed=sv_processed.get_string();
696: if(s_processed && !s_processed->is_empty()) { // we have body
697: if(need_delim) // need delim & iteration produced string?
698: r.write(r.process(*delim_maybe_code));
699: else
700: need_delim=true;
701: }
702:
703: r.write(sv_processed);
704:
705: if(skip.check_break())
706: break;
707: }
708: } else {
1.36 moko 709: for(ArrayValue::RobustIterator i(array); i; i.next()){
1.40 moko 710: if(key_var_name)
711: r.put_element(caller, *key_var_name, VString::uitoa(i.index()));
1.5 moko 712: if(value_var_name)
713: r.put_element(caller, *value_var_name, i.value() ? i.value() : VVoid::get());
714:
715: r.process_write(*body_code);
716:
717: if(r.check_skip_break())
718: break;
719: }
720: }
721: }
722:
1.1 moko 723: static void _foreach(Request& r, MethodParams& params) {
724: InCycle temp(r);
725:
726: const String* key_var_name=¶ms.as_string(0, "key-var name must be string");
727: const String* value_var_name=¶ms.as_string(1, "value-var name must be string");
728: Value* body_code=¶ms.as_junction(2, "body must be code");
1.7 moko 729: Value* delim_maybe_code=params.count()>3 ? ¶ms[3] : 0;
1.1 moko 730: Value& caller=*r.get_method_frame()->caller();
731:
732: if(key_var_name->is_empty()) key_var_name=0;
733: if(value_var_name->is_empty()) value_var_name=0;
734:
735: ArrayValue& array=GET_SELF(r, VArray).array();
736:
737: if(delim_maybe_code){ // delimiter set
738: bool need_delim=false;
1.36 moko 739: for(ArrayValue::RobustIterator i(array); i; i.next()){
1.1 moko 740: if(i.value()){
1.40 moko 741: if(key_var_name)
742: r.put_element(caller, *key_var_name, VString::uitoa(i.index()));
1.1 moko 743: if(value_var_name)
744: r.put_element(caller, *value_var_name, i.value());
745:
746: Value& sv_processed=r.process(*body_code);
747: TempSkip4Delimiter skip(r);
748:
749: const String* s_processed=sv_processed.get_string();
750: if(s_processed && !s_processed->is_empty()) { // we have body
751: if(need_delim) // need delim & iteration produced string?
752: r.write(r.process(*delim_maybe_code));
753: else
754: need_delim=true;
755: }
756:
757: r.write(sv_processed);
758:
759: if(skip.check_break())
760: break;
761: }
762: }
763: } else {
1.36 moko 764: for(ArrayValue::RobustIterator i(array); i; i.next()){
1.1 moko 765: if(i.value()){
1.40 moko 766: if(key_var_name)
767: r.put_element(caller, *key_var_name, VString::uitoa(i.index()));
1.1 moko 768: if(value_var_name)
769: r.put_element(caller, *value_var_name, i.value());
770:
771: r.process_write(*body_code);
772:
773: if(r.check_skip_break())
774: break;
775: }
776: }
777: }
778: }
779:
780: #ifndef DOXYGEN
781: struct Array_seq_item : public PA_Allocated {
782: Value *array_data;
783: union {
784: const char *c_str;
785: double d;
786: } value;
787: };
788: #endif
789:
790: static int sort_cmp_string(const void *a, const void *b) {
791: return strcmp(
792: static_cast<const Array_seq_item *>(a)->value.c_str,
793: static_cast<const Array_seq_item *>(b)->value.c_str
794: );
795: }
796: static int sort_cmp_double(const void *a, const void *b) {
797: double va=static_cast<const Array_seq_item *>(a)->value.d;
798: double vb=static_cast<const Array_seq_item *>(b)->value.d;
799: if(va<vb)
800: return -1;
801: else if(va>vb)
802: return +1;
803: else
804: return 0;
805: }
806:
807: static void _sort(Request& r, MethodParams& params){
808: const String& key_var_name=params.as_string(0, "key-var name must be string");
809: const String& value_var_name=params.as_string(1, "value-var name must be string");
810: Value& key_maker=params.as_junction(2, "key-maker must be code");
811: bool reverse=params.count()>3 && params.as_no_junction(3, "order must not be code").as_string()=="desc"; // default=asc
812:
813: const String* key_var=key_var_name.is_empty()? 0 : &key_var_name;
814: const String* value_var=value_var_name.is_empty()? 0 : &value_var_name;
815: VMethodFrame* context=r.get_method_frame()->caller();
816:
817: VArray& self=GET_SELF(r, VArray);
818: ArrayValue& array=self.array();
1.4 moko 819: int count=array.used(); // not array.count()
1.1 moko 820:
821: Array_seq_item* seq=new Array_seq_item[count];
822: int pos=0;
823: bool key_values_are_strings=true;
824:
1.36 moko 825: for(ArrayValue::RobustIterator i(array); i; i.next() ){
1.1 moko 826: if(i.value()){
827: if(key_var)
1.40 moko 828: r.put_element(*context, *key_var, VString::uitoa(i.index()));
1.1 moko 829: if(value_var)
830: r.put_element(*context, *value_var, i.value());
831:
832: Value& value=r.process(key_maker);
833: if(pos==0) // determining key values type by first one
834: key_values_are_strings=value.is_string();
835:
836: seq[pos].array_data=i.value();
837: if(key_values_are_strings)
838: seq[pos++].value.c_str=value.as_string().cstr();
839: else
840: seq[pos++].value.d=value.as_expr_result().as_double();
841: }
842: }
843:
844: // @todo: handle this elsewhere
845: if(r.charsets.source().NAME()=="KOI8-R" && key_values_are_strings)
846: for(pos=0; pos<count; pos++)
847: if(*seq[pos].value.c_str)
848: seq[pos].value.c_str=Charset::transcode(seq[pos].value.c_str, r.charsets.source(), pa_UTF8_charset).cstr();
849:
850: // sort keys
851: qsort(seq, count, sizeof(Array_seq_item), key_values_are_strings ? sort_cmp_string : sort_cmp_double);
852:
853: // reorder array as required in 'seq'
854: array.clear();
855: if(reverse)
856: for(pos=count-1; pos>=0; pos--)
857: array+=seq[pos].array_data;
858: else
859: for(pos=0; pos<count; pos++)
860: array+=seq[pos].array_data;
861:
1.26 moko 862: array.confirm_all_used();
1.1 moko 863: delete[] seq;
864: }
865:
1.5 moko 866: enum AtResultType {
867: AtResultTypeValue = 0,
868: AtResultTypeKey = 1,
869: AtResultTypeHash = 2
870: };
871:
1.16 moko 872: static Value& SingleElementHash(String::Body akey, Value* avalue) {
1.5 moko 873: Value& result=*new VHash;
874: result.put_element(*new String(akey, String::L_TAINTED), avalue);
875: return result;
876: }
877:
1.1 moko 878: static void _at(Request& r, MethodParams& params) {
1.26 moko 879: ArrayValue& array=GET_SELF(r, VArray).array();
1.5 moko 880: size_t count=array.used(); // not array.count()
1.1 moko 881:
882: AtResultType result_type=AtResultTypeValue;
883: if(params.count() > 1) {
884: const String& stype=params.as_string(1, "type must be string");
885: if(stype == "key")
886: result_type=AtResultTypeKey;
887: else if(stype == "hash")
888: result_type=AtResultTypeHash;
889: else if(stype != "value")
890: throw Exception(PARSER_RUNTIME, &stype, "type must be 'key', 'value' or 'hash'");
891: }
892:
1.30 moko 893: int pos=params.as_index(0, count, r);
1.1 moko 894:
895: if(count && pos >= 0 && (size_t)pos < count){
1.21 moko 896: if(count == array.count()){
1.31 moko 897: l1: switch(result_type) {
1.21 moko 898: case AtResultTypeKey:
1.40 moko 899: r.write(*VString::uitoa(pos));
1.21 moko 900: break;
901: case AtResultTypeValue:
1.25 moko 902: r.write(*array.get(pos));
1.21 moko 903: break;
904: case AtResultTypeHash:
1.38 moko 905: r.write(SingleElementHash(String::Body::uitoa(pos), array.get(pos)));
1.21 moko 906: break;
907: }
1.31 moko 908: } else if((size_t)pos == count-1){
909: pos=array.count()-1;
910: goto l1;
1.21 moko 911: } else {
912: for(ArrayValue::Iterator i(array); i; i.next() ){
913: if(i.value() && !(pos--)){
914: switch(result_type) {
915: case AtResultTypeKey:
1.40 moko 916: r.write(*VString::uitoa(i.index()));
1.1 moko 917: break;
1.21 moko 918: case AtResultTypeValue:
1.1 moko 919: r.write(*i.value());
920: break;
1.21 moko 921: case AtResultTypeHash:
1.38 moko 922: r.write(SingleElementHash(String::Body::uitoa(i.index()), i.value()));
1.1 moko 923: break;
1.21 moko 924: }
1.1 moko 925: break;
926: }
1.21 moko 927: }
1.1 moko 928: }
929: }
930: }
931:
1.31 moko 932: static void _set(Request& r, MethodParams& params) {
933: ArrayValue& array=GET_SELF(r, VArray).array();
934: size_t count=array.used(); // not array.count()
935:
936: int pos=params.as_index(0, count, r);
937:
938: if(count && pos >= 0 && (size_t)pos < count){
939: if(count == array.count()){
940: array.put(pos, &r.process(params[1]));
941: return;
942: } else if((size_t)pos == count-1){
943: array.put(array.count()-1, &r.process(params[1]));
944: return;
945: } else {
946: for(ArrayValue::Iterator i(array); i; i.next() ){
947: if(i.value() && !(pos--)){
1.32 moko 948: array.put(i.index(), &r.process(params[1]));
1.31 moko 949: return;
950: }
951: }
952: }
953: }
954:
955: if(count)
956: throw Exception(PARSER_RUNTIME, 0, "index '%d' is out of range 0..%d", pos, count-1);
957: throw Exception(PARSER_RUNTIME, 0, "index '%d' is out of range: array is empty", pos);
958: }
1.1 moko 959:
960: extern String table_reverse_name;
961:
962: static void _select(Request& r, MethodParams& params) {
963: InCycle temp(r);
964: const String* key_var_name=¶ms.as_string(0, "key-var name must be string");
965: const String* value_var_name=¶ms.as_string(1, "value-var name must be string");
966: Value& vcondition=params.as_expression(2, "condition must be number, bool or expression");
967:
968: if(key_var_name->is_empty()) key_var_name=0;
969: if(value_var_name->is_empty()) value_var_name=0;
970:
971: ArrayValue& source_array=GET_SELF(r, VArray).array();
972: Value& caller=*r.get_method_frame()->caller();
973:
974: int limit=source_array.count();
975: bool reverse=false;
976:
977: if(params.count()>3)
978: if(HashStringValue* options=params.as_hash(3)) {
979: int valid_options=0;
980: if(Value* vlimit=options->get(sql_limit_name)) {
981: valid_options++;
982: limit=r.process(*vlimit).as_int();
983: }
984: if(Value* vreverse=options->get(table_reverse_name)) {
985: valid_options++;
986: reverse=r.process(*vreverse).as_bool();
987: }
988: if(valid_options!=options->count())
989: throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION);
990: }
991:
992: VArray *result=new VArray;
993: ArrayValue& result_array=result->array();
994:
995: if(limit>0){
996: if(reverse){
997: for(ArrayValue::ReverseIterator i(source_array); i; ){
1.5 moko 998: if(Value *value=i.prev()){ // here for correct i.key()
999: if(key_var_name)
1.40 moko 1000: r.put_element(caller, *key_var_name, VString::uitoa(i.index()));
1.5 moko 1001: if(value_var_name)
1002: r.put_element(caller, *value_var_name, value);
1.1 moko 1003:
1.5 moko 1004: bool condition=r.process(vcondition).as_bool();
1.1 moko 1005:
1.5 moko 1006: if(r.check_skip_break())
1007: break;
1.1 moko 1008:
1.5 moko 1009: if(condition){
1010: result_array+=value;
1011: if(!--limit)
1012: break;
1013: }
1.1 moko 1014: }
1015: }
1016: } else {
1.36 moko 1017: for(ArrayValue::RobustIterator i(source_array); i; i.next() ){
1.5 moko 1018: if(Value *value=i.value()){
1.1 moko 1019: if(key_var_name)
1.40 moko 1020: r.put_element(caller, *key_var_name, VString::uitoa(i.index()));
1.1 moko 1021: if(value_var_name)
1022: r.put_element(caller, *value_var_name, value);
1023:
1024: bool condition=r.process(vcondition).as_bool();
1025:
1026: if(r.check_skip_break())
1027: break;
1028:
1029: if(condition){
1030: result_array+=value;
1031: if(!--limit)
1032: break;
1033: }
1034: }
1035: }
1036: }
1037: }
1038:
1.17 moko 1039: result_array.confirm_all_used();
1.1 moko 1040: r.write(*result);
1041: }
1042:
1.19 moko 1043: static void _reverse(Request& r, MethodParams&) {
1.5 moko 1044: ArrayValue& source_array=GET_SELF(r, VArray).array();
1.1 moko 1045:
1.5 moko 1046: VArray& result=*new VArray(source_array.count());
1.1 moko 1047: ArrayValue& result_array=result.array();
1048:
1.4 moko 1049: for(ArrayValue::ReverseIterator i(source_array); i; ){
1050: result_array+=i.prev();
1.1 moko 1051: }
1052:
1053: r.write(result);
1054: }
1055:
1.24 moko 1056: static void _compact(Request& r, MethodParams& params) {
1057: bool compact_undef=false;
1058: if(params.count()>0){
1059: const String& what=params.as_string(0, PARAMETER_MUST_BE_STRING);
1060: if(!what.is_empty()){
1061: if(what != "undef")
1062: throw Exception(PARSER_RUNTIME, &what, "param must be empty or 'undef'");
1063: compact_undef=true;
1064: }
1065: }
1.26 moko 1066: ArrayValue& array=GET_SELF(r, VArray).array();
1067: array.compact(compact_undef);
1068: array.confirm_all_used();
1.22 moko 1069: }
1070:
1.1 moko 1071:
1072: // constructor
1073:
1074: MArray::MArray(): Methoded(VARRAY_TYPE) {
1075:
1.18 moko 1076: // ^array::copy[[copy_from]]
1.25 moko 1077: add_native_method("copy", Method::CT_DYNAMIC, _copy_or_add, 0, 1);
1.1 moko 1078: // ^array.add[add_from]
1.25 moko 1079: add_native_method("add", Method::CT_DYNAMIC, _copy_or_add, 1, 1);
1.6 moko 1080: // ^array.join[join_from[;options]]
1081: add_native_method("join", Method::CT_DYNAMIC, _join, 1, 2);
1.1 moko 1082:
1.27 moko 1083: // ^array::create[value[;value...]]
1084: add_native_method("create", Method::CT_DYNAMIC, _create_or_append_or_push, 0, 10000);
1085: // ^array.append[value[;value...]]
1086: add_native_method("append", Method::CT_DYNAMIC, _create_or_append_or_push, 1, 10000);
1087: // ^array.push[value[;value...]]
1088: add_native_method("push", Method::CT_DYNAMIC, _create_or_append_or_push, 1, 10000);
1.31 moko 1089: // ^array.insert(index)[value[;value...]]
1.18 moko 1090: add_native_method("insert", Method::CT_DYNAMIC, _insert, 2, 10000);
1091:
1.9 moko 1092: // ^array.left(n)
1093: add_native_method("left", Method::CT_DYNAMIC, _left, 1, 1);
1094: // ^array.right(n)
1095: add_native_method("right", Method::CT_DYNAMIC, _right, 1, 1);
1096: // ^array.mid(p)
1097: // ^array.mid(p;n)
1098: add_native_method("mid", Method::CT_DYNAMIC, _mid, 1, 2);
1.1 moko 1099:
1.29 moko 1100: // ^array.delete(index) or ^array.delete[index]
1.1 moko 1101: add_native_method("delete", Method::CT_DYNAMIC, _delete, 0, 1);
1.29 moko 1102: // ^array.remove(index)
1.7 moko 1103: add_native_method("remove", Method::CT_DYNAMIC, _remove, 1, 1);
1.27 moko 1104: // ^array.pop[]
1105: add_native_method("pop", Method::CT_DYNAMIC, _pop, 0, 0);
1.1 moko 1106:
1.31 moko 1107: // ^array.contains(index) ^array.contains[index]
1.1 moko 1108: add_native_method("contains", Method::CT_DYNAMIC, _contains, 1, 1);
1109:
1110: // ^array::sql[query][options array]
1111: add_native_method("sql", Method::CT_DYNAMIC, _sql, 1, 2);
1112:
1113: // ^array._keys[[column name]]
1114: add_native_method("_keys", Method::CT_DYNAMIC, _keys, 0, 1);
1115:
1.5 moko 1116: // ^array._count[[all]]
1117: add_native_method("_count", Method::CT_DYNAMIC, _count, 0, 1);
1.1 moko 1118:
1.7 moko 1119: // ^array.for[index;value]{code}[delim]
1120: add_native_method("for", Method::CT_DYNAMIC, _for, 3, 3+1);
1.2 moko 1121: // ^array.foreach[index;value]{code}[delim]
1.7 moko 1122: add_native_method("foreach", Method::CT_DYNAMIC, _foreach, 3, 3+1);
1.1 moko 1123:
1.2 moko 1124: // ^array.sort[index;value]{string-key-maker}[[asc|desc]]
1125: // ^array.sort[index;value](numeric-key-maker)[[asc|desc]]
1.1 moko 1126: add_native_method("sort", Method::CT_DYNAMIC, _sort, 3, 4);
1127:
1.2 moko 1128: // ^array.select[index;value](bool-condition)[options hash]
1.1 moko 1129: add_native_method("select", Method::CT_DYNAMIC, _select, 3, 4);
1130:
1131: // ^array.reverse[]
1132: add_native_method("reverse", Method::CT_DYNAMIC, _reverse, 0, 0);
1133:
1.24 moko 1134: // ^array.compact[[undef]]
1135: add_native_method("compact", Method::CT_DYNAMIC, _compact, 0, 1);
1.22 moko 1136:
1.2 moko 1137: // ^array._at[first|last[;'key'|'value'|'hash']]
1.31 moko 1138: // ^array._at([-+]index)[['key'|'value'|'hash']]
1.1 moko 1139: add_native_method("_at", Method::CT_DYNAMIC, _at, 1, 2);
1140:
1.31 moko 1141: // ^array.set[first|last;value]
1142: // ^array.set([-+]index)[value]
1143: add_native_method("set", Method::CT_DYNAMIC, _set, 2, 2);
1144:
1.1 moko 1145: #ifdef FEATURE_GET_ELEMENT4CALL
1146: // aliases without "_"
1147: add_native_method("keys", Method::CT_DYNAMIC, _keys, 0, 1);
1.14 moko 1148: add_native_method("count", Method::CT_DYNAMIC, _count, 0, 1);
1.1 moko 1149: add_native_method("at", Method::CT_DYNAMIC, _at, 1, 2);
1150: #endif
1151:
1152: }