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