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