Diff for /parser3/src/classes/op.C between versions 1.127.2.22.2.33 and 1.181

version 1.127.2.22.2.33, 2003/06/26 11:51:30 version 1.181, 2008/07/08 13:37:30
Line 1 Line 1
 /** @file  /** @file
         Parser: parser @b operators.          Parser: parser @b operators.
   
         Copyright (c) 2001-2003 ArtLebedev Group (http://www.artlebedev.com)          Copyright (c) 2001-2005 ArtLebedev Group (http://www.artlebedev.com)
         Author: Alexandr Petrosian <paf@design.ru> (http://paf.design.ru)          Author: Alexandr Petrosian <paf@design.ru> (http://paf.design.ru)
 */  */
   
 static const char* IDENT_OP_C="$Date$";  static const char * const IDENT_OP_C="$Date$";
   
 #include "classes.h"  #include "classes.h"
 #include "pa_vmethod_frame.h"  #include "pa_vmethod_frame.h"
   
 #include "pa_common.h"  #include "pa_common.h"
   #include "pa_os.h"
 #include "pa_request.h"  #include "pa_request.h"
 #include "pa_vint.h"  #include "pa_vint.h"
 #include "pa_sql_connection.h"  #include "pa_sql_connection.h"
 #include "pa_vdate.h"  #include "pa_vdate.h"
 #include "pa_vmethod_frame.h"  #include "pa_vmethod_frame.h"
 #include "pa_vclass.h"  #include "pa_vclass.h"
   #include "pa_charset.h"
   
 // limits  // limits
   
 #define MAX_LOOPS 10000  #define MAX_LOOPS 20000
   
 // defines  // defines
   
 #define CASE_DEFAULT_VALUE "DEFAULT"  #define CASE_DEFAULT_VALUE "DEFAULT"
   #define PROCESS_MAIN_OPTION_NAME "main"
   #define PROCESS_FILE_OPTION_NAME "file"
   #define PROCESS_LINENO_OPTION_NAME "lineno"
   
 // class  // class
   
Line 33  public: Line 38  public:
         VClassMAIN();          VClassMAIN();
 };  };
   
   // defines for globals
   
   #define CYCLE_DATA_NAME "CYCLE-DATA"
   
   // globals
   
   //^for & co
   String cycle_data_name(CYCLE_DATA_NAME);
   
 // defines for statics  // defines for statics
   
 #define SWITCH_DATA_NAME "SWITCH-DATA"  #define SWITCH_DATA_NAME "SWITCH-DATA"
 #define CACHE_DATA_NAME "CACHE-DATA"  #define CACHE_DATA_NAME "CACHE-DATA"
   
 #define EXCEPTION_VAR_NAME "exception"  #define EXCEPTION_VAR_NAME "exception"
   
 // statics  // statics
Line 48  static const String cache_data_name(CACH Line 63  static const String cache_data_name(CACH
   
 static const String exception_var_name(EXCEPTION_VAR_NAME);  static const String exception_var_name(EXCEPTION_VAR_NAME);
   
   
   // local defines
   
   #define CACHE_EXCEPTION_HANDLED_CACHE_NAME "cache"
   
 // helpers  // helpers
   
 class Untaint_lang_name2enum: public Hash<const StringBody, String::Language> {  class Untaint_lang_name2enum: public Hash<const String::Body, String::Language> {
 public:  public:
         Untaint_lang_name2enum() {          Untaint_lang_name2enum() {
                 #define ULN(name, LANG) \                  #define ULN(name, LANG) \
                         put(StringBody(name), (value_type)(String::L_##LANG));                          put(String::Body(name), (value_type)(String::L_##LANG));
                 ULN("as-is", AS_IS);                  ULN("as-is", AS_IS);
                 ULN("optimized-as-is", AS_IS|String::L_OPTIMIZE_BIT);                  ULN("optimized-as-is", AS_IS|String::L_OPTIMIZE_BIT);
                 ULN("file-spec", FILE_SPEC);                  ULN("file-spec", FILE_SPEC);
                 ULN("http-header", HTTP_HEADER);                  ULN("http-header", HTTP_HEADER);
                 ULN("mail-header", MAIL_HEADER);                  ULN("mail-header", MAIL_HEADER);
                 ULN("uri", URI);                  ULN("uri", URI);
                 ULN("table", TABLE);  
                 ULN("sql", SQL);                  ULN("sql", SQL);
                 ULN("js", JS);                  ULN("js", JS);
                 ULN("xml", XML);                  ULN("xml", XML);
                 ULN("optimized-xml", XML|String::L_OPTIMIZE_BIT);                  ULN("optimized-xml", XML|String::L_OPTIMIZE_BIT);
                 ULN("html", HTML);                  ULN("html", HTML);
                 ULN("optimized-html", HTML|String::L_OPTIMIZE_BIT);                  ULN("optimized-html", HTML|String::L_OPTIMIZE_BIT);
                   ULN("regex", REGEX);
                 #undef ULN                  #undef ULN
         }          }
 } untaint_lang_name2enum;  } untaint_lang_name2enum;
Line 75  public: Line 95  public:
 // methods  // methods
   
 static void _if(Request& r, MethodParams& params) {  static void _if(Request& r, MethodParams& params) {
         Value& condition_code=params.as_junction(0, "condition must be expression");          bool condition=params.as_bool(0, "condition must be expression", r);
   
         bool condition=r.process_to_value(condition_code,   
                 /*0/*no name* /,*/  
                 false/*don't intercept string*/).as_bool();  
         if(condition)          if(condition)
                 r.write_pass_lang(r.process(params.as_junction(1, "'then' parameter must be code")));                  r.write_pass_lang(r.process(*params.get(1)));
         else if(params.count()>2)          else if(params.count()>2)
                 r.write_pass_lang(r.process(params.as_junction(2, "'else' parameter must be code")));                  r.write_pass_lang(r.process(*params.get(2)));
 }  }
   
 static void _untaint(Request& r, MethodParams& params) {  static String::Language get_untaint_lang(MethodParams& params, int index){
           const String& lang_name=params.as_string(index, "lang must be string");
           String::Language lang=untaint_lang_name2enum.get(lang_name);
           if(!lang)
                   throw Exception(0,
                           &lang_name,
                           "invalid taint language");
           return lang;
   }
   
   static void _untaint(Request& r, MethodParams& params) {
         String::Language lang;          String::Language lang;
         if(params.count()==1)          if(params.count()==1)
                 lang=String::L_AS_IS; // mark as simply 'tainted'. useful in html from sql                   lang=String::L_AS_IS; // mark as simply 'as-is'. useful in html from sql 
         else {          else
                 const String& lang_name=params.as_string(0, "lang must be string");                  lang=get_untaint_lang(params, 0);
                 lang=untaint_lang_name2enum.get(lang_name);  
                 if(!lang)  
                         throw Exception(0,  
                                 &lang_name,  
                                 "invalid taint language");  
         }  
   
         {          {
                 Value& vbody=params.as_junction(params.count()-1, "body must be code");                  Value& vbody=params.as_junction(params.count()-1, "body must be code");
Line 111  static void _untaint(Request& r, MethodP Line 130  static void _untaint(Request& r, MethodP
 static void _taint(Request& r, MethodParams& params) {  static void _taint(Request& r, MethodParams& params) {
         String::Language lang;          String::Language lang;
         if(params.count()==1)          if(params.count()==1)
                 lang=String::L_TAINTED; // mark as simply 'tainted'. useful in table:set                  lang=String::L_TAINTED; // mark as simply 'tainted'. useful in table:create
         else {          else
                 const String& lang_name=params.as_string(0, "lang must be string");                  lang=get_untaint_lang(params, 0);
                 lang=untaint_lang_name2enum.get(lang_name);  
                 if(!lang)  
                         throw Exception(0,  
                                 &lang_name,  
                                 "invalid taint language");  
         }  
   
         {          {
                 Value& vbody=params.as_no_junction(params.count()-1, "body must not be code");                  Value& vbody=params.as_no_junction(params.count()-1, "body must not be code");
Line 147  static void _process(Request& r, MethodP Line 160  static void _process(Request& r, MethodP
         {          {
                 VStateless_class *target_class=target_self->get_last_derived_class();                  VStateless_class *target_class=target_self->get_last_derived_class();
                 if(!target_class)                  if(!target_class)
                         throw Exception("parser.runtime",                          throw Exception(PARSER_RUNTIME,
                                 0,                                  0,
                                 "no target class");                                  "no target class");
   
Line 158  static void _process(Request& r, MethodP Line 171  static void _process(Request& r, MethodP
                 // temporary zero @auto so it wouldn't be auto-called in Request::use_buf                  // temporary zero @auto so it wouldn't be auto-called in Request::use_buf
                 Temp_method temp_method_auto(*target_class, auto_method_name, 0);                  Temp_method temp_method_auto(*target_class, auto_method_name, 0);
   
                 size_t main_alias_index=index+1;                  size_t options_index=index+1;
                 const String* main_alias=0;                  HashStringValue* options=0;
                 if(main_alias_index<params.count())                  if(options_index<params.count()) {
                         main_alias=&params.as_string(main_alias_index, "main alias must be string");                          Value& voptions=params.as_no_junction(options_index, "options must not be code");
                           options=voptions.get_hash();
                           if(!options)
                                   throw Exception(PARSER_RUNTIME,
                                           0,
                                           "options must be hash");
                   }
   
                 if(const String* file_name=params[index].get_string()) { // ^process...[file]                  const String* main_alias=0;
                         r.use_file(*target_class, r.absolute(*file_name), main_alias, true/*ignore_class_path*/);                  const String* file_alias=0;
                 } else { // process...{string}                  int line_no_alias_offset=0;
                         Value& vjunction=params.as_junction(index, "body must be code");                  if(options) {
                         // evaluate source to process                          int valid_options=0;
                         const String& source=r.process_to_string(vjunction);                          if(Value* vmain_alias=options->get(PROCESS_MAIN_OPTION_NAME)) {
                         r.use_buf(*target_class,                                  valid_options++;
                                 source.cstr(String::L_UNSPECIFIED, r.connection(false)),                                  main_alias=&vmain_alias->as_string();
                                 main_alias,                          }
                                 pseudo_file_no__process);                                         if(Value* vfile_alias=options->get(PROCESS_FILE_OPTION_NAME)) {
                                   valid_options++;
                                   file_alias=&vfile_alias->as_string();
                           }
                           if(Value* vline_no_alias_offset=options->get(PROCESS_LINENO_OPTION_NAME)) {
                                   valid_options++;
                                   line_no_alias_offset=vline_no_alias_offset->as_int();
                           }
           
                           if(valid_options!=options->count())
                                   throw Exception(PARSER_RUNTIME,
                                           0,
                                           "called with invalid option");
                 }                  }
   
                   uint processe_file_no=file_alias?
                           r.register_file(r.absolute(*file_alias))
                           : pseudo_file_no__process;
                   // process...{string}
                   Value& vjunction=params.as_junction(index, "body must be code");
                   // evaluate source to process
                   const String& source=r.process_to_string(vjunction);
                   r.use_buf(*target_class,
                           source.cstr(String::L_UNSPECIFIED, r.connection(false)),
                           main_alias,
                           processe_file_no,
                           line_no_alias_offset);
   
                 // main_method                  // main_method
                 main_method=target_class->get_method(main_method_name);                  main_method=target_class->get_method(main_method_name);
         }          }
Line 188  static void _process(Request& r, MethodP Line 232  static void _process(Request& r, MethodP
         }          }
 }  }
                   
 static void _rem(Request& r, MethodParams& params) {  static void _rem(Request&, MethodParams& params) {
         params.as_junction(0, "body must be code");          params.as_junction(0, "body must be code");
 }  }
   
 static void _while(Request& r, MethodParams& params) {  static void _while(Request& r, MethodParams& params) {
         Value& vcondition=params.as_junction(0, "condition must be expression");          Temp_hash_value<const String::Body, void*> 
         Value& body=params.as_junction(1, "body must be code");                  cycle_data_setter(r.classes_conf, cycle_data_name, /*any not null flag*/&r);
   
           Value& vcondition=params.as_expression(0, "condition must be number, bool or expression");
   
           Value& body_code=params.as_junction(1, "body must be code");
           Value* delim_maybe_code=params.count()>2?&params[2]:0;
   
         // while...          // while...
         int endless_loop_count=0;          int endless_loop_count=0;
           bool need_delim=false;
         while(true) {          while(true) {
                 if(++endless_loop_count>=MAX_LOOPS) // endless loop?                  if(++endless_loop_count>=MAX_LOOPS) // endless loop?
                         throw Exception("parser.runtime",                          throw Exception(PARSER_RUNTIME,
                                 0,                                  0,
                                 "endless loop detected");                                  "endless loop detected");
   
                 bool condition=r.process_to_value(vcondition,                   bool condition=r.process_to_value(vcondition, 
                                 /*0/*no name* /,*/  
                                 false/*don't intercept string*/).as_bool();                                  false/*don't intercept string*/).as_bool();
                 if(!condition) // ...condition is true                  if(!condition) // ...condition is true
                         break;                          break;
   
                 // write processed body                  StringOrValue sv_processed=r.process(body_code);
                 r.write_pass_lang(r.process(body));                  Request::Skip lskip=r.get_skip(); r.set_skip(Request::SKIP_NOTHING);
                   const String* s_processed=sv_processed.get_string();
                   if(delim_maybe_code && s_processed && s_processed->length()) { // delimiter set and we have body
                           if(need_delim) // need delim & iteration produced string?
                                   r.write_pass_lang(r.process(*delim_maybe_code));
                           else
                                   need_delim=true;
                   }
                   r.write_pass_lang(sv_processed);
   
                   if(lskip==Request::SKIP_BREAK)
                           break;
         }          }
 }  }
   
 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");          Value& vfile=params.as_no_junction(0, FILE_NAME_MUST_NOT_BE_CODE);
         r.use_file(r.main_class, vfile.as_string());          r.use_file(r.main_class, vfile.as_string());
 }  }
   
   static void set_skip(Request& r, Request::Skip askip) {
           void* data=r.classes_conf.get(cycle_data_name);
           if(!data)
                   throw Exception(PARSER_RUNTIME,
                           0,
                           "without cycle");
   
           r.set_skip(askip);
   }
   
   static void _break(Request& r, MethodParams&) {
           set_skip(r, Request::SKIP_BREAK);
   }
   
   static void _continue(Request& r, MethodParams&) {
           set_skip(r, Request::SKIP_CONTINUE);
   }
   
 static void _for(Request& r, MethodParams& params) {  static void _for(Request& r, MethodParams& params) {
           Temp_hash_value<const String::Body, void*> 
                   cycle_data_setter(r.classes_conf, cycle_data_name, /*any not null flag*/&r);
   
         const String& var_name=params.as_string(0, "var name must be string");          const String& var_name=params.as_string(0, "var name must be string");
         int from=params.as_int(1, "from must be int", r);          int from=params.as_int(1, "from must be int", r);
         int to=params.as_int(2, "to must be int", r);          int to=params.as_int(2, "to must be int", r);
Line 228  static void _for(Request& r, MethodParam Line 309  static void _for(Request& r, MethodParam
         Value* delim_maybe_code=params.count()>4?&params[4]:0;          Value* delim_maybe_code=params.count()>4?&params[4]:0;
   
         if(to-from>=MAX_LOOPS) // too long loop?          if(to-from>=MAX_LOOPS) // too long loop?
                 throw Exception("parser.runtime",                  throw Exception(PARSER_RUNTIME,
                         0,                          0,
                         "endless loop detected");                          "endless loop detected");
   
         bool need_delim=false;          bool need_delim=false;
         VInt* vint=new VInt(0);          VInt* vint=new VInt(0);
         r.get_method_frame()->caller()->put_element(var_name, vint, false);  
           VMethodFrame& caller=*r.get_method_frame()->caller();
           caller.put_element(caller, var_name, vint, false);
         for(int i=from; i<=to; i++) {          for(int i=from; i<=to; i++) {
                 vint->set_int(i);                  vint->set_int(i);
   
                 StringOrValue sv_processed=r.process(body_code);                  StringOrValue sv_processed=r.process(body_code);
                   Request::Skip lskip=r.get_skip(); r.set_skip(Request::SKIP_NOTHING);
                 const String* s_processed=sv_processed.get_string();                  const String* s_processed=sv_processed.get_string();
                 if(delim_maybe_code && s_processed && s_processed->length()) { // delimiter set and we have body                  if(delim_maybe_code && s_processed && s_processed->length()) { // delimiter set and we have body
                         if(need_delim) // need delim & iteration produced string?                          if(need_delim) // need delim & iteration produced string?
                                 r.write_pass_lang(r.process(*delim_maybe_code));                                  r.write_pass_lang(r.process(*delim_maybe_code));
                         need_delim=true;                          else
                                   need_delim=true;
                 }                  }
                 r.write_pass_lang(sv_processed);                  r.write_pass_lang(sv_processed);
   
                   if(lskip==Request::SKIP_BREAK)
                           break;
         }          }
 }  }
   
Line 253  static void _eval(Request& r, MethodPara Line 341  static void _eval(Request& r, MethodPara
         Value& expr=params.as_junction(0, "need expression");          Value& expr=params.as_junction(0, "need expression");
         // evaluate expresion          // evaluate expresion
         Value& value_result=r.process_to_value(expr,           Value& value_result=r.process_to_value(expr, 
                 /*0/*no name YET* /,*/                  false/*don't intercept string*/).as_expr_result();
                 true/*don't intercept string*/).as_expr_result();  
         if(params.count()>1) {          if(params.count()>1) {
                 Value& fmt=params.as_no_junction(1, "fmt must not be code");                  const String& fmt=params.as_string(1, "fmt must be string").trim();
                 r.write_no_lang(String(format(value_result.as_double(), fmt.as_string().cstrm())));                  if(fmt.is_empty()){
                           r.write_no_lang(value_result);
                   } else {
                           r.write_no_lang(String(format(value_result.as_double(), fmt.cstrm())));
                   }
         } else          } else
                 r.write_no_lang(value_result);                  r.write_no_lang(value_result);
 }  }
Line 281  struct timeval mt[2]; Line 372  struct timeval mt[2];
 gettimeofday(&mt[0],NULL);  gettimeofday(&mt[0],NULL);
 #endif  #endif
         // connect          // connect
         SQL_Connection* connection=SQL_driver_manager.get_connection(url.as_string(),           SQL_Connection* connection=SQL_driver_manager->get_connection(url.as_string(), 
                 protocol2driver_and_client);                  protocol2driver_and_client,
                   r.charsets.source().NAME().cstr(),
                   r.request_info.document_root);
   
 #ifdef RESOURCES_DEBUG  #ifdef RESOURCES_DEBUG
 //measure:after connect  //measure:after connect
Line 298  r.sql_connect_time+=t[1]-t[0]; Line 391  r.sql_connect_time+=t[1]-t[0];
         // execute body          // execute body
         try {          try {
                 r.write_assign_lang(r.process(body_code));                  r.write_assign_lang(r.process(body_code));
                   connection->commit();
                   connection->close();
         } catch(...) { // process problem          } catch(...) { // process problem
                 connection->mark_to_rollback();                  connection->rollback();
                 rethrow;                   connection->close();
                   rethrow;
         }          }
 }  }
   
Line 308  r.sql_connect_time+=t[1]-t[0]; Line 404  r.sql_connect_time+=t[1]-t[0];
 class Switch_data: public PA_Object {  class Switch_data: public PA_Object {
 public:  public:
         Request& r;          Request& r;
         Value& searching;          const String* searching_string;
           double searching_double;
         Value* found;          Value* found;
         Value* _default;          Value* _default;
 public:  public:
         Switch_data(Request& ar, Value& asearching):           Switch_data(Request& ar, Value& asearching): 
           r(ar), searching(asearching) {}                  r(ar)
           {
                   if(asearching.is_string() || asearching.is_void()){
                           searching_string=&asearching.as_string();
                           searching_double=0;
                   } else {
                           searching_string=0;
                           searching_double=asearching.as_double();
                   }
           }
 };  };
 #endif  #endif
 static void _switch(Request& r, MethodParams& params) {  static void _switch(Request& r, MethodParams& params) {
         Switch_data* data=new Switch_data(r, r.process_to_value(params[0]));          Switch_data* data=new Switch_data(r, r.process_to_value(params[0]));
         Temp_hash_value<const StringBody, PA_Object*>           Temp_hash_value<const String::Body, void*> 
                 switch_data_setter(r.classes_conf, switch_data_name, data);                  switch_data_setter(r.classes_conf, switch_data_name, data);
   
         Value& cases_code=params.as_junction(1, "switch cases must be code");          Value& cases_code=params.as_junction(1, "switch cases must be code");
         // execution of found ^case[...]{code} must be in context of ^switch[...]{code}          // execution of found ^case[...]{code} must be in context of ^switch[...]{code}
         // because of stacked WWrapper used there as wcontext          // because of stacked WWrapper used there as wcontext
         r.process(cases_code, true/*intercept_string*/);          r.process(cases_code, true/*intercept_string*/);
         if(Value* selected_code=data->found? data->found: data->_default) {          if(Value* selected_code=data->found? data->found: data->_default)
                 // setting code context, would execute in ^switch[...]{>>context<<}  
                 //selected_code->get_junction()->change_context(cases_code.get_junction());  
                 r.write_pass_lang(r.process(*selected_code));                  r.write_pass_lang(r.process(*selected_code));
         }  
 }  }
   
 static void _case(Request& r, MethodParams& params) {  static void _case(Request& r, MethodParams& params) {
         Switch_data* data=static_cast<Switch_data*>(r.classes_conf.get(switch_data_name));          Switch_data* data=static_cast<Switch_data*>(r.classes_conf.get(switch_data_name));
         if(!data)          if(!data)
                 throw Exception("parser.runtime",                  throw Exception(PARSER_RUNTIME,
                         0,                          0,
                         "without switch");                          "without switch");
   
           if(data->found) // matches already was found
                   return;
   
         int count=params.count();          int count=params.count();
         Value& code=params.as_junction(--count, "case result must be code");          Value& code=params.as_junction(--count, "case result must be code");
                   
         // killing context for safety, would execute in ^switch[...]{>>context<<}          for(int i=0; i<count; i++){
         // reason: context is stacked, and it would become invalid afterwards  
         //code->get_junction()->change_context(0);  
   
         for(int i=0; i<count; i++) {  
                 Value& value=r.process_to_value(params[i]);                  Value& value=r.process_to_value(params[i]);
   
                 if(value.as_string() == CASE_DEFAULT_VALUE) {                  if(value.is_string() && value.as_string() == CASE_DEFAULT_VALUE){
                         data->_default=&code;                          data->_default=&code;
                         break;                          continue;
                 }                  }
   
                 bool matches;                  bool matches;
                 if(data->searching.is_string())                  if(data->searching_string)
                         matches=data->searching.as_string() == value.as_string();                          matches=(*data->searching_string) == value.as_string();
                 else                  else
                         matches=data->searching.as_double() == value.as_double();                          matches=data->searching_double == value.as_double();
   
                 if(matches) {  
                         if(data->found)  
                                 throw Exception("parser.runtime",  
                                         0,  
                                         "duplicate found");  
   
                   if(matches){
                         data->found=&code;                          data->found=&code;
                         break;                          break;
                 }                  }
         }          }
 }  }
   #ifndef DOXYGEN
   struct Try_catch_result {
           StringOrValue processed_code;
           const String* exception_should_be_handled;
   
           Try_catch_result(): exception_should_be_handled(0) {}
   };
   #endif
   
   /// used by ^try and ^cache, @returns $exception.handled[string] if any
   template<class I>
   static Try_catch_result try_catch(Request& r, 
                     StringOrValue body_code(Request&, I), I info, 
                     Value* catch_code,
                     bool could_be_handled_by_caller=false) 
   {
           Try_catch_result result;
   
           if(!catch_code) {
                   result.processed_code=body_code(r, info);
                   return result;
           }
   
           // taking snapshot of try-context
           Request_context_saver try_context(r);
           try {
                   result.processed_code=body_code(r, info);
           } catch(const Exception& e) {
                   Request_context_saver throw_context(r); // taking snapshot of throw-context [stack trace contains error]
                   Request::Exception_details details=r.get_details(e);
                   try_context.restore(); // restoring try-context to perform catch-code
   
                   Junction* junction=catch_code->get_junction();
                   Value* method_frame=junction->method_frame;
                   Value* saved_exception_var_value=method_frame->get_element(exception_var_name, *method_frame, false);
                   VMethodFrame& frame=*junction->method_frame;
                   frame.put_element(frame, exception_var_name, &details.vhash, false);
   
                   result.processed_code=r.process(*catch_code);
                   
                   // retriving $exception.handled, restoring $exception var
                   Value* vhandled=details.vhash.hash().get(exception_handled_part_name);
                   frame.put_element(frame, exception_var_name, saved_exception_var_value, false);
   
                   bool bhandled=false;
                   if(vhandled) {
                           if(vhandled->is_string()) { // not simple $exception.handled(1/0)?
                                   if(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
                                           return result;
                                   }
                                   
                                   bhandled=false;
                           } else
                                   bhandled=vhandled->as_bool();
                   }
   
                   if(!bhandled) {
                           throw_context.restore(); // restoring throw-context [exception were not handled]
                           rethrow;
                   }
           }
   
           return result;
   }
   
 // cache--  // cache--
   
 // consts  // consts
   
 const int DATA_STRING_SERIALIZED_VERSION=0x0004;  const int DATA_STRING_SERIALIZED_VERSION=0x0006;
   
 // helper types  // helper types
   
Line 388  struct Data_string_serialized_prolog { Line 550  struct Data_string_serialized_prolog {
 #endif  #endif
   
 void cache_delete(const String& file_spec) {  void cache_delete(const String& file_spec) {
         file_delete(file_spec, false/*fail_on_read_problem*/);          file_delete(file_spec, false/*fail_on_problem*/);
 }  }
   
 #ifndef DOXYGEN  #ifndef DOXYGEN
 class Cache_data: public PA_Object {  struct Cache_scope {
 public:  public:
         time_t expires;          time_t expires;
           const String* body_from_disk;
 };  };
 struct Locked_process_and_cache_put_action_info {  struct Locked_process_and_cache_put_action_info {
         Request *r;          Request *r;
         Cache_data *data;          Cache_scope *scope;
         Value* body_code; const String* evaluated_body;          Value* body_code;
           Value* catch_code; 
           const String* processed_code;
 };  };
 #endif  #endif
   
   
   
   static StringOrValue process_cache_body_code(Request& r, Value* body_code) {
           return StringOrValue(r.process_to_string(*body_code));
   }
   
 /* @todo maybe network order worth spending some effort?  /* @todo maybe network order worth spending some effort?
         don't bothering myself with network byte order,          don't bothering myself with network byte order,
         am not planning to be able to move resulting file across platforms          am not planning to be able to move resulting file across platforms
Line 410  static void locked_process_and_cache_put Line 582  static void locked_process_and_cache_put
         Locked_process_and_cache_put_action_info& info=          Locked_process_and_cache_put_action_info& info=
                 *static_cast<Locked_process_and_cache_put_action_info *>(context);                  *static_cast<Locked_process_and_cache_put_action_info *>(context);
   
           const String* body_from_disk=info.scope->body_from_disk;
         // body->process           // body->process 
         info.evaluated_body=&info.r->process_to_string(*info.body_code);          Try_catch_result result=try_catch(*info.r, 
                   process_cache_body_code, info.body_code,
                   info.catch_code, body_from_disk!=0 /*we have something old=we can handle=recover later*/);
   
           if(result.exception_should_be_handled) {
                   if(*result.exception_should_be_handled==CACHE_EXCEPTION_HANDLED_CACHE_NAME) {
                           assert(body_from_disk);
                           info.processed_code=body_from_disk;
                   } else
                           throw Exception(PARSER_RUNTIME,
                                   result.exception_should_be_handled,
                                   "$"EXCEPTION_VAR_NAME"."EXCEPTION_HANDLED_PART_NAME" value must be "
                                   "either boolean or string '"CACHE_EXCEPTION_HANDLED_CACHE_NAME"'");
           } else
                   info.processed_code=&result.processed_code.as_string();
   
         // expiration time not spoiled by ^cache(0) or something?          // expiration time not spoiled by ^cache(0) or something?
         if(info.data->expires > time(0)) {          if(info.scope->expires > time(0)) {
                 // string -serialize> buffer                  // string -serialize> buffer
                 String::Cm serialized=info.evaluated_body->serialize(                  String::Cm serialized=info.processed_code->serialize(
                         sizeof(Data_string_serialized_prolog));                          sizeof(Data_string_serialized_prolog));
                 Data_string_serialized_prolog& prolog=                  Data_string_serialized_prolog& prolog=
                         *reinterpret_cast<Data_string_serialized_prolog *>(serialized.str);                          *reinterpret_cast<Data_string_serialized_prolog *>(serialized.str);
                 prolog.version=DATA_STRING_SERIALIZED_VERSION;                  prolog.version=DATA_STRING_SERIALIZED_VERSION;
                 prolog.expires=info.data->expires;                  prolog.expires=info.scope->expires;
                                   
                 // buffer -write> file                  // buffer -write> file
                 write(f, serialized.str, serialized.length);                  write(f, serialized.str, serialized.length);
         } else // expired!          } else // expired!
                 info.data->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
 }  }
 const String* locked_process_and_cache_put(Request& r,   const String* locked_process_and_cache_put(Request& r, 
                                            Value& body_code,                                             Value& body_code,
                                            Cache_data& data,                                             Value* catch_code,
                                            const String& file_spec) {                                             Cache_scope& scope,
         Locked_process_and_cache_put_action_info info={0};                                             const String& file_spec) 
         info.r=&r;  {
         info.data=&data;          Locked_process_and_cache_put_action_info info={&r, &scope, &body_code, catch_code, 0};
         info.body_code=&body_code;  
   
         const String* result=file_write_action_under_lock(          const String* result=file_write_action_under_lock(
                 file_spec,                   file_spec, 
                 "cache_put", locked_process_and_cache_put_action, &info,                  "cache_put",
                   locked_process_and_cache_put_action,
                   &info,
                 false/*as_text*/,                  false/*as_text*/,
                 false/*do_append*/,                  false/*do_append*/,
                 false/*block*/,                  false/*block == don't wait till other thread release lock*/,
                 false/*fail on lock problem*/) ? info.evaluated_body: 0;                  false/*dun throw exception if lock failed*/) ? info.processed_code: 0;
   
         time_t now=time(0);          time_t now=time(0);
         if(data.expires<=now)          if(scope.expires<=now)
                 cache_delete(file_spec);                  cache_delete(file_spec);
         return result;          return result;
 }  }
 const String* cache_get(Request_charsets& charsets, const String& file_spec, time_t now) {  #ifndef DOXYGEN
   struct Cache_get_result {
           const String* body;
           bool expired;
   };
   #endif
   static Cache_get_result cache_get(Request_charsets& charsets, const String& file_spec, time_t now) {
           Cache_get_result result={0, false};
   
         File_read_result file=file_read(charsets, file_spec,           File_read_result file=file_read(charsets, file_spec, 
                            false/*as_text*/,                              false/*as_text*/, 
                            0, //no params                             0, //no params
Line 460  const String* cache_get(Request_charsets Line 657  const String* cache_get(Request_charsets
                 Data_string_serialized_prolog& prolog=                  Data_string_serialized_prolog& prolog=
                         *reinterpret_cast<Data_string_serialized_prolog *>(file.str);                          *reinterpret_cast<Data_string_serialized_prolog *>(file.str);
   
                 String* result=new String;                  String* body=new String;
                 if(                  if(
                         file.length>=sizeof(Data_string_serialized_prolog)                          file.length>=sizeof(Data_string_serialized_prolog)
                         && prolog.version==DATA_STRING_SERIALIZED_VERSION                          && prolog.version==DATA_STRING_SERIALIZED_VERSION) {
                         && prolog.expires > now                          if(body->deserialize(sizeof(Data_string_serialized_prolog),  file.str, file.length)) {
                         && result->deserialize(sizeof(Data_string_serialized_prolog),  file.str, file.length))                                  result.body=body;
                         return result;                                  result.expired=prolog.expires <= now;
                           }
                   }
         }          }
   
         return 0;          return result;
 }  }
 static time_t as_expires(Request& r, MethodParams& params,   static time_t as_expires(Request& r, MethodParams& params, 
                                                 int index, time_t now) {                                                  int index, time_t now) {
Line 485  static const String& as_file_spec(Reques Line 684  static const String& as_file_spec(Reques
         return r.absolute(params.as_string(index, "filespec must be string"));          return r.absolute(params.as_string(index, "filespec must be string"));
 }  }
 static void _cache(Request& r, MethodParams& params) {  static void _cache(Request& r, MethodParams& params) {
           if(params.count()==0) {
                   // ^cache[] -- return current expiration time
                   Cache_scope* scope=static_cast<Cache_scope*>(r.classes_conf.get(cache_data_name));
                   if(!scope)
                           throw Exception(PARSER_RUNTIME,
                                   0,
                                   "expire-time get without cache");
                   r.write_no_lang(*new VDate(scope->expires));
                   return;
           }
   
         time_t now=time(0);          time_t now=time(0);
   
         // ^cache[filename] ^cache(seconds) ^cache[expires date]  
         if(params.count()==1) {          if(params.count()==1) {
                   // ^cache[filename] ^cache(seconds) ^cache[expires date]
                 if(params[0].is_string()) { // filename?                  if(params[0].is_string()) { // filename?
                         cache_delete(as_file_spec(r, params, 0));                          cache_delete(as_file_spec(r, params, 0));
                         return;                          return;
                 }                  }
   
                 // secods|expires date                  // secods|expires date
                 Cache_data* data=static_cast<Cache_data*>(r.classes_conf.get(cache_data_name));                  Cache_scope* scope=static_cast<Cache_scope*>(r.classes_conf.get(cache_data_name));
                 if(!data)                  if(!scope)
                         throw Exception("parser.runtime",                          throw Exception(PARSER_RUNTIME,
                                 0,                                  0,
                                 "expire-time reducing instruction without cache");                                  "expire-time reducing instruction without cache");
                                   
                 time_t expires=as_expires(r, params, 0, now);                  time_t expires=as_expires(r, params, 0, now);
                 if(expires < data->expires)                  if(expires < scope->expires)
                         data->expires=expires;                          scope->expires=expires;
   
                 return;                  return;
         }          } else if(params.count()<3)
                   throw Exception(PARSER_RUNTIME,
                           0,
                           "invalid number of parameters"); 
                   
         // file_spec, expires, body code          // file_spec, expires, body code
         const String& file_spec=r.absolute(params.as_string(0, "filespec must be string"));          const String& file_spec=as_file_spec(r, params, 0);
   
         Cache_data* data=new Cache_data;          Cache_scope scope={as_expires(r, params, 1, now), 0};
         Temp_hash_value<const StringBody, PA_Object*>   
                 cache_data_setter(r.classes_conf, cache_data_name, data);  
         data->expires=as_expires(r, params, 1, now);  
         Value& body_code=params.as_junction(2, "body must be code");  
   
         if(data->expires>now) { // valid 'expires' specified? try cached copy...  
                 // hence we don't hope to have unary create/lockEX  
                 // we need some plan to live in a life like that, so...   
                 // worst races plan:  
                 // A        B  
                 // open  
                 //          |open  
                 // lockSH  
                 //          |nonblocking-lockEX fails  
                 // unlockSH  
                 // close, cache_get returns 0  
                 // open  
                 // nonblocking-lockEX succeeds; process, write, close  
                 //          |retry1: open  
                 // ...  
                 //          |lockSH succeeds; ...  
   
                 for(int retry=0; retry<2; retry++) {  
                         if(const String* cached_body=cache_get(r.charsets, file_spec, now)) { // have cached copy?  
                                 // write it out   
                                 r.write_assign_lang(*cached_body);  
                                 // happy with it  
                                 return;  
                         }  
   
                         // non-blocked lock; process; cache it          Temp_hash_value<const String::Body, void*> 
                         if(const String* processed_body=                  cache_scope_setter(r.classes_conf, cache_data_name, &scope);
                                 locked_process_and_cache_put(r, body_code, *data, file_spec)) {          Value& body_code=params.as_junction(2, "body_code must be code");
                                 // write it out           Value* catch_code=0;
                                 r.write_assign_lang(*processed_body);          if(params.count()>3)
                                 // happy with it                  catch_code=&params.as_junction(3, "catch_code must be code");
                                 return;  
                         } else { // somebody writing result right now          if(scope.expires>now) {
                                 pa_sleep(0, 500000); // waiting half a second                  Cache_get_result cached=cache_get(r.charsets, file_spec, now);
                                 retry=0; // prolonging our wait, than could cache_get it, without processing body_code  
                         }                  if(cached.body) { // have cached copy
                           if(cached.expired) {
                                   scope.body_from_disk=cached.body; // storing for user to retrive it with ^cache[]
                           } else {
                                   // and it's not expired yet write it out 
                                   r.write_assign_lang(*cached.body);
                                   // happy with it
                                   return;
                           }
                   }
   
                   // no cached info or it's already expired
   
                   // trying to process it under lock and store result in file
                   const String* processed_body=locked_process_and_cache_put(r, body_code, catch_code, scope, file_spec);
                   if(processed_body){
                           // write it out 
                           r.write_assign_lang(*processed_body);
                           // happy with it
                           return;
                   } else {
                           // we fail while get exclusive lock. nvm, we just execute body_code a bit later
                 }                  }
                 throw Exception(0,  
                         &file_spec,  
                         "locking problem");  
         } else {           } else { 
                 // instructed not to cache; forget cached copy                  // instructed not to cache; forget cached copy
                 cache_delete(file_spec);                  cache_delete(file_spec);
                 // process  
                 const String& processed_body=r.process_to_string(body_code);  
                 // write it out   
                 r.write_assign_lang(processed_body);  
                 // happy with it  
                 return;  
         }          }
         // never reached          
           // process without cacheing
           const String& processed_body=r.process_to_string(body_code);
           // write it out 
           r.write_assign_lang(processed_body);
 }  }
   
   static StringOrValue process_try_body_code(Request& r, Value* body_code) {
           return r.process(*body_code);
 // also used in pa_request.C to pass param to @unhandled_exception  }
   
 static void _try_operator(Request& r, MethodParams& params) {  static void _try_operator(Request& r, MethodParams& params) {
         Value& body_code=params.as_junction(0, "body_code must be code");          Value& body_code=params.as_junction(0, "body_code must be code");
         Value& catch_code=params.as_junction(1, "catch_code must be code");          Value& catch_code=params.as_junction(1, "catch_code must be code");
           Value* finally_code=(params.count()==3) ? &params.as_junction(2, "finally_code must be code") : 0;
   
         StringOrValue result;          Try_catch_result result;
         // taking snapshot of try-context          StringOrValue finally_result;
         Request_context_saver try_context(r);          try{
         try {                  result=try_catch(r, 
                 result=r.process(body_code);                          process_try_body_code, &body_code,
         } catch(const Exception& e) {                          &catch_code);
                 Request::Exception_details details=r.get_details(e);                  if(result.exception_should_be_handled)
                           throw Exception(PARSER_RUNTIME,
                 Request_context_saver throw_context(r); // taking snapshot of throw-context [stack trace contains error]                                  result.exception_should_be_handled,
                 try_context.restore(); // restoring try-context to perform catch-code                                  "catch block must set $exception.handled to some boolean value, not string");
           } catch(...){
                 Junction* junction=catch_code.get_junction();                  if(finally_code)
                 Value* method_frame=junction->method_frame;                          finally_result=r.process(*finally_code);
                 Value* saved_exception_var_value=method_frame->get_element(exception_var_name, *method_frame, false);                  rethrow;
                 junction->method_frame->put_element(exception_var_name, &details.vhash, false);          }
                 result=r.process(catch_code);  
                 bool handled=false;          if(finally_code)
                 if(Value* value=                  finally_result=r.process(*finally_code);
                         details.vhash.hash().get(exception_handled_part_name))  
                         handled=value->as_bool();                         // write out processed body_code or catch_code
                 junction->method_frame->put_element(exception_var_name, saved_exception_var_value, false);          r.write_pass_lang(result.processed_code);
   
                 if(!handled) {          // write out processed finally code
                         throw_context.restore(); // restoring throw-context [exception were not handled]          if(finally_code)
                         rethrow;                  r.write_pass_lang(finally_result);
                 }  
         }  
         // write out result  
         r.write_pass_lang(result);  
 }  }
   
 static void _throw_operator(Request& r, MethodParams& params) {  static void _throw_operator(Request&, MethodParams& params) {
         if(params.count()==1) {          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(exception_type_part_name))
Line 618  static void _throw_operator(Request& r, Line 814  static void _throw_operator(Request& r,
                         const String* source=0;                          const String* source=0;
                         if(Value* value=hash->get(exception_source_part_name))                          if(Value* value=hash->get(exception_source_part_name))
                                 source=&value->as_string();                                  source=&value->as_string();
                         const char* comment;                          const char* comment=0;
                         if(Value* value=hash->get(exception_comment_part_name))                          if(Value* value=hash->get(exception_comment_part_name))
                                 comment=value->as_string().cstr();                                  comment=value->as_string().cstr();
   
Line 626  static void _throw_operator(Request& r, Line 822  static void _throw_operator(Request& r,
                                 source?source:0,                                  source?source:0,
                                 "%s", comment?comment:"");                                  "%s", comment?comment:"");
                 } else                  } else
                         throw Exception("parser.runtime",                          throw Exception(PARSER_RUNTIME,
                                 0,                                  0,
                                 "one-param version has hash param");                                  "one-param version has hash or string param");
         } else {          } else {
                 const char* type=params.as_string(0, "type must be string").cstr();                  const char* type=params.as_string(0, "type must be string").cstr();
                 const String& source=params.as_string(1, "source must be string");                  const String* source=params.count()>1? &params.as_string(1, "source must be string"):0;
                 const char* comment=params.count()>2? params.as_string(2, "comment must be string").cstr()                  const char* comment=params.count()>2? params.as_string(2, "comment must be string").cstr():0;
                         :0;                  throw Exception(type, source, "%s", comment?comment:"");
                 throw Exception(type, &source, "%s", comment?comment:"");          }
         }   }
 }  
   static void _sleep_operator(Request& r, MethodParams& params) {
           double seconds=params.as_double(0, "seconds must be double", r);
           pa_sleep((int)trunc(seconds), (int)trunc(seconds*1000));
    }
   
 #if defined(WIN32) && defined(_DEBUG)  #if defined(WIN32) && defined(_DEBUG)
 #       define PA_BPT  #       define PA_BPT
 static void _bpt(Request& r, MethodParams& params) {  static void _bpt(Request&, MethodParams&) {
         _asm int 3;          _asm int 3;
 }  }
 #endif  #endif
Line 666  VClassMAIN::VClassMAIN(): VClass() { Line 866  VClassMAIN::VClassMAIN(): VClass() {
         add_native_method("taint", Method::CT_ANY, _taint, 1, 2);          add_native_method("taint", Method::CT_ANY, _taint, 1, 2);
   
         // ^process[code]          // ^process[code]
         add_native_method("process", Method::CT_ANY, _process, 1, 2);          add_native_method("process", Method::CT_ANY, _process, 1, 3);
   
         // ^rem{code}          // ^rem{code}
         add_native_method("rem", Method::CT_ANY, _rem, 1, 10000);          add_native_method("rem", Method::CT_ANY, _rem, 1, 10000);
   
         // ^while(condition){code}          // ^while(condition){code}
         add_native_method("while", Method::CT_ANY, _while, 2, 2);          add_native_method("while", Method::CT_ANY, _while, 2, 3);
   
         // ^use[file]          // ^use[file]
         add_native_method("use", Method::CT_ANY, _use, 1, 1);          add_native_method("use", Method::CT_ANY, _use, 1, 1);
   
           // ^break[]
           add_native_method("break", Method::CT_ANY, _break, 0, 0);
           // ^continue[]
           add_native_method("continue", Method::CT_ANY, _continue, 0, 0);
         // ^for[i](from-number;to-number-inclusive){code}[delim]          // ^for[i](from-number;to-number-inclusive){code}[delim]
         add_native_method("for", Method::CT_ANY, _for, 3+1, 3+1+1);          add_native_method("for", Method::CT_ANY, _for, 3+1, 3+1+1);
   
Line 688  VClassMAIN::VClassMAIN(): VClass() { Line 892  VClassMAIN::VClassMAIN(): VClass() {
         add_native_method("connect", Method::CT_ANY, _connect, 2, 2);          add_native_method("connect", Method::CT_ANY, _connect, 2, 2);
   
   
           // ^cache[file_spec](time){code}[{catch code}] time=0 no cache
         // ^cache[file_spec] delete cache          // ^cache[file_spec] delete cache
         // ^cache[file_spec](time){code} time=0 no cache          // ^cache[] get current expiration time
         add_native_method("cache", Method::CT_ANY, _cache, 1, 3);          add_native_method("cache", Method::CT_ANY, _cache, 0, 4);
                   
         // switch          // switch
   
Line 703  VClassMAIN::VClassMAIN(): VClass() { Line 908  VClassMAIN::VClassMAIN(): VClass() {
         // try-catch          // try-catch
   
         // ^try{code}{catch code}          // ^try{code}{catch code}
         add_native_method("try", Method::CT_ANY, _try_operator, 2, 2);          add_native_method("try", Method::CT_ANY, _try_operator, 2, 3);
         // ^throw[$exception hash]          // ^throw[$exception hash]
         // ^throw[type;source;comment]          // ^throw[type;source;comment]
         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);
 }  }
   
 // constructor & configurator  // constructor & configurator

Removed from v.1.127.2.22.2.33  
changed lines
  Added in v.1.181


E-mail: