|
|
1.1 moko 1: /** @file
2: Parser: @b array parser class.
3:
1.35 moko 4: Copyright (c) 2001-2024 Art. Lebedev Studio (http://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.40 ! moko 20: volatile const char * IDENT_ARRAY_C="$Id: array.C,v 1.39 2025/05/26 01:56:54 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")) {
455: int valid_options=0;
1.13 moko 456: bool distinct_specified=false;
1.11 moko 457: for(HashStringValue::Iterator i(*options); i; i.next() ){
458: String::Body key=i.key();
459: Value* value=i.value();
460: if(key == sql_bind_name) {
461: bind=value->get_hash();
462: valid_options++;
463: } else if(key == sql_limit_name) {
464: limit=(ulong)r.process(*value).as_double();
465: valid_options++;
466: } else if(key == sql_offset_name) {
467: offset=(ulong)r.process(*value).as_double();
468: valid_options++;
469: } else if (key == sql_distinct_name) {
470: distinct=r.process(*value).as_bool();
1.13 moko 471: distinct_specified=true;
1.11 moko 472: valid_options++;
473: } else if (key == sql_value_type_name) {
1.12 moko 474: value_type=get_value_type(r.process(*value));
1.11 moko 475: valid_options++;
476: } else if (key == "sparse") {
1.12 moko 477: sparse=r.process(*value).as_bool();
1.11 moko 478: valid_options++;
479: }
480: }
481: if(valid_options!=options->count())
482: throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION);
1.13 moko 483: if(distinct_specified && !sparse)
484: throw Exception(PARSER_RUNTIME, 0, "'distinct' option can only be used when $.sparse(true) is specified");
1.11 moko 485: }
486:
487: SQL_Driver::Placeholder* placeholders=0;
488: uint placeholders_count=0;
489: if(bind)
490: placeholders_count=marshal_binds(*bind, placeholders);
491:
492: const String& statement_string=r.process_to_string(statement);
493: const char* statement_cstr=statement_string.untaint_cstr(String::L_SQL, r.connection());
494:
495: VArray& self=GET_SELF(r, VArray);
1.17 moko 496: ArrayValue& array=self.array();
1.13 moko 497:
1.23 moko 498: if(array.count()){
499: array.clear(); array.invalidate(); // just in case if called as method
500: }
1.11 moko 501:
502: if(sparse){
1.17 moko 503: SparseArray_sql_event_handlers handlers(distinct, array, value_type);
1.11 moko 504: r.connection()->query(statement_cstr, placeholders_count, placeholders, offset, limit, handlers, statement_string);
505: } else {
1.17 moko 506: Array_sql_event_handlers handlers(array, value_type);
1.11 moko 507: r.connection()->query(statement_cstr, placeholders_count, placeholders, offset, limit, handlers, statement_string);
508: }
509:
1.17 moko 510: array.confirm_all_used();
511:
1.11 moko 512: if(bind)
513: unmarshal_bind_updates(*bind, placeholders_count, placeholders);
514: }
515:
1.1 moko 516:
1.10 moko 517: static void mid(Request& r, size_t offset=0, size_t limit=ARRAY_OPTION_LIMIT_ALL) {
1.9 moko 518: ArrayValue& array=GET_SELF(r, VArray).array();
1.10 moko 519: if(limit>0){
1.9 moko 520: VArray *result=new VArray;
521: ArrayValue& result_array=result->array();
522: for(ArrayValue::Iterator i(array); i; i.next()){
523: if(i.value()){
1.10 moko 524: if(offset > 0){
525: offset--;
1.9 moko 526: continue;
527: }
1.10 moko 528: if(limit-- == 0)
1.9 moko 529: break;
530: result_array+=i.value();
531: }
532: }
1.17 moko 533: result_array.confirm_all_used();
1.9 moko 534: r.write(*result);
535: } else {
536: r.write(*new VArray);
537: }
538: }
1.1 moko 539:
1.9 moko 540: static void _left(Request& r, MethodParams& params) {
541: int sn=params.as_int(0, "n must be int", r);
1.10 moko 542: mid(r, 0, sn < 0 ? 0 : sn);
1.9 moko 543: }
544:
545: static void _right(Request& r, MethodParams& params) {
546: int sn=params.as_int(0, "n must be int", r);
547:
548: if(sn>0){
549: size_t used=GET_SELF(r, VArray).array().used();
1.20 moko 550: if((size_t)sn<used){
1.10 moko 551: mid(r, used-sn, sn);
1.9 moko 552: } else {
1.10 moko 553: mid(r);
1.9 moko 554: }
555: } else {
1.10 moko 556: mid(r, 0, 0);
1.9 moko 557: }
558: }
559:
560: static void _mid(Request& r, MethodParams& params) {
1.19 moko 561: int begin=params.as_int(0, "p must be int", r);
1.9 moko 562:
563: if(begin<0)
564: throw Exception(PARSER_RUNTIME, 0, "p(%d) must be >=0", begin);
565:
566: if(params.count()>1) {
567: int n=params.as_int(1, "n must be int", r);
568: if(n<0)
569: throw Exception(PARSER_RUNTIME, 0, "n(%d) must be >=0", n);
1.10 moko 570: mid(r, begin, n);
1.9 moko 571: } else {
1.10 moko 572: mid(r, begin);
1.9 moko 573: }
574: }
1.1 moko 575:
576: static void _keys(Request& r, MethodParams& params) {
577: const String* keys_column_name;
578: if(params.count()>0)
579: keys_column_name=¶ms.as_string(0, COLUMN_NAME_MUST_BE_STRING);
580: else
581: keys_column_name=new String("key");
582:
583: Table::columns_type columns(new ArrayString(1));
584: *columns+=keys_column_name;
585: Table* table=new Table(columns);
586:
587: ArrayValue& array=GET_SELF(r, VArray).array();
588: for(ArrayValue::Iterator i(array); i; i.next()){
589: if(i.value()){
590: Table::element_type row(new ArrayString(1));
591: *row+=new String(i.key(), String::L_TAINTED);
592: *table+=row;
593: }
594: }
595:
596: r.write(*new VTable(table));
597: }
598:
1.5 moko 599: static void _count(Request& r, MethodParams& params) {
600: ArrayValue& array=GET_SELF(r, VArray).array();
601: if(params.count()>0){
602: const String& what=params.as_string(0, PARAMETER_MUST_BE_STRING);
603: if(!what.is_empty()){
604: if(what != "all")
605: throw Exception(PARSER_RUNTIME, &what, "param must be empty or 'all'");
606: return r.write(*new VInt(array.count()));
607: }
608: }
609: r.write(*new VInt(array.used()));
1.1 moko 610: }
611:
1.27 moko 612: static void _create_or_append_or_push(Request& r, MethodParams& params) {
1.23 moko 613: ArrayValue& array=GET_SELF(r, VArray).array();
1.2 moko 614:
615: int count=params.count();
616:
1.26 moko 617: if(array.count()){
1.28 moko 618: for(int i=0; i<count; i++){
1.26 moko 619: array+=&r.process(params[i]);
1.28 moko 620: array.change_used(+1); // after each element, since an exception can occur
621: }
1.26 moko 622: } else {
623: for(int i=0; i<count; i++)
624: array+=&r.process(params[i]);
625: array.confirm_all_used();
1.2 moko 626: }
627: }
628:
629: static void _insert(Request& r, MethodParams& params) {
1.23 moko 630: ArrayValue& array=GET_SELF(r, VArray).array();
1.2 moko 631:
632: int count=params.count();
1.7 moko 633: size_t index=VArray::index(params.as_int(0, PARAM_INDEX, r));
1.2 moko 634:
635: for(int i=1; i<count; i++){
1.8 moko 636: array.insert(index++, &r.process(params[i]));
1.28 moko 637: array.change_used(+1); // after each element, since an exception can occur
1.2 moko 638: }
639: }
640:
1.1 moko 641: static void _delete(Request& r, MethodParams& params) {
1.23 moko 642: ArrayValue& array=GET_SELF(r, VArray).array();
1.29 moko 643: if(params.count()>0) {
644: if(params[0].is_string()) {
645: array.clear(VArray::index(*params[0].get_string()));
646: } else {
647: array.clear(VArray::index(params.as_int(0, PARAM_INDEX, r)));
648: }
649: } else
1.23 moko 650: array.clear();
651: array.invalidate();
1.7 moko 652: }
653:
654: static void _remove(Request& r, MethodParams& params) {
1.23 moko 655: ArrayValue& array=GET_SELF(r, VArray).array();
656: array.remove(VArray::index(params.as_int(0, PARAM_INDEX, r)));
657: array.invalidate();
1.1 moko 658: }
659:
1.37 moko 660: static void _pop(Request& r, MethodParams&) {
1.27 moko 661: ArrayValue& array=GET_SELF(r, VArray).array();
662: Value *result=array.pop();
663: if(result){
664: r.write(*result);
1.28 moko 665: array.change_used(-1);
1.27 moko 666: } else {
667: r.write(*VVoid::get());
668: }
669: }
670:
1.1 moko 671: static void _contains(Request& r, MethodParams& params) {
672: VArray& self=GET_SELF(r, VArray);
1.31 moko 673:
674: 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 675: r.write(VBool::get(result));
676: }
677:
1.5 moko 678: static void _for(Request& r, MethodParams& params) {
679: InCycle temp(r);
680:
1.7 moko 681: const String* key_var_name=¶ms.as_string(0, "key-var name must be string");
682: const String* value_var_name=¶ms.as_string(1, "value-var name must be string");
683: Value* body_code=¶ms.as_junction(2, "body must be code");
684: Value* delim_maybe_code=params.count()>3 ? ¶ms[3] : 0;
1.5 moko 685: Value& caller=*r.get_method_frame()->caller();
686:
1.7 moko 687: if(key_var_name->is_empty()) key_var_name=0;
1.5 moko 688: if(value_var_name->is_empty()) value_var_name=0;
689:
690: ArrayValue& array=GET_SELF(r, VArray).array();
691:
692: if(delim_maybe_code){ // delimiter set
693: bool need_delim=false;
1.36 moko 694: for(ArrayValue::RobustIterator i(array); i; i.next()){
1.40 ! moko 695: if(key_var_name)
! 696: r.put_element(caller, *key_var_name, VString::uitoa(i.index()));
1.5 moko 697: if(value_var_name)
698: r.put_element(caller, *value_var_name, i.value() ? i.value() : VVoid::get());
699:
700: Value& sv_processed=r.process(*body_code);
701: TempSkip4Delimiter skip(r);
702:
703: const String* s_processed=sv_processed.get_string();
704: if(s_processed && !s_processed->is_empty()) { // we have body
705: if(need_delim) // need delim & iteration produced string?
706: r.write(r.process(*delim_maybe_code));
707: else
708: need_delim=true;
709: }
710:
711: r.write(sv_processed);
712:
713: if(skip.check_break())
714: break;
715: }
716: } else {
1.36 moko 717: for(ArrayValue::RobustIterator i(array); i; i.next()){
1.40 ! moko 718: if(key_var_name)
! 719: r.put_element(caller, *key_var_name, VString::uitoa(i.index()));
1.5 moko 720: if(value_var_name)
721: r.put_element(caller, *value_var_name, i.value() ? i.value() : VVoid::get());
722:
723: r.process_write(*body_code);
724:
725: if(r.check_skip_break())
726: break;
727: }
728: }
729: }
730:
1.1 moko 731: static void _foreach(Request& r, MethodParams& params) {
732: InCycle temp(r);
733:
734: const String* key_var_name=¶ms.as_string(0, "key-var name must be string");
735: const String* value_var_name=¶ms.as_string(1, "value-var name must be string");
736: Value* body_code=¶ms.as_junction(2, "body must be code");
1.7 moko 737: Value* delim_maybe_code=params.count()>3 ? ¶ms[3] : 0;
1.1 moko 738: Value& caller=*r.get_method_frame()->caller();
739:
740: if(key_var_name->is_empty()) key_var_name=0;
741: if(value_var_name->is_empty()) value_var_name=0;
742:
743: ArrayValue& array=GET_SELF(r, VArray).array();
744:
745: if(delim_maybe_code){ // delimiter set
746: bool need_delim=false;
1.36 moko 747: for(ArrayValue::RobustIterator i(array); i; i.next()){
1.1 moko 748: if(i.value()){
1.40 ! moko 749: if(key_var_name)
! 750: r.put_element(caller, *key_var_name, VString::uitoa(i.index()));
1.1 moko 751: if(value_var_name)
752: r.put_element(caller, *value_var_name, i.value());
753:
754: Value& sv_processed=r.process(*body_code);
755: TempSkip4Delimiter skip(r);
756:
757: const String* s_processed=sv_processed.get_string();
758: if(s_processed && !s_processed->is_empty()) { // we have body
759: if(need_delim) // need delim & iteration produced string?
760: r.write(r.process(*delim_maybe_code));
761: else
762: need_delim=true;
763: }
764:
765: r.write(sv_processed);
766:
767: if(skip.check_break())
768: break;
769: }
770: }
771: } else {
1.36 moko 772: for(ArrayValue::RobustIterator i(array); i; i.next()){
1.1 moko 773: if(i.value()){
1.40 ! moko 774: if(key_var_name)
! 775: r.put_element(caller, *key_var_name, VString::uitoa(i.index()));
1.1 moko 776: if(value_var_name)
777: r.put_element(caller, *value_var_name, i.value());
778:
779: r.process_write(*body_code);
780:
781: if(r.check_skip_break())
782: break;
783: }
784: }
785: }
786: }
787:
788: #ifndef DOXYGEN
789: struct Array_seq_item : public PA_Allocated {
790: Value *array_data;
791: union {
792: const char *c_str;
793: double d;
794: } value;
795: };
796: #endif
797:
798: static int sort_cmp_string(const void *a, const void *b) {
799: return strcmp(
800: static_cast<const Array_seq_item *>(a)->value.c_str,
801: static_cast<const Array_seq_item *>(b)->value.c_str
802: );
803: }
804: static int sort_cmp_double(const void *a, const void *b) {
805: double va=static_cast<const Array_seq_item *>(a)->value.d;
806: double vb=static_cast<const Array_seq_item *>(b)->value.d;
807: if(va<vb)
808: return -1;
809: else if(va>vb)
810: return +1;
811: else
812: return 0;
813: }
814:
815: static void _sort(Request& r, MethodParams& params){
816: const String& key_var_name=params.as_string(0, "key-var name must be string");
817: const String& value_var_name=params.as_string(1, "value-var name must be string");
818: Value& key_maker=params.as_junction(2, "key-maker must be code");
819: bool reverse=params.count()>3 && params.as_no_junction(3, "order must not be code").as_string()=="desc"; // default=asc
820:
821: const String* key_var=key_var_name.is_empty()? 0 : &key_var_name;
822: const String* value_var=value_var_name.is_empty()? 0 : &value_var_name;
823: VMethodFrame* context=r.get_method_frame()->caller();
824:
825: VArray& self=GET_SELF(r, VArray);
826: ArrayValue& array=self.array();
1.4 moko 827: int count=array.used(); // not array.count()
1.1 moko 828:
829: Array_seq_item* seq=new Array_seq_item[count];
830: int pos=0;
831: bool key_values_are_strings=true;
832:
1.36 moko 833: for(ArrayValue::RobustIterator i(array); i; i.next() ){
1.1 moko 834: if(i.value()){
835: if(key_var)
1.40 ! moko 836: r.put_element(*context, *key_var, VString::uitoa(i.index()));
1.1 moko 837: if(value_var)
838: r.put_element(*context, *value_var, i.value());
839:
840: Value& value=r.process(key_maker);
841: if(pos==0) // determining key values type by first one
842: key_values_are_strings=value.is_string();
843:
844: seq[pos].array_data=i.value();
845: if(key_values_are_strings)
846: seq[pos++].value.c_str=value.as_string().cstr();
847: else
848: seq[pos++].value.d=value.as_expr_result().as_double();
849: }
850: }
851:
852: // @todo: handle this elsewhere
853: if(r.charsets.source().NAME()=="KOI8-R" && key_values_are_strings)
854: for(pos=0; pos<count; pos++)
855: if(*seq[pos].value.c_str)
856: seq[pos].value.c_str=Charset::transcode(seq[pos].value.c_str, r.charsets.source(), pa_UTF8_charset).cstr();
857:
858: // sort keys
859: qsort(seq, count, sizeof(Array_seq_item), key_values_are_strings ? sort_cmp_string : sort_cmp_double);
860:
861: // reorder array as required in 'seq'
862: array.clear();
863: if(reverse)
864: for(pos=count-1; pos>=0; pos--)
865: array+=seq[pos].array_data;
866: else
867: for(pos=0; pos<count; pos++)
868: array+=seq[pos].array_data;
869:
1.26 moko 870: array.confirm_all_used();
1.1 moko 871: delete[] seq;
872: }
873:
1.5 moko 874: enum AtResultType {
875: AtResultTypeValue = 0,
876: AtResultTypeKey = 1,
877: AtResultTypeHash = 2
878: };
879:
1.16 moko 880: static Value& SingleElementHash(String::Body akey, Value* avalue) {
1.5 moko 881: Value& result=*new VHash;
882: result.put_element(*new String(akey, String::L_TAINTED), avalue);
883: return result;
884: }
885:
1.1 moko 886: static void _at(Request& r, MethodParams& params) {
1.26 moko 887: ArrayValue& array=GET_SELF(r, VArray).array();
1.5 moko 888: size_t count=array.used(); // not array.count()
1.1 moko 889:
890: AtResultType result_type=AtResultTypeValue;
891: if(params.count() > 1) {
892: const String& stype=params.as_string(1, "type must be string");
893: if(stype == "key")
894: result_type=AtResultTypeKey;
895: else if(stype == "hash")
896: result_type=AtResultTypeHash;
897: else if(stype != "value")
898: throw Exception(PARSER_RUNTIME, &stype, "type must be 'key', 'value' or 'hash'");
899: }
900:
1.30 moko 901: int pos=params.as_index(0, count, r);
1.1 moko 902:
903: if(count && pos >= 0 && (size_t)pos < count){
1.21 moko 904: if(count == array.count()){
1.31 moko 905: l1: switch(result_type) {
1.21 moko 906: case AtResultTypeKey:
1.40 ! moko 907: r.write(*VString::uitoa(pos));
1.21 moko 908: break;
909: case AtResultTypeValue:
1.25 moko 910: r.write(*array.get(pos));
1.21 moko 911: break;
912: case AtResultTypeHash:
1.38 moko 913: r.write(SingleElementHash(String::Body::uitoa(pos), array.get(pos)));
1.21 moko 914: break;
915: }
1.31 moko 916: } else if((size_t)pos == count-1){
917: pos=array.count()-1;
918: goto l1;
1.21 moko 919: } else {
920: for(ArrayValue::Iterator i(array); i; i.next() ){
921: if(i.value() && !(pos--)){
922: switch(result_type) {
923: case AtResultTypeKey:
1.40 ! moko 924: r.write(*VString::uitoa(i.index()));
1.1 moko 925: break;
1.21 moko 926: case AtResultTypeValue:
1.1 moko 927: r.write(*i.value());
928: break;
1.21 moko 929: case AtResultTypeHash:
1.38 moko 930: r.write(SingleElementHash(String::Body::uitoa(i.index()), i.value()));
1.1 moko 931: break;
1.21 moko 932: }
1.1 moko 933: break;
934: }
1.21 moko 935: }
1.1 moko 936: }
937: }
938: }
939:
1.31 moko 940: static void _set(Request& r, MethodParams& params) {
941: ArrayValue& array=GET_SELF(r, VArray).array();
942: size_t count=array.used(); // not array.count()
943:
944: int pos=params.as_index(0, count, r);
945:
946: if(count && pos >= 0 && (size_t)pos < count){
947: if(count == array.count()){
948: array.put(pos, &r.process(params[1]));
949: return;
950: } else if((size_t)pos == count-1){
951: array.put(array.count()-1, &r.process(params[1]));
952: return;
953: } else {
954: for(ArrayValue::Iterator i(array); i; i.next() ){
955: if(i.value() && !(pos--)){
1.32 moko 956: array.put(i.index(), &r.process(params[1]));
1.31 moko 957: return;
958: }
959: }
960: }
961: }
962:
963: if(count)
964: throw Exception(PARSER_RUNTIME, 0, "index '%d' is out of range 0..%d", pos, count-1);
965: throw Exception(PARSER_RUNTIME, 0, "index '%d' is out of range: array is empty", pos);
966: }
1.1 moko 967:
968: extern String table_reverse_name;
969:
970: static void _select(Request& r, MethodParams& params) {
971: InCycle temp(r);
972: const String* key_var_name=¶ms.as_string(0, "key-var name must be string");
973: const String* value_var_name=¶ms.as_string(1, "value-var name must be string");
974: Value& vcondition=params.as_expression(2, "condition must be number, bool or expression");
975:
976: if(key_var_name->is_empty()) key_var_name=0;
977: if(value_var_name->is_empty()) value_var_name=0;
978:
979: ArrayValue& source_array=GET_SELF(r, VArray).array();
980: Value& caller=*r.get_method_frame()->caller();
981:
982: int limit=source_array.count();
983: bool reverse=false;
984:
985: if(params.count()>3)
986: if(HashStringValue* options=params.as_hash(3)) {
987: int valid_options=0;
988: if(Value* vlimit=options->get(sql_limit_name)) {
989: valid_options++;
990: limit=r.process(*vlimit).as_int();
991: }
992: if(Value* vreverse=options->get(table_reverse_name)) {
993: valid_options++;
994: reverse=r.process(*vreverse).as_bool();
995: }
996: if(valid_options!=options->count())
997: throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION);
998: }
999:
1000: VArray *result=new VArray;
1001: ArrayValue& result_array=result->array();
1002:
1003: if(limit>0){
1004: if(reverse){
1005: for(ArrayValue::ReverseIterator i(source_array); i; ){
1.5 moko 1006: if(Value *value=i.prev()){ // here for correct i.key()
1007: if(key_var_name)
1.40 ! moko 1008: r.put_element(caller, *key_var_name, VString::uitoa(i.index()));
1.5 moko 1009: if(value_var_name)
1010: r.put_element(caller, *value_var_name, value);
1.1 moko 1011:
1.5 moko 1012: bool condition=r.process(vcondition).as_bool();
1.1 moko 1013:
1.5 moko 1014: if(r.check_skip_break())
1015: break;
1.1 moko 1016:
1.5 moko 1017: if(condition){
1018: result_array+=value;
1019: if(!--limit)
1020: break;
1021: }
1.1 moko 1022: }
1023: }
1024: } else {
1.36 moko 1025: for(ArrayValue::RobustIterator i(source_array); i; i.next() ){
1.5 moko 1026: if(Value *value=i.value()){
1.1 moko 1027: if(key_var_name)
1.40 ! moko 1028: r.put_element(caller, *key_var_name, VString::uitoa(i.index()));
1.1 moko 1029: if(value_var_name)
1030: r.put_element(caller, *value_var_name, value);
1031:
1032: bool condition=r.process(vcondition).as_bool();
1033:
1034: if(r.check_skip_break())
1035: break;
1036:
1037: if(condition){
1038: result_array+=value;
1039: if(!--limit)
1040: break;
1041: }
1042: }
1043: }
1044: }
1045: }
1046:
1.17 moko 1047: result_array.confirm_all_used();
1.1 moko 1048: r.write(*result);
1049: }
1050:
1.19 moko 1051: static void _reverse(Request& r, MethodParams&) {
1.5 moko 1052: ArrayValue& source_array=GET_SELF(r, VArray).array();
1.1 moko 1053:
1.5 moko 1054: VArray& result=*new VArray(source_array.count());
1.1 moko 1055: ArrayValue& result_array=result.array();
1056:
1.4 moko 1057: for(ArrayValue::ReverseIterator i(source_array); i; ){
1058: result_array+=i.prev();
1.1 moko 1059: }
1060:
1061: r.write(result);
1062: }
1063:
1.24 moko 1064: static void _compact(Request& r, MethodParams& params) {
1065: bool compact_undef=false;
1066: if(params.count()>0){
1067: const String& what=params.as_string(0, PARAMETER_MUST_BE_STRING);
1068: if(!what.is_empty()){
1069: if(what != "undef")
1070: throw Exception(PARSER_RUNTIME, &what, "param must be empty or 'undef'");
1071: compact_undef=true;
1072: }
1073: }
1.26 moko 1074: ArrayValue& array=GET_SELF(r, VArray).array();
1075: array.compact(compact_undef);
1076: array.confirm_all_used();
1.22 moko 1077: }
1078:
1.1 moko 1079:
1080: // constructor
1081:
1082: MArray::MArray(): Methoded(VARRAY_TYPE) {
1083:
1.18 moko 1084: // ^array::copy[[copy_from]]
1.25 moko 1085: add_native_method("copy", Method::CT_DYNAMIC, _copy_or_add, 0, 1);
1.1 moko 1086: // ^array.add[add_from]
1.25 moko 1087: add_native_method("add", Method::CT_DYNAMIC, _copy_or_add, 1, 1);
1.6 moko 1088: // ^array.join[join_from[;options]]
1089: add_native_method("join", Method::CT_DYNAMIC, _join, 1, 2);
1.1 moko 1090:
1.27 moko 1091: // ^array::create[value[;value...]]
1092: add_native_method("create", Method::CT_DYNAMIC, _create_or_append_or_push, 0, 10000);
1093: // ^array.append[value[;value...]]
1094: add_native_method("append", Method::CT_DYNAMIC, _create_or_append_or_push, 1, 10000);
1095: // ^array.push[value[;value...]]
1096: add_native_method("push", Method::CT_DYNAMIC, _create_or_append_or_push, 1, 10000);
1.31 moko 1097: // ^array.insert(index)[value[;value...]]
1.18 moko 1098: add_native_method("insert", Method::CT_DYNAMIC, _insert, 2, 10000);
1099:
1.9 moko 1100: // ^array.left(n)
1101: add_native_method("left", Method::CT_DYNAMIC, _left, 1, 1);
1102: // ^array.right(n)
1103: add_native_method("right", Method::CT_DYNAMIC, _right, 1, 1);
1104: // ^array.mid(p)
1105: // ^array.mid(p;n)
1106: add_native_method("mid", Method::CT_DYNAMIC, _mid, 1, 2);
1.1 moko 1107:
1.29 moko 1108: // ^array.delete(index) or ^array.delete[index]
1.1 moko 1109: add_native_method("delete", Method::CT_DYNAMIC, _delete, 0, 1);
1.29 moko 1110: // ^array.remove(index)
1.7 moko 1111: add_native_method("remove", Method::CT_DYNAMIC, _remove, 1, 1);
1.27 moko 1112: // ^array.pop[]
1113: add_native_method("pop", Method::CT_DYNAMIC, _pop, 0, 0);
1.1 moko 1114:
1.31 moko 1115: // ^array.contains(index) ^array.contains[index]
1.1 moko 1116: add_native_method("contains", Method::CT_DYNAMIC, _contains, 1, 1);
1117:
1118: // ^array::sql[query][options array]
1119: add_native_method("sql", Method::CT_DYNAMIC, _sql, 1, 2);
1120:
1121: // ^array._keys[[column name]]
1122: add_native_method("_keys", Method::CT_DYNAMIC, _keys, 0, 1);
1123:
1.5 moko 1124: // ^array._count[[all]]
1125: add_native_method("_count", Method::CT_DYNAMIC, _count, 0, 1);
1.1 moko 1126:
1.7 moko 1127: // ^array.for[index;value]{code}[delim]
1128: add_native_method("for", Method::CT_DYNAMIC, _for, 3, 3+1);
1.2 moko 1129: // ^array.foreach[index;value]{code}[delim]
1.7 moko 1130: add_native_method("foreach", Method::CT_DYNAMIC, _foreach, 3, 3+1);
1.1 moko 1131:
1.2 moko 1132: // ^array.sort[index;value]{string-key-maker}[[asc|desc]]
1133: // ^array.sort[index;value](numeric-key-maker)[[asc|desc]]
1.1 moko 1134: add_native_method("sort", Method::CT_DYNAMIC, _sort, 3, 4);
1135:
1.2 moko 1136: // ^array.select[index;value](bool-condition)[options hash]
1.1 moko 1137: add_native_method("select", Method::CT_DYNAMIC, _select, 3, 4);
1138:
1139: // ^array.reverse[]
1140: add_native_method("reverse", Method::CT_DYNAMIC, _reverse, 0, 0);
1141:
1.24 moko 1142: // ^array.compact[[undef]]
1143: add_native_method("compact", Method::CT_DYNAMIC, _compact, 0, 1);
1.22 moko 1144:
1.2 moko 1145: // ^array._at[first|last[;'key'|'value'|'hash']]
1.31 moko 1146: // ^array._at([-+]index)[['key'|'value'|'hash']]
1.1 moko 1147: add_native_method("_at", Method::CT_DYNAMIC, _at, 1, 2);
1148:
1.31 moko 1149: // ^array.set[first|last;value]
1150: // ^array.set([-+]index)[value]
1151: add_native_method("set", Method::CT_DYNAMIC, _set, 2, 2);
1152:
1.1 moko 1153: #ifdef FEATURE_GET_ELEMENT4CALL
1154: // aliases without "_"
1155: add_native_method("keys", Method::CT_DYNAMIC, _keys, 0, 1);
1.14 moko 1156: add_native_method("count", Method::CT_DYNAMIC, _count, 0, 1);
1.1 moko 1157: add_native_method("at", Method::CT_DYNAMIC, _at, 1, 2);
1158: #endif
1159:
1160: }