|
|
| version 1.266, 2024/11/10 20:28:15 | version 1.283, 2026/09/25 17:03:52 |
|---|---|
| Line 1 | Line 1 |
| /** @file | /** @file |
| Parser: parser @b operators. | Parser: parser @b operators. |
| Copyright (c) 2001-2024 Art. Lebedev Studio (http://www.artlebedev.com) | Copyright (c) 2001-2026 Art. Lebedev Studio (https://www.artlebedev.com) |
| Authors: Konstantin Morshnev <moko@design.ru>, Alexandr Petrosian <paf@design.ru> | Authors: Konstantin Morshnev <moko@design.ru>, Alexandr Petrosian <paf@design.ru> |
| */ | */ |
| Line 17 | Line 17 |
| #include "pa_vmethod_frame.h" | #include "pa_vmethod_frame.h" |
| #include "pa_vclass.h" | #include "pa_vclass.h" |
| #include "pa_charset.h" | #include "pa_charset.h" |
| #include "pa_varray.h" | |
| #ifdef HAVE_SYSLOG | |
| #include "syslog.h" | |
| #endif | |
| volatile const char * IDENT_OP_C="$Id$"; | volatile const char * IDENT_OP_C="$Id$"; |
| Line 79 public: | Line 84 public: |
| static Untaint_lang_name2enum &instance(){ | static Untaint_lang_name2enum &instance(){ |
| static Untaint_lang_name2enum *singleton=NULL; | static Untaint_lang_name2enum *singleton=NULL; |
| if(singleton=NULL) | if(!singleton) |
| singleton=new Untaint_lang_name2enum; | singleton=new Untaint_lang_name2enum; |
| return *singleton; | return *singleton; |
| } | } |
| Line 182 static void _process(Request& r, MethodP | Line 187 static void _process(Request& r, MethodP |
| size_t options_index=index+1; | size_t options_index=index+1; |
| if(options_index<params.count()) | if(options_index<params.count()) |
| if(HashStringValue* options=params.as_hash(options_index)) { | if(HashStringValue* options=params.as_hash(options_index)) { |
| int valid_options=0; | |
| for(HashStringValue::Iterator i(*options); i; i.next() ){ | for(HashStringValue::Iterator i(*options); i; i.next() ){ |
| String::Body key=i.key(); | String::Body key=i.key(); |
| Value* value=i.value(); | Value* value=i.value(); |
| if(key == "main") { | if(key == "main") { |
| valid_options++; | |
| main_alias=&value->as_string(); | main_alias=&value->as_string(); |
| } else if(key == "file") { | } else if(key == "file") { |
| valid_options++; | |
| file_alias=&value->as_string(); | file_alias=&value->as_string(); |
| } else if(key == "lineno") { | } else if(key == "lineno") { |
| valid_options++; | |
| line_no_alias_offset=value->as_int(); | line_no_alias_offset=value->as_int(); |
| } else if(key == "replace") { | } else if(key == "replace") { |
| valid_options++; | |
| allow_class_replace=r.process(*value).as_bool(); | allow_class_replace=r.process(*value).as_bool(); |
| } | } else |
| throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION); | |
| } | } |
| if(valid_options!=options->count()) | |
| throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION); | |
| } | } |
| uint processe_file_no=file_alias ? r.register_file(r.full_disk_path(*file_alias)) : pseudo_file_no__process; | uint processe_file_no=file_alias ? r.register_file(r.full_disk_path(*file_alias)) : pseudo_file_no__process; |
| Line 287 static void _while(Request& r, MethodPar | Line 282 static void _while(Request& r, MethodPar |
| } | } |
| static void _use(Request& r, MethodParams& params) { | static void _use(Request& r, MethodParams& params) { |
| Value& vfile=params.as_no_junction(0, FILE_NAME_MUST_NOT_BE_CODE); | const String& file_name=params.as_file_name(0); |
| const String* use_origin=0; | const String* use_origin=0; |
| bool allow_class_replace=false; | bool allow_class_replace=false; |
| Line 295 static void _use(Request& r, MethodParam | Line 290 static void _use(Request& r, MethodParam |
| if(params.count()==2) | if(params.count()==2) |
| if(HashStringValue* options=params.as_hash(1)) { | if(HashStringValue* options=params.as_hash(1)) { |
| int valid_options=0; | |
| for(HashStringValue::Iterator i(*options); i; i.next() ){ | for(HashStringValue::Iterator i(*options); i; i.next() ){ |
| String::Body key=i.key(); | String::Body key=i.key(); |
| Value* value=i.value(); | Value* value=i.value(); |
| if(key == "origin") { | if(key == "origin") { |
| valid_options++; | |
| use_origin=&value->as_string(); | use_origin=&value->as_string(); |
| } | } else if(key == "replace") { |
| if(key == "replace") { | |
| valid_options++; | |
| allow_class_replace=r.process(*value).as_bool(); | allow_class_replace=r.process(*value).as_bool(); |
| } | } else if(key == "main") { |
| if(key == "main") { | |
| valid_options++; | |
| load_auto_p=r.process(*value).as_bool(); | load_auto_p=r.process(*value).as_bool(); |
| } | } else |
| if(valid_options!=options->count()) | |
| throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION); | throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION); |
| } | } |
| } | } |
| Line 327 static void _use(Request& r, MethodParam | Line 310 static void _use(Request& r, MethodParam |
| Temp_class_replace class_replace(r, allow_class_replace); | Temp_class_replace class_replace(r, allow_class_replace); |
| r.use_file(vfile.as_string(), use_origin, load_auto_p); | r.use_file(file_name, use_origin, load_auto_p); |
| } | } |
| static void set_skip(Request& r, Request::Skip askip) { | static void set_skip(Request& r, Request::Skip askip) { |
| Line 554 template<class I> | Line 537 template<class I> |
| static Try_catch_result try_catch(Request& r, Value& body_code(Request&, I), I info, Value* catch_code, bool could_be_handled_by_caller=false) { | static Try_catch_result try_catch(Request& r, Value& body_code(Request&, I), I info, Value* catch_code, bool could_be_handled_by_caller=false) { |
| Try_catch_result result; | Try_catch_result result; |
| // minor bug: context not restored if only finally code is present, see #1062 | |
| if(!catch_code) { | if(!catch_code) { |
| result.processed_code=body_code(r, info); | result.processed_code=body_code(r, info); |
| return result; | return result; |
| Line 562 static Try_catch_result try_catch(Reques | Line 544 static Try_catch_result try_catch(Reques |
| // taking snapshot of try-context | // taking snapshot of try-context |
| Request_context_saver try_context(r); | Request_context_saver try_context(r); |
| size_t trace_start=r.exception_trace.top_index(); | |
| try { | try { |
| result.processed_code=body_code(r, info); | result.processed_code=body_code(r, info); |
| } catch(const Exception& e) { | } catch(const Exception& e) { |
| Request_context_saver throw_context(r); // remembering exception stack trace | size_t trace_bottom=r.exception_trace.bottom_index(); |
| size_t trace_top=r.exception_trace.top_index(); | |
| Request::Exception_details details=r.get_details(e); | VException& details=*new VException(r, e); |
| try_context.restore(); // restoring try-context for code after try and catch-code | try_context.restore(); // restoring try-context for catch code |
| { | r.exception_trace.set_bottom_index(trace_top); // keep the original trace occupied; errors in catch start after it |
| Temp_value_element temp(r, *catch_code->get_junction()->method_frame, exception_var_name, &details.vhash); | |
| Temp_skip temp_skip(r); | |
| result.processed_code=r.process(*catch_code); | |
| } | |
| // retriving $exception.handled | |
| Value* vhandled=details.vhash.hash().get(exception_handled_part_name); | |
| bool bhandled=false; | bool bhandled=false; |
| if(vhandled) { | try { |
| if(vhandled->is_string()) { // not simple $exception.handled(1/0)? | { |
| if(bhandled=could_be_handled_by_caller) { // and we can possibly handle it | Temp_value_element temp(r, *catch_code->get_junction()->method_frame, exception_var_name, &details); |
| result.exception_should_be_handled=vhandled->get_string(); // considering 'recovered' and let the caller recover | Temp_skip temp_skip(r); |
| } | result.processed_code=r.process(*catch_code); |
| } else | } |
| bhandled=vhandled->as_bool(); | |
| Value* vhandled=details.handled(); | |
| if(vhandled) { | |
| if(vhandled->is_string()) { // not simple $exception.handled(1/0)? | |
| if(bhandled=could_be_handled_by_caller) { // and we can possibly handle it | |
| result.exception_should_be_handled=vhandled->get_string(); // considering 'recovered' and let the caller recover | |
| } | |
| } else | |
| bhandled=vhandled->as_bool(); | |
| } | |
| } catch(...) { | |
| // restore execution for finally, but keep new error trace. | |
| try_context.restore(); | |
| details.expire_trace(); | |
| rethrow; | |
| } | } |
| details.expire_trace(); | |
| if(!bhandled){ | if(!bhandled){ |
| throw_context.restore(); // restoring exception stack trace creared by try_context.restore() | r.exception_trace.set_range(trace_bottom, trace_top); |
| rethrow; | rethrow; |
| } | } |
| r.exception_trace.set_range(trace_start, trace_start); | |
| } | } |
| return result; | return result; |
| Line 664 static void locked_process_and_cache_put | Line 658 static void locked_process_and_cache_put |
| } else | } else |
| throw Exception(PARSER_RUNTIME, | throw Exception(PARSER_RUNTIME, |
| result.exception_should_be_handled, | result.exception_should_be_handled, |
| "$" EXCEPTION_VAR_NAME "." EXCEPTION_HANDLED_PART_NAME " value must be " | "$" EXCEPTION_VAR_NAME ".handled value must be " |
| "either boolean or string '" CACHE_EXCEPTION_HANDLED_CACHE_NAME "'"); | "either boolean or string '" CACHE_EXCEPTION_HANDLED_CACHE_NAME "'"); |
| } else | } else |
| info.processed_code=&((Value &)result.processed_code).as_string(); | info.processed_code=&((Value &)result.processed_code).as_string(); |
| Line 680 static void locked_process_and_cache_put | Line 674 static void locked_process_and_cache_put |
| prolog.expires=info.scope->expires; | prolog.expires=info.scope->expires; |
| // buffer -write> file | // buffer -write> file |
| write(f, serialized.str, serialized.length); | if(write(f, serialized.str, serialized.length)<0) |
| throw Exception("file.write", 0, "write failed: %s (%d)", strerror(errno), errno); | |
| } else // expired! | } else // expired! |
| info.scope->expires=0; // flag it so that could be easily checked by caller | info.scope->expires=0; // flag it so that could be easily checked by caller |
| } | } |
| Line 715 struct Cache_get_result { | Line 710 struct Cache_get_result { |
| }; | }; |
| #endif | #endif |
| static Cache_get_result cache_get(Request_charsets& charsets, const String& file_spec, time_t now) { | static Cache_get_result cache_get(const String& file_spec, time_t now) { |
| Cache_get_result result={0, false}; | Cache_get_result result={0, false}; |
| File_read_result file=file_read_binary(file_spec, false /*fail_on_read_problem*/); | File_read_result file=file_read_binary(file_spec, false /*fail_on_read_problem*/); |
| Line 738 static Cache_get_result cache_get(Reques | Line 733 static Cache_get_result cache_get(Reques |
| static time_t as_expires(Request& r, MethodParams& params, int index, time_t now) { | static time_t as_expires(Request& r, MethodParams& params, int index, time_t now) { |
| time_t result; | time_t result; |
| if(VDate* vdate=dynamic_cast<VDate*>(¶ms[index])) | if(VDate* vdate=dynamic_cast<VDate*>(¶ms[index])) |
| result=vdate->get_time(); | result=(time_t)vdate->get_time(); |
| else | else |
| result=now+(time_t)params.as_double(index, "lifespan must be date or number", r); | result=now+(time_t)params.as_double(index, "lifespan must be date or number", r); |
| Line 797 static void _cache(Request& r, MethodPar | Line 792 static void _cache(Request& r, MethodPar |
| catch_code=¶ms.as_junction(3, "catch_code must be code"); | catch_code=¶ms.as_junction(3, "catch_code must be code"); |
| if(scope.expires>now) { | if(scope.expires>now) { |
| Cache_get_result cached=cache_get(r.charsets, file_spec, now); | Cache_get_result cached=cache_get(file_spec, now); |
| if(cached.body) { // have cached copy | if(cached.body) { // have cached copy |
| if(cached.expired) { | if(cached.expired) { |
| Line 849 static void _try_operator(Request& r, Me | Line 844 static void _try_operator(Request& r, Me |
| } catch(...){ | } catch(...){ |
| // process finally code but ignore the result | // process finally code but ignore the result |
| if(finally_code){ | if(finally_code){ |
| size_t trace_bottom=r.exception_trace.bottom_index(); | |
| size_t trace_top=r.exception_trace.top_index(); | |
| r.exception_trace.set_bottom_index(trace_top); | |
| Temp_skip temp(r); | Temp_skip temp(r); |
| /* Value &finally_result= */ r.process(*finally_code); | /* Value &finally_result= */ r.process(*finally_code); |
| r.exception_trace.set_range(trace_bottom, trace_top); | |
| } | } |
| rethrow; | rethrow; |
| } | } |
| Line 875 static void _throw_operator(Request&, Me | Line 874 static void _throw_operator(Request&, Me |
| if(params.count()==1 && !params[0].is_string()) { | if(params.count()==1 && !params[0].is_string()) { |
| if(HashStringValue *hash=params[0].get_hash()) { | if(HashStringValue *hash=params[0].get_hash()) { |
| const char* type=0; | const char* type=0; |
| if(Value* value=hash->get(exception_type_part_name)) | if(Value* value=hash->get(Symbols::TYPE_SYMBOL)) |
| type=value->as_string().cstr(); | type=value->as_string().cstr(); |
| const String* source=0; | const String* source=0; |
| if(Value* value=hash->get(exception_source_part_name)) | if(Value* value=hash->get(Symbols::SOURCE_SYMBOL)) |
| source=&value->as_string(); | source=&value->as_string(); |
| const char* comment=0; | const char* comment=0; |
| if(Value* value=hash->get(exception_comment_part_name)) | if(Value* value=hash->get(Symbols::COMMENT_SYMBOL)) |
| comment=value->as_string().cstr(); | comment=value->as_string().cstr(); |
| throw Exception(type, source, "%s", comment); // to avoid MAX_STRING limit | throw Exception(type, source, "%s", comment); // to avoid MAX_STRING limit |
| Line 901 static void _sleep_operator(Request& r, | Line 900 static void _sleep_operator(Request& r, |
| pa_sleep((int)trunc(seconds), (int)trunc((seconds-trunc(seconds))*1000000)); | pa_sleep((int)trunc(seconds), (int)trunc((seconds-trunc(seconds))*1000000)); |
| } | } |
| static void _a_operator(Request& r, MethodParams& params) { | |
| VArray& result=*new VArray; | |
| ArrayValue& array=result.array(); | |
| int count=params.count(); | |
| for(int i=0; i<count; i++) | |
| array+=&r.process(params[i]); | |
| array.confirm_all_used(); | |
| r.write(result); | |
| } | |
| #ifdef HAVE_SYSLOG | |
| static int log_level(const String &name){ | |
| if(name.is_empty()) return LOG_INFO; | |
| const char *sname = str_upper(name.cstr()); | |
| if(!strcmp(sname,"INFO")) return LOG_INFO; | |
| if(!strcmp(sname,"WARNING")) return LOG_WARNING; | |
| if(!strcmp(sname,"ERROR")) return LOG_ERR; | |
| if(!strcmp(sname,"DEBUG")) return LOG_DEBUG; | |
| throw Exception("syslog", &name, "invalid log level value"); | |
| } | |
| #endif | |
| static void _syslog_operator(Request& r, MethodParams& params) { | |
| const char* message=params.as_string(1, "message must be string").cstr(); | |
| #ifdef HAVE_SYSLOG | |
| const char* ident=params.as_string(0, "ident must be string").cstr(); | |
| int level=params.count()>2 ? log_level(params.as_string(2, "level must be string")) : LOG_INFO; | |
| openlog(*ident ? ident : "parser3", LOG_PID, LOG_USER); | |
| syslog(level, "%s", message); | |
| closelog(); | |
| (void)r; | |
| #else | |
| SAPI::log(r.sapi_info, "syslog: %s", message); | |
| #endif | |
| } | |
| #if defined(WIN32) && defined(_DEBUG) && !defined(_WIN64) | #if defined(WIN32) && defined(_DEBUG) && !defined(_WIN64) |
| # define PA_BPT | # define PA_BPT |
| static void _bpt(Request&, MethodParams&) { | static void _bpt(Request&, MethodParams&) { |
| Line 989 VClassMAIN::VClassMAIN(): VClass(MAIN_CL | Line 1028 VClassMAIN::VClassMAIN(): VClass(MAIN_CL |
| add_native_method("throw", Method::CT_ANY, _throw_operator, 1, 3); | add_native_method("throw", Method::CT_ANY, _throw_operator, 1, 3); |
| add_native_method("sleep", Method::CT_ANY, _sleep_operator, 1, 1); | add_native_method("sleep", Method::CT_ANY, _sleep_operator, 1, 1); |
| // equivalent to ^array::create[...] | |
| add_native_method("A", Method::CT_ANY, _a_operator, 0, 10000, Method::CO_WITHOUT_FRAME); | |
| // ^syslog[ident;message[;info|warning|error|debug]] | |
| add_native_method("syslog", Method::CT_ANY, _syslog_operator, 2, 3); | |
| } | } |
| // constructor & configurator | // constructor & configurator |