--- parser3/src/classes/hash.C 2016/10/26 15:44:49 1.136 +++ parser3/src/classes/hash.C 2021/11/04 21:31:15 1.151 @@ -1,7 +1,7 @@ /** @file Parser: @b hash parser class. - Copyright (c) 2001-2015 Art. Lebedev Studio (http://www.artlebedev.com) + Copyright (c) 2001-2020 Art. Lebedev Studio (http://www.artlebedev.com) Author: Alexandr Petrosian (http://paf.design.ru) */ @@ -17,7 +17,7 @@ #include "pa_vbool.h" #include "pa_vmethod_frame.h" -volatile const char * IDENT_HASH_C="$Id: hash.C,v 1.136 2016/10/26 15:44:49 moko Exp $"; +volatile const char * IDENT_HASH_C="$Id: hash.C,v 1.151 2021/11/04 21:31:15 moko Exp $"; // class @@ -37,7 +37,6 @@ DECLARE_CLASS_VAR(hash, new MHash); #ifndef DOXYGEN class Hash_sql_event_handlers: public SQL_Driver_query_event_handlers { - const String& statement_string; const char* statement_cstr; bool distinct; HashStringValue& rows_hash; Value* row_value; @@ -50,22 +49,14 @@ class Hash_sql_event_handlers: public SQ public: Table* empty; public: - Hash_sql_event_handlers( - const String& astatement_string, - const char* astatement_cstr, - bool adistinct, - HashStringValue& arows_hash, - Table2hash_value_type avalue_type) - : - statement_string(astatement_string), - statement_cstr(astatement_cstr), + Hash_sql_event_handlers(bool adistinct, HashStringValue& arows_hash, Table2hash_value_type avalue_type): distinct(adistinct), rows_hash(arows_hash), - value_type(avalue_type), row_value(0), column_index(0), - one_bool_column(false), columns(*new ArrayString), + one_bool_column(false), + value_type(avalue_type), empty(0) { } @@ -74,20 +65,20 @@ public: columns+=new String(str, String::L_TAINTED /* no length as 0x00 can be inside */); return false; } catch(...) { - error=SQL_Error("exception occured in Hash_sql_event_handlers::add_column"); + error=SQL_Error("exception occurred in Hash_sql_event_handlers::add_column"); return true; } } bool before_rows(SQL_Error& error) { if(columns.count()<1) { - error=SQL_Error(PARSER_RUNTIME, "no columns"); + error=SQL_Error("no columns"); return true; } switch(value_type){ case C_STRING: { if(columns.count()>2){ - error=SQL_Error(PARSER_RUNTIME, "only 2 columns allowed for $.type[string]."); + error=SQL_Error("only 2 columns allowed for $.type[string]."); return true; } } @@ -110,7 +101,7 @@ public: bool add_row_cell(SQL_Error& error, const char *str, size_t ) { try { - const String& cell=str?*new String(str, String::L_TAINTED /* no length as 0x00 can be inside */):String::Empty; + const String& cell=str ? *new String(str, String::L_TAINTED /* no length as 0x00 can be inside */) : String::Empty; bool duplicate=false; if(one_bool_column) { @@ -173,14 +164,14 @@ public: } if(duplicate & !distinct) { - error=SQL_Error(PARSER_RUNTIME, "duplicate key"); + error=SQL_Error("duplicate key"); return true; } column_index++; return false; } catch(...) { - error=SQL_Error("exception occured in Hash_sql_event_handlers::add_row_cell"); + error=SQL_Error("exception occurred in Hash_sql_event_handlers::add_row_cell"); return true; } } @@ -210,7 +201,8 @@ static void _create_or_add(Request& r, M } if(src_hash) - src_hash->for_each(copy_all_overwrite_to, self_hash); + for(HashStringValue::Iterator i(*src_hash); i; i.next()) + self_hash->put(i.key(), i.value()); } } @@ -221,7 +213,8 @@ static void _sub(Request& r, MethodParam self->clear(); return; } - src->for_each(remove_key_from, self); + for(HashStringValue::Iterator i(*src); i; i.next()) + self->remove(i.key()); } } @@ -236,7 +229,7 @@ static void _union(Request& r, MethodPar src->for_each(copy_all_dontoverwrite_to, result.get_hash()); // return result - r.write_no_lang(result); + r.write(result); } #ifndef DOXYGEN @@ -258,7 +251,7 @@ static void _intersection(Request& r, Me } // return result - r.write_no_lang(result); + r.write(result); } static bool intersects( HashStringValue::key_type key, HashStringValue::value_type /*value*/, HashStringValue* b) { @@ -271,23 +264,17 @@ static void _intersects(Request& r, Meth if(HashStringValue* b=params.as_hash(0, "param")) { HashStringValue* self=&(GET_SELF(r, VHashBase).hash()); if(b==self) { - r.write_no_lang(VBool::get(true)); + r.write(VBool::get(true)); return; } result=self->first_that(intersects, b)!=0; } // return result - r.write_no_lang(VBool::get(result)); + r.write(VBool::get(result)); } -extern String sql_bind_name; -extern String sql_limit_name; -extern String sql_offset_name; -extern String sql_default_name; -extern String sql_distinct_name; -extern String sql_value_type_name; extern Table2hash_value_type get_value_type(Value& vvalue_type); extern int marshal_binds(HashStringValue& hash, SQL_Driver::Placeholder*& placeholders); extern void unmarshal_bind_updates(HashStringValue& hash, int placeholder_count, SQL_Driver::Placeholder* placeholders); @@ -336,19 +323,10 @@ static void _sql(Request& r, MethodParam const char* statement_cstr=statement_string.untaint_cstr(String::L_SQL, r.connection()); HashStringValue& hash=GET_SELF(r, VHashBase).hash(); - hash.clear(); - Hash_sql_event_handlers handlers( - statement_string, statement_cstr, - distinct, - hash, - value_type); - - r.connection()->query( - statement_cstr, - placeholders_count, placeholders, - offset, limit, - handlers, - statement_string); + hash.clear(); + Hash_sql_event_handlers handlers(distinct, hash, value_type); + + r.connection()->query(statement_cstr, placeholders_count, placeholders, offset, limit, handlers, statement_string); if(bind) unmarshal_bind_updates(*bind, placeholders_count, placeholders); @@ -372,11 +350,11 @@ static void _keys(Request& r, MethodPara GET_SELF(r, VHashBase).hash().for_each(keys_collector, table); - r.write_no_lang(*new VTable(table)); + r.write(*new VTable(table)); } static void _count(Request& r, MethodParams&) { - r.write_no_lang(*new VInt(GET_SELF(r, VHashBase).hash().count())); + r.write(*new VInt(GET_SELF(r, VHashBase).hash().count())); } static void _delete(Request& r, MethodParams& params) { @@ -390,7 +368,7 @@ static void _contains(Request& r, Method VHashBase& self=GET_SELF(r, VHashBase); const String& key_name=params.as_string(0, "key must be string"); bool result=SYMBOLS_EQ(key_name,_DEFAULT_SYMBOL) ? (self.get_default() != 0) : self.hash().contains(key_name); - r.write_no_lang(VBool::get(result)); + r.write(VBool::get(result)); } static void _foreach(Request& r, MethodParams& params) { @@ -419,19 +397,19 @@ static void _foreach(Request& r, MethodP r.put_element(caller, *value_var_name, i.value()); Value& sv_processed=r.process(*body_code); - Request::Skip lskip=r.get_skip(); r.set_skip(Request::SKIP_NOTHING); + TempSkip4Delimiter skip(r); const String* s_processed=sv_processed.get_string(); if(s_processed && !s_processed->is_empty()) { // we have body if(need_delim) // need delim & iteration produced string? - r.write_pass_lang(r.process(*delim_maybe_code)); + r.write(r.process(*delim_maybe_code)); else need_delim=true; } - r.write_pass_lang(sv_processed); + r.write(sv_processed); - if(lskip==Request::SKIP_BREAK) + if(skip.check_break()) break; } } else { @@ -445,9 +423,8 @@ static void _foreach(Request& r, MethodP r.put_element(caller, *value_var_name, i.value()); r.process_write(*body_code); - Request::Skip lskip=r.get_skip(); r.set_skip(Request::SKIP_NOTHING); - if(lskip==Request::SKIP_BREAK) + if(r.check_skip_break()) break; } } @@ -466,7 +443,7 @@ inline Value& SingleElementHash(String:: } #ifndef DOXYGEN -struct Hash_seq_item { +struct Hash_seq_item : public PA_Allocated { HashStringValue::Pair *hash_pair; union { const char *c_str; @@ -495,9 +472,7 @@ static void _sort(Request& r, MethodPara const String& key_var_name=params.as_string(0, "key-var name must be string"); const String& value_var_name=params.as_string(1, "value-var name must be string"); Value& key_maker=params.as_junction(2, "key-maker must be code"); - bool reverse=params.count()>3/*..[desc|asc|]*/? - reverse=params.as_no_junction(3, "order must not be code").as_string()=="desc": - false; // default=asc + bool reverse=params.count()>3 /*..[desc|asc|]*/ && params.as_no_junction(3, "order must not be code").as_string()=="desc"; // default=asc const String* key_var=key_var_name.is_empty()? 0 : &key_var_name; const String* value_var=value_var_name.is_empty()? 0 : &value_var_name; @@ -506,7 +481,7 @@ static void _sort(Request& r, MethodPara HashStringValue& hash=GET_SELF(r, VHashBase).hash(); int count=hash.count(); - Hash_seq_item* seq=new(PointerFreeGC) Hash_seq_item[count]; + Hash_seq_item* seq=new Hash_seq_item[count]; int pos=0; bool key_values_are_strings=true; @@ -590,15 +565,15 @@ static void _at(Request& r, MethodParams { #ifdef HASH_ORDER if(pos == 0) { - r.write_pass_lang(*new VString(*new String(hash.first_key(), String::L_TAINTED))); + r.write(*new VString(*new String(hash.first_key(), String::L_TAINTED))); } else if((size_t)pos == count-1) { - r.write_pass_lang(*new VString(*new String(hash.last_key(), String::L_TAINTED))); + r.write(*new VString(*new String(hash.last_key(), String::L_TAINTED))); } else #endif { for(HashStringValue::Iterator i(hash); i; i.next(), pos-- ) if(!pos){ - r.write_pass_lang(*new VString(*new String(i.key(), String::L_TAINTED))); + r.write(*new VString(*new String(i.key(), String::L_TAINTED))); break; } } @@ -608,15 +583,15 @@ static void _at(Request& r, MethodParams { #ifdef HASH_ORDER if(pos == 0) { - r.write_pass_lang(*hash.first_value()); + r.write(*hash.first_value()); } else if((size_t)pos == count-1) { - r.write_pass_lang(*hash.last_value()); + r.write(*hash.last_value()); } else #endif { for(HashStringValue::Iterator i(hash); i; i.next(), pos-- ) if(!pos){ - r.write_pass_lang(*i.value()); + r.write(*i.value()); break; } } @@ -626,15 +601,15 @@ static void _at(Request& r, MethodParams { #ifdef HASH_ORDER if(pos == 0) { - r.write_no_lang(SingleElementHash(hash.first_key(), hash.first_value())); + r.write(SingleElementHash(hash.first_key(), hash.first_value())); } else if((size_t)pos == count-1) { - r.write_no_lang(SingleElementHash(hash.last_key(), hash.last_value())); + r.write(SingleElementHash(hash.last_key(), hash.last_value())); } else #endif { for(HashStringValue::Iterator i(hash); i; i.next(), pos-- ) if(!pos){ - r.write_no_lang(SingleElementHash(i.key(), i.value())); + r.write(SingleElementHash(i.key(), i.value())); break; } } @@ -644,6 +619,124 @@ static void _at(Request& r, MethodParams } } +extern String table_reverse_name; + +static void _select(Request& r, MethodParams& params) { + const String* key_var_name=¶ms.as_string(0, "key-var name must be string"); + const String* value_var_name=¶ms.as_string(1, "value-var name must be string"); + Value& vcondition=params.as_expression(2, "condition must be number, bool or expression"); + + if(key_var_name->is_empty()) key_var_name=0; + if(value_var_name->is_empty()) value_var_name=0; + + HashStringValue& source_hash=GET_SELF(r, VHashBase).hash(); + Value& caller=*r.get_method_frame()->caller(); + + int limit=source_hash.count(); + bool reverse=false; + bool copy_default=false; + + if(params.count()>3) + if(HashStringValue* options=params.as_hash(3)) { + int valid_options=0; + if(Value* vlimit=options->get(sql_limit_name)) { + valid_options++; + limit=r.process(*vlimit).as_int(); + } + if(Value* vreverse=options->get(table_reverse_name)) { + valid_options++; + reverse=r.process(*vreverse).as_bool(); + } + if(Value* vcopy_default=options->get(sql_default_name)) { + valid_options++; + copy_default=r.process(*vcopy_default).as_bool(); + } + if(valid_options!=options->count()) + throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION); + } + + HashStringValue& result_hash=*new HashStringValue(); + + if(limit>0){ +#ifdef HASH_ORDER + if(reverse){ + for(HashStringValue::ReverseIterator i(source_hash); i; i.prev()){ + if(key_var_name) + r.put_element(caller, *key_var_name, new VString(*new String(i.key(), String::L_TAINTED))); + if(value_var_name) + r.put_element(caller, *value_var_name, i.value()); + + if(r.process(vcondition).as_bool()){ + result_hash.put(i.key(), i.value()); + if(!--limit) + break; + } + } + } else { +#else + { +#endif + for(HashStringValue::Iterator i(source_hash); i; i.next() ){ + if(key_var_name) + r.put_element(caller, *key_var_name, new VString(*new String(i.key(), String::L_TAINTED))); + if(value_var_name) + r.put_element(caller, *value_var_name, i.value()); + + if(r.process(vcondition).as_bool()){ + result_hash.put(i.key(), i.value()); + if(!--limit) + break; + } + } + } + } + + VHash *result=new VHash(result_hash); + if(copy_default){ + result->set_default(GET_SELF(r, VHashBase).get_default()); + } + + r.write(*result); +} + +static void _reverse(Request& r, MethodParams& params) { + VHashBase& self=GET_SELF(r, VHashBase); + HashStringValue& source_hash=self.hash(); + HashStringValue& result_hash=*new HashStringValue(); + +#ifdef HASH_ORDER + for(HashStringValue::ReverseIterator i(source_hash); i; i.prev()) + result_hash.put(i.key(), i.value()); +#else + for(HashStringValue::Iterator i(source_hash); i; i.next() ) + result_hash.put(i.key(), i.value()); +#endif + + VHashBase& result=*new VHash(result_hash); + if(Value* vdefault=self.get_default()) + result.set_default(vdefault); + + r.write(result); +} + + +static void _rename(Request& r, MethodParams& params) { + HashStringValue& hash=GET_SELF(r, VHashBase).hash(); + + if(params.count()>1){ + const String& key_from=params.as_string(0, "from key must be string"); + const String& key_to=params.as_string(1, "to key must be string"); + + hash.rename(key_from, key_to); + } else { + HashStringValue* names=params.as_hash(0); + + for(HashStringValue::Iterator i(*names); i; i.next()) + hash.rename(i.key(), i.value()->as_string()); + } +} + + // constructor MHash::MHash(): Methoded("hash") @@ -683,12 +776,22 @@ MHash::MHash(): Methoded("hash") // ^hash.sort[key;value]{string-key-maker}[[asc|desc]] // ^hash.sort[key;value](numeric-key-maker)[[asc|desc]] - add_native_method("sort", Method::CT_DYNAMIC, _sort, 2+1, 2+1+1); + add_native_method("sort", Method::CT_DYNAMIC, _sort, 3, 4); + + // ^hash.select[key;value](bool-condition)[options hash] + add_native_method("select", Method::CT_DYNAMIC, _select, 3, 4); + + // ^hash.reverse[] + add_native_method("reverse", Method::CT_DYNAMIC, _reverse, 0, 0); // ^hash._at[first|last[;'key'|'value'|'hash']] // ^hash._at([-+]offset)[['key'|'value'|'hash']] add_native_method("_at", Method::CT_DYNAMIC, _at, 1, 2); + // ^hash.rename[from;to] + // ^hash.rename[ $.from[to] ... ] + add_native_method("rename", Method::CT_DYNAMIC, _rename, 1, 2); + #ifdef FEATURE_GET_ELEMENT4CALL // aliases without "_" add_native_method("keys", Method::CT_DYNAMIC, _keys, 0, 1);