Files correlati : Ricompilazione Demo : [ ] Commento : aggiunta la funzione ZZEROFILL da usare nelle maschere (serve quando si ha un campo non zerofill e lo si deve confrontare con un altro che ivece lo è: vedi maschere di co0) git-svn-id: svn://10.65.10.50/trunk@20664 c028cbd2-c16b-5b4b-a496-9718f37d4682
		
			
				
	
	
		
			1339 lines
		
	
	
		
			30 KiB
		
	
	
	
		
			C++
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			1339 lines
		
	
	
		
			30 KiB
		
	
	
	
		
			C++
		
	
	
		
			Executable File
		
	
	
	
	
| #define __EXPR_CPP
 | |
| #include <expr.h>
 | |
| #include <date.h>
 | |
| #include <validate.h>
 | |
| 
 | |
| ///////////////////////////////////////////////////////////
 | |
| // TValue
 | |
| ///////////////////////////////////////////////////////////
 | |
| 
 | |
| real& TValue::number()
 | |
| { 
 | |
|   if (_t == _strexpr) 
 | |
|   { 
 | |
|     if (TDate::isdate(_s))
 | |
|       _r = TDate(_s).date2ansi();
 | |
|     else
 | |
|       _r = real(_s); 
 | |
|     _t = _numexpr; 
 | |
|   } 
 | |
|   return _r; 
 | |
| }
 | |
| 
 | |
| TString& TValue::string() 
 | |
| { 
 | |
|   if (_t == _numexpr) 
 | |
|   { 
 | |
|     _s = _r.string(); 
 | |
|     _t = _strexpr; 
 | |
|   } 
 | |
|   return _s; 
 | |
| }
 | |
| 
 | |
| ///////////////////////////////////////////////////////////
 | |
| // TCodearray
 | |
| ///////////////////////////////////////////////////////////
 | |
| 
 | |
| void TCodearray::clear()
 | |
| {
 | |
|   destroy();
 | |
|   TArray::add(new TCode(_endsym));
 | |
| }
 | |
| 
 | |
| void TCodearray::add(TCodesym sym, const TValue& val)
 | |
| {
 | |
|   TArray::insert(new TCode(sym, val), last());
 | |
| }
 | |
| 
 | |
| ///////////////////////////////////////////////////////////
 | |
| // TCode
 | |
| ///////////////////////////////////////////////////////////
 | |
| 
 | |
| TObject* TCode::dup() const
 | |
| {
 | |
|   return new TCode(*this);
 | |
| }    
 | |
| 
 | |
| TCode& TCode::operator =(const TCode& b)
 | |
| {
 | |
|   _sym = b._sym;
 | |
|   _val = b._val;
 | |
|   return *this;
 | |
| }
 | |
| 
 | |
| ///////////////////////////////////////////////////////////
 | |
| // TVar
 | |
| ///////////////////////////////////////////////////////////
 | |
| 
 | |
| TObject* TVar::dup() const
 | |
| {
 | |
|   return new TVar(*this);
 | |
| }    
 | |
| 
 | |
| int TVar::compare(const TSortable& s) const
 | |
| {
 | |
|   const TVar& v = (const TVar&)s;
 | |
|   return _name.compare(v._name);
 | |
| }
 | |
| 
 | |
| ///////////////////////////////////////////////////////////
 | |
| // TVararray
 | |
| ///////////////////////////////////////////////////////////
 | |
| 
 | |
| void TVararray::add(const char* name, const TValue& val)
 | |
| {
 | |
|   _vars.add(new TVar(name, val));
 | |
|   _vars.sort();  // Brutto, ma per ora va bene qui;
 | |
| }
 | |
| 
 | |
| TVar* TVararray::find(int i) const
 | |
| {
 | |
|   TVar* v = (TVar*)_vars.objptr(i);
 | |
|   CHECKD(v, "Variabile non trovata:", i);
 | |
|   return v;
 | |
| }
 | |
| 
 | |
| TVar* TVararray::find(const char* name) const
 | |
| {
 | |
|   int mi = 0, ma = _vars.last(), i = 0;
 | |
|   while(mi <= ma)
 | |
|   {
 | |
|     i = (mi+ma)/2;
 | |
|     TVar* v = find(i);
 | |
|     const int cmp = v->getname().compare(name);
 | |
|     if (cmp == 0)
 | |
|       return v;
 | |
|     if (cmp < 0)
 | |
|       mi = i+1;
 | |
|     else
 | |
|       ma = i-1;
 | |
|   }
 | |
| 
 | |
|   for (i = _vars.last(); i >= 0; i--)
 | |
|   {
 | |
|     TVar* v = find(i);
 | |
|     if (v->getname() == name)
 | |
|       return v;  // Se passa di qua ... m'incazzo
 | |
|   }  
 | |
| 
 | |
|   CHECKS(NULL, "Variabile non trovata:", name);
 | |
|   return NULL;
 | |
| }
 | |
| 
 | |
| void TVararray::set(const char* name, const real& val)
 | |
| {
 | |
|   TVar* v = find(name);
 | |
|   if (v != NULL) 
 | |
|     *v = val;
 | |
| }
 | |
| 
 | |
| 
 | |
| void TVararray::set(const char* name, const char* val)
 | |
| {
 | |
|   TVar* v = find(name);
 | |
|   if (v != NULL) 
 | |
|     *v = val;
 | |
| }
 | |
| 
 | |
| const real& TVararray::getnum(const char* name)
 | |
| {
 | |
|   TVar* v = find(name);
 | |
|   if (v != NULL)
 | |
|     return v->number();
 | |
|   return ZERO;
 | |
| }
 | |
| 
 | |
| const TString& TVararray::getstring(const char* name)
 | |
| {
 | |
|   TVar* v = find(name);
 | |
|   if (v != NULL)
 | |
|     return v->string();
 | |
|   return EMPTY_STRING;
 | |
| }
 | |
| 
 | |
| ///////////////////////////////////////////////////////////
 | |
| // TEval_stack
 | |
| ///////////////////////////////////////////////////////////
 | |
| 
 | |
| void TEval_stack::push(bool b)
 | |
| {
 | |
|   if (_sp < _data.items())
 | |
|   {
 | |
|     _sp++;
 | |
|     peek_real() = b ? 1.0 : 0.0;
 | |
|   }
 | |
|   else  
 | |
|     TStack::push(new TValue(b ? real(1.0) : ZERO));
 | |
| }
 | |
| 
 | |
| void TEval_stack::push(int n)
 | |
| {
 | |
|   if (_sp < _data.items())
 | |
|   {
 | |
|     _sp++;
 | |
|     peek_real() = n;
 | |
|   }
 | |
|   else  
 | |
|     TStack::push(new TValue(real(n)));
 | |
| }
 | |
| 
 | |
| 
 | |
| void TEval_stack::push(const real& r)
 | |
| {
 | |
|   if (_sp < _data.items())
 | |
|   {
 | |
|     _sp++;
 | |
|     peek_real() = r;
 | |
|   }
 | |
|   else  
 | |
|     TStack::push(new TValue(r));
 | |
| }
 | |
| 
 | |
| void TEval_stack::push(const TString& s)
 | |
| {   
 | |
|   if (_sp < _data.items())
 | |
|   {
 | |
|     _sp++;
 | |
|     peek_string() = s;
 | |
|   }
 | |
|   else  
 | |
|     TStack::push(new TValue(s));
 | |
| }
 | |
| 
 | |
| void TEval_stack::push(const char* s)
 | |
| {
 | |
|   if (_sp < _data.items())
 | |
|   {
 | |
|     _sp++;
 | |
|     peek_string() = s;
 | |
|   }
 | |
|   else  
 | |
|     TStack::push(new TValue(s));
 | |
| }
 | |
| 
 | |
| real& TEval_stack::pop_real()
 | |
| {          
 | |
|   TValue& o = (TValue&)pop();
 | |
|   return o.number();
 | |
| }
 | |
| 
 | |
| real& TEval_stack::peek_real()
 | |
| {          
 | |
|   TValue& o = (TValue&)peek(0);
 | |
|   return o.number();
 | |
| }
 | |
| 
 | |
| TString& TEval_stack::pop_string()
 | |
| {          
 | |
|   TValue& o = (TValue&)pop();
 | |
|   return o.string();
 | |
| }
 | |
| 
 | |
| TString& TEval_stack::peek_string()
 | |
| {          
 | |
|   TValue& o = (TValue&)peek(0);
 | |
|   return o.string();
 | |
| }
 | |
| 
 | |
| ///////////////////////////////////////////////////////////
 | |
| // TExpression
 | |
| ///////////////////////////////////////////////////////////
 | |
| 
 | |
| TExpression::TExpression(const char* expression, TTypeexp type, bool ignore_err)
 | |
|            : _original(expression)
 | |
| {
 | |
|   _ignore_error=ignore_err;
 | |
|   _error=0;
 | |
|   _dirty = true;
 | |
|   _type = type;
 | |
|   compile(_original, type);
 | |
| }
 | |
| 
 | |
| 
 | |
| TExpression::TExpression(TTypeexp type, bool ignore_err)
 | |
| {
 | |
|   _ignore_error=ignore_err;   
 | |
|   _error=0;
 | |
|   _dirty = FALSE;
 | |
|   _type = type;
 | |
|   _code.clear();
 | |
| }
 | |
| 
 | |
| TExpression::TExpression(const TExpression & expr)
 | |
|            : _code(expr._code), _var(expr._var), 
 | |
|              _val(expr._val), _ignore_error(expr._ignore_error),
 | |
|              _error(expr._error), _dirty(expr._dirty), 
 | |
|              _user_func_defined(expr._user_func_defined),
 | |
|              _type(expr._type), _original(expr._original)
 | |
| {
 | |
| }
 | |
| 
 | |
| TObject* TExpression::dup() const
 | |
| {
 | |
|   TExpression* o = new TExpression(*this);
 | |
|   return o;
 | |
| }    
 | |
| 
 | |
| const real & TExpression::as_real()
 | |
| {            
 | |
|   if (user_func_dirty() || _dirty) 
 | |
|     eval();
 | |
|   _dirty = FALSE;
 | |
|   return _val.number();
 | |
| }
 | |
| 
 | |
| const TString & TExpression::as_string()
 | |
| {            
 | |
|   if (user_func_dirty() || _dirty) 
 | |
|     eval();
 | |
|   _dirty = FALSE;
 | |
|   return _val.string();
 | |
| }
 | |
| 
 | |
| bool TExpression::as_bool()
 | |
| {            
 | |
|   return !as_real().is_zero();
 | |
| }
 | |
| 
 | |
| void TExpression::print_on(ostream& out) const       
 | |
| { out << _original; }
 | |
| 
 | |
| void TExpression::evaluate_user_func(int index, int nparms, TEval_stack& stack, TTypeexp  curtype) const
 | |
| {                
 | |
|   NFCHECK("Unknown function %d.", index);
 | |
|   for (int i = nparms; i > 0; i--)
 | |
|     stack.pop(); 
 | |
|   stack.push(ZERO);
 | |
| }    
 | |
| 
 | |
| void TExpression::setvar(const char* varname, const real& val)
 | |
| {
 | |
|   if (_var.getnum(varname) != val)
 | |
|   {
 | |
|     _var.set(varname, val);
 | |
|     _dirty = TRUE;
 | |
|   }  
 | |
| }
 | |
| 
 | |
| void TExpression::setvar(int varnum, const real& val)
 | |
| {
 | |
|   if (_var.getnum(varnum) != val)
 | |
|   {
 | |
|     _var.set(varnum, val);
 | |
|     _dirty = TRUE;
 | |
|   }  
 | |
| }
 | |
| 
 | |
| void TExpression::setvar(const char* varname, const char* val)
 | |
| {
 | |
|   if (_var.getstring(varname) != val)
 | |
|   {
 | |
|     _var.set(varname, val);
 | |
|     _dirty = true;
 | |
|   }  
 | |
| }
 | |
| 
 | |
| 
 | |
| void TExpression::setvar(int varnum, const char* val)
 | |
| {
 | |
|   if (_var.getstring(varnum) != val)
 | |
|   {
 | |
|     _var.set(varnum, val);
 | |
|     _dirty = true;
 | |
|   }  
 | |
| }
 | |
| 
 | |
| bool TExpression::print_error(const char* msg) const
 | |
| {
 | |
|   return yesnofatal_box("%s", msg);
 | |
| }
 | |
| 
 | |
| static bool str2date(const TString& s, TDate& d)
 | |
| {
 | |
|   if (s.blank() || s == "0")
 | |
|     return true;
 | |
| 
 | |
|   if (TDate::isdate(s))
 | |
|   {
 | |
|     d = s;
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| static bool all_zeroes(const TString& str)
 | |
| {
 | |
|   bool yes = str.not_empty();
 | |
|   for (const char* s = str; *s && yes; s++)
 | |
|     yes = (*s == '0') || isspace(*s);
 | |
|   return yes;
 | |
| }
 | |
| 
 | |
| void TExpression::eval()
 | |
| {                                   
 | |
|   TEval_stack evalstack;
 | |
|   TBit_array  types;
 | |
|   int         type_pointer = 0;
 | |
|   TCode       instr;
 | |
|     
 | |
|   types.set(type_pointer, (_type == _numexpr));
 | |
|   
 | |
|   TTypeexp type = _type;
 | |
|   
 | |
|   _code.begin();
 | |
|   while (!_code.end())
 | |
|   {
 | |
|     instr = _code.step();
 | |
|     switch (instr.getsym())
 | |
|     {
 | |
|     case _invalid:
 | |
|     case _endsym:
 | |
|       break;
 | |
|     case _variable:
 | |
|       if (type == _strexpr)
 | |
|       {
 | |
|         const TString & s1 = _var.getstring(instr.string());
 | |
|         evalstack.push(s1);
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|         const real & r1 = _var.getnum(instr.string());
 | |
|         evalstack.push(r1);
 | |
|       }
 | |
|       break;
 | |
|     case _number:    
 | |
|       {
 | |
|         const real & r1 = instr.number();
 | |
|         evalstack.push(r1);
 | |
|       }
 | |
|       break;
 | |
|     case _string:
 | |
|       {
 | |
|         const TString & s1 = instr.string();
 | |
|         evalstack.push(s1);
 | |
|       }
 | |
|       break;
 | |
|     case _plus:                    
 | |
|       if (type == _strexpr)
 | |
|       {  
 | |
|         // Non unire le seguenti righe
 | |
|         const TString& s = evalstack.pop_string();
 | |
|         evalstack.peek_string() << s;
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|         // Non unire le seguenti righe
 | |
|         const real& r = evalstack.pop_real();
 | |
|         evalstack.peek_real() += r;
 | |
|       }
 | |
|       break;
 | |
|     case _minus:
 | |
|       {
 | |
|         const real& r = evalstack.pop_real();
 | |
|         evalstack.peek_real() -= r;
 | |
|       }
 | |
|       break;
 | |
|     case _multiply:
 | |
|       {
 | |
|         const real& r = evalstack.pop_real();
 | |
|         evalstack.peek_real() *= r;
 | |
|       }
 | |
|       break;
 | |
|     case _divide:
 | |
|       {
 | |
|         const real& r = evalstack.pop_real();
 | |
|         if (r.is_zero())
 | |
|         {
 | |
|           if (!evalstack.peek_real().is_zero())
 | |
|             if (_ignore_error)
 | |
|               _error=1;
 | |
|             else
 | |
|               print_error("Divisione per zero!");
 | |
|         } else
 | |
|           evalstack.peek_real() /= r;
 | |
|       }
 | |
|       break;
 | |
|     case _chgs:
 | |
|       {
 | |
|         real & r = evalstack.peek_real();
 | |
|         r = -r;
 | |
|       }
 | |
|       break;
 | |
|     case _and:
 | |
|       {
 | |
|         const real & r2 = evalstack.pop_real();
 | |
|         real & r1 = evalstack.peek_real();   
 | |
|         r1 = (!r1.is_zero() && !r2.is_zero()) ? UNO : ZERO;
 | |
|       }
 | |
|       break;
 | |
|     case _or:
 | |
|       {
 | |
|         const real & r2 = evalstack.pop_real();
 | |
|         real & r1 = evalstack.peek_real();   
 | |
|         r1 = (r1 != ZERO || r2 != ZERO) ? UNO : ZERO;
 | |
|       }
 | |
|       break;
 | |
|     case _not:
 | |
|       {
 | |
|         real & r1 = evalstack.peek_real();
 | |
|         r1 = (r1 == ZERO) ? UNO : ZERO;
 | |
|       }
 | |
|       break;
 | |
|     case _equal:
 | |
|       if (type == _strexpr)
 | |
|       {
 | |
|         const TString & s2 = evalstack.pop_string();
 | |
|         const TString & s1 = evalstack.pop_string(); 
 | |
|         evalstack.push(s1 == s2);
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|         const real & r2 = evalstack.pop_real();
 | |
|         real & r1 = evalstack.peek_real();
 | |
|         r1 = (r1 == r2) ? 1.0 : 0.0;
 | |
|       }
 | |
|       break;
 | |
|     case _match:                                    
 | |
|      {
 | |
|         const TString & s2 = evalstack.pop_string();
 | |
|         const TString & s1 = evalstack.pop_string(); 
 | |
|         evalstack.push(s1.match(s2, true)); // Match ignoring case
 | |
|      }
 | |
|      break;
 | |
|     case _noteq:
 | |
|       if (type == _strexpr)
 | |
|       {
 | |
|         const TString & s2 = evalstack.pop_string();
 | |
|         TString & s1 = evalstack.pop_string(); 
 | |
|         evalstack.push(s1 != s2);
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|         const real & r2 = evalstack.pop_real();
 | |
|         real & r1 = evalstack.peek_real();
 | |
|         r1 = (r1 != r2) ? 1.0 : 0.0;
 | |
|       }
 | |
|       break;
 | |
|     case _lt:
 | |
|       if (type == _strexpr)
 | |
|       {
 | |
|         const TString & s2 = evalstack.pop_string();
 | |
|         const TString & s1 = evalstack.pop_string(); 
 | |
|         evalstack.push(s1 < s2);
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|         const real & r2 = evalstack.pop_real();
 | |
|         real & r1 = evalstack.peek_real();
 | |
|         r1 = (r1 < r2) ? 1.0 : 0.0;
 | |
|       }
 | |
|       break;
 | |
|     case _gt:
 | |
|       if (type == _strexpr)
 | |
|       {
 | |
|         const TString & s2 = evalstack.pop_string();
 | |
|         const TString & s1 = evalstack.pop_string(); 
 | |
|         evalstack.push(s1 > s2);
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|         const real & r2 = evalstack.pop_real();
 | |
|         real & r1 = evalstack.peek_real();
 | |
|         r1 = (r1 > r2) ? 1.0 : 0.0;
 | |
|       }
 | |
|       break;
 | |
|     case _lteq:
 | |
|       if (type == _strexpr)
 | |
|       {
 | |
|         const TString& s2 = evalstack.pop_string();
 | |
|         const TString& s1 = evalstack.pop_string(); 
 | |
|         evalstack.push(s1 <= s2);
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|         const real& r2 = evalstack.pop_real();
 | |
|         real& r1 = evalstack.peek_real();
 | |
|         r1 = (r1 <= r2) ? 1.0 : 0.0;
 | |
|       }
 | |
|       break;
 | |
|     case _gteq:
 | |
|       if (type == _strexpr)
 | |
|       {
 | |
|         const TString& s2 = evalstack.pop_string();
 | |
|         const TString& s1 = evalstack.pop_string(); 
 | |
|         evalstack.push(s1 >= s2);
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|         const real & r2 = evalstack.pop_real();
 | |
|         real & r1 = evalstack.peek_real();
 | |
|         r1 = (r1 >= r2) ? 1.0 : 0.0;
 | |
|       }
 | |
|       break;  
 | |
|     case _userfunc: 
 | |
|       {
 | |
|         const int nparms = (int) evalstack.pop_real().integer();
 | |
|         const int index = atoi(instr.string());
 | |
|         
 | |
|         evaluate_user_func(index, nparms, evalstack, type);
 | |
|       }
 | |
|       break;
 | |
|     case _sqrt:
 | |
|       {
 | |
|         real& r = evalstack.peek_real();
 | |
|         if (r < ZERO)
 | |
|         {
 | |
|           if (_ignore_error)
 | |
|             _error=1;
 | |
|           else
 | |
|             print_error("Radice negativa!");
 | |
|           r = -r;
 | |
|         } 
 | |
|         r = sqrt(r);
 | |
|       }
 | |
|       break;
 | |
|     case _sqr:
 | |
|       evalstack.peek_real() = sqr(evalstack.peek_real());
 | |
|       break;
 | |
|     case _exp10:
 | |
|       evalstack.peek_real() = exp10(evalstack.peek_real());
 | |
|       break;
 | |
|     case _exp:
 | |
|       evalstack.peek_real() = exp(evalstack.peek_real());
 | |
|       break;
 | |
|     case _log10:
 | |
|       evalstack.peek_real() = log10(evalstack.peek_real());
 | |
|       break;
 | |
|     case _log:
 | |
|       evalstack.peek_real() = log(evalstack.peek_real());
 | |
|       break;
 | |
|     case _sin:
 | |
|       evalstack.peek_real() = sin(evalstack.peek_real());
 | |
|       break;
 | |
|     case _cos:
 | |
|       evalstack.peek_real() = cos(evalstack.peek_real());
 | |
|       break;
 | |
|     case _tan:
 | |
|       evalstack.peek_real() = tan(evalstack.peek_real());
 | |
|       break;
 | |
|     case _left:
 | |
|       {
 | |
|         const int len = (int)evalstack.pop_real().integer();
 | |
|         TString & s1 = evalstack.peek_string();
 | |
|         s1.cut(len);
 | |
|       }
 | |
|       break;
 | |
|     case _right:
 | |
|       {
 | |
|         const int len = (int)evalstack.pop_real().integer();
 | |
|         TString & s1 = evalstack.peek_string();
 | |
|         s1 = s1.right(len);
 | |
|       }
 | |
|       break;
 | |
|     case _mid:
 | |
|       {
 | |
|         int count = (int)evalstack.pop_real().integer();   
 | |
|         if (count == 0) count--;
 | |
|         int from = (int)evalstack.pop_real().integer() - 1; 
 | |
|         if (from < 0) from = 0;
 | |
|         TString & s1 = evalstack.peek_string();
 | |
|         s1 = s1.mid(from, count);
 | |
|       }  
 | |
|       break;
 | |
|     case _substr:
 | |
|       {
 | |
|         const int to = (int)evalstack.pop_real().integer();   
 | |
|         int from = (int)evalstack.pop_real().integer() - 1; 
 | |
|         if (from < 0) from = 0;
 | |
|         TString & s1 = evalstack.peek_string();
 | |
|         s1 = s1.sub(from, to);
 | |
|       }  
 | |
|       break;
 | |
|     case _len:
 | |
|       {
 | |
|         const TString& s1 = evalstack.pop_string();
 | |
|         evalstack.push(s1.len());
 | |
|       }  
 | |
|       break;
 | |
|     case _pow:
 | |
|       {
 | |
|         const real & r2 = evalstack.pop_real();
 | |
|         real & r1 = evalstack.peek_real();
 | |
|         r1 = pow(r1, r2);
 | |
|       }
 | |
|       break;
 | |
|     case _min:
 | |
|       {
 | |
|         const real & r2 = evalstack.pop_real();
 | |
|         real & r1 = evalstack.peek_real();
 | |
|         if (r2 < r1)
 | |
|           r1 = r2;
 | |
|       }
 | |
|       break;
 | |
|     case _max:
 | |
|       {
 | |
|         const real & r2 = evalstack.pop_real();
 | |
|         real & r1 = evalstack.peek_real();
 | |
|         if (r2 > r1)
 | |
|           r1 = r2;
 | |
|       }
 | |
|       break;
 | |
|     case _upper:
 | |
|       evalstack.peek_string().upper();
 | |
|       break;
 | |
|     case _trim:
 | |
|       evalstack.peek_string().trim();
 | |
|       break;
 | |
|     case _round:
 | |
|       {
 | |
|         const int ndec = (int)(evalstack.pop_real()).integer();
 | |
|         real & r = evalstack.peek_real();
 | |
|         r.round(ndec);
 | |
|       }
 | |
|       break;
 | |
|     case _trunc:
 | |
|       {
 | |
|         const int ndec = (int)(evalstack.pop_real()).integer();
 | |
|         real & r = evalstack.peek_real();
 | |
|         r.trunc(ndec);
 | |
|       }
 | |
|       break;
 | |
|     case _ceil:
 | |
|       {
 | |
|         const int ndec = (int)(evalstack.pop_real()).integer();
 | |
|         real & r = evalstack.peek_real();
 | |
|         r.ceil(ndec);
 | |
|       }
 | |
|       break;
 | |
|     case _perc:
 | |
|       {
 | |
|         const real & r2 = evalstack.pop_real();
 | |
|         real & r1 = evalstack.peek_real();
 | |
|         r1 = real(r1 * r2 / 100.0);
 | |
|       }
 | |
|       break;
 | |
|     case _scorp:
 | |
|       {
 | |
|         const real & r2 = evalstack.pop_real();
 | |
|         real & r1 = evalstack.peek_real();
 | |
|         r1 -= r1 * r2 / (r2 + 100.0);
 | |
|       }
 | |
|       break;
 | |
|     case _if:
 | |
|       {     
 | |
|         if (type == _strexpr)
 | |
|         {
 | |
|           const TString & s1 = evalstack.pop_string();
 | |
|           const TString & s2 = evalstack.pop_string();
 | |
|           const real & cond = evalstack.pop_real();
 | |
|           evalstack.push(cond.is_zero() ? s1 : s2);
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|           const real & r1 = evalstack.pop_real();
 | |
|           const real & r2 = evalstack.pop_real();
 | |
|           real & cond = evalstack.peek_real();
 | |
|           cond = cond.is_zero() ? r1 : r2;
 | |
|         }
 | |
|       }
 | |
|       break;
 | |
|     case _ansi:
 | |
|       {            
 | |
|         TString& s = evalstack.peek_string();
 | |
| 				if (!s.empty())
 | |
| 				{
 | |
| 					const TDate d(s);
 | |
| 					s = d.string(ANSI);
 | |
| 				}
 | |
|       }
 | |
|       break;     
 | |
|     case _num:      
 | |
|       type_pointer++;
 | |
|       types.set(type_pointer, FALSE);
 | |
|       type = _strexpr;
 | |
|       break;
 | |
|     case _endnum:
 | |
|       type_pointer--;
 | |
|       type = types[type_pointer] ? _numexpr : _strexpr;
 | |
|       if (type == _numexpr)
 | |
|         evalstack.peek_real();
 | |
|       break;
 | |
|     case _str:
 | |
|       type_pointer++;
 | |
|       types.set(type_pointer, TRUE);
 | |
|       type = _numexpr;
 | |
|       break;
 | |
|     case _endstr:
 | |
|       type_pointer--;
 | |
|       type = types[type_pointer] ? _numexpr : _strexpr;
 | |
|       if (type == _strexpr)
 | |
|         evalstack.peek_string();
 | |
|       break;
 | |
|     case _between:
 | |
|       {
 | |
|         TString& s3 = evalstack.pop_string();
 | |
|         if (all_zeroes(s3)) s3.cut(0); // Svuota una stringa finale di soli zeri
 | |
|         TString& s2 = evalstack.pop_string();
 | |
|         if (all_zeroes(s2)) s2.cut(0); // Svuota una stringa iniziale di soli zeri
 | |
|         const TString& s1 = evalstack.pop_string();
 | |
|         bool good = true;
 | |
|         if (s2.full() || s3.full())
 | |
|         {
 | |
|           bool done = false;
 | |
|           if (TDate::isdate(s1))
 | |
|           {
 | |
|             TDate d2, d3;
 | |
|             done = str2date(s2, d2) && str2date(s3, d3);
 | |
|             if (done)
 | |
|             {
 | |
|               const TDate d1(s1);
 | |
|               good = (d1 >= d2) && (!d3.ok() || d1 <= d3);
 | |
|             }
 | |
|           }
 | |
|           if (!done)
 | |
|           {
 | |
|             if (type == _strexpr)
 | |
|               good = s1 >= s2 && (s3.empty() || s1 <= s3);
 | |
|             else
 | |
|             {
 | |
|               const real r1(s1), r2(s2), r3(s3);
 | |
|               good = (s2.empty() || r1 >= r2) && (s3.empty() || r1 <= r3);
 | |
|             }
 | |
|           }
 | |
|         }
 | |
|         evalstack.push(good ? UNO : ZERO);
 | |
|       }
 | |
|       break;
 | |
|     case _cfcheck:
 | |
|       {
 | |
|         const TString& s1 = evalstack.pop_string();
 | |
|         const TString& s2 = evalstack.pop_string();
 | |
|         const bool good = s1.len() < s2.len() ? cf_check(s1, s2) : cf_check(s2, s1);
 | |
|         evalstack.push(good ? UNO : ZERO);
 | |
|       }
 | |
|       break;
 | |
|     case _picheck:
 | |
|       {
 | |
|         const TString& s1 = evalstack.pop_string();
 | |
|         const TString& s2 = evalstack.pop_string();
 | |
|         const bool good = s1.len() < s2.len() ? pi_check(s1, s2) : pi_check(s2, s1);
 | |
|         evalstack.push(good ? UNO : ZERO);
 | |
|       }
 | |
|       break;
 | |
|     case _year:
 | |
|       {
 | |
|         const TDate d = evalstack.pop_string();
 | |
|         evalstack.push(d.year());
 | |
|       }
 | |
|       break;
 | |
|     case _zerofill:
 | |
|       {
 | |
|         const int len = evalstack.pop_real().integer();
 | |
|         TString& s = evalstack.peek_string();
 | |
|         s.right_just(len, '0');
 | |
|       }
 | |
|       break;
 | |
|     default:
 | |
|       NFCHECK("operazione non valida %d", (int) instr.getsym());
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
|   
 | |
|   // Lo stack non e' vuoto
 | |
|   if (_code.items() > 1)
 | |
|     _val = (const TValue&)evalstack.pop();
 | |
|   else
 | |
|     _val = ZERO;    
 | |
| }
 | |
| 
 | |
| 
 | |
| HIDDEN const char* _s;
 | |
| HIDDEN char  _tok[81];
 | |
| 
 | |
| TCodesym TExpression::tok2fun(const char* tok) const
 | |
| {                                                   
 | |
|   const int MAX_TOK = 33;
 | |
|   HIDDEN const char* fnstr[MAX_TOK] = { "ANSI",    "BETWEEN","CEIL",    "CF_CHECK","COS",   
 | |
|                                         "EXP",     "EXP10",  "IF",      "LEFT",    "LEN",     
 | |
|                                         "LOG",     "LOG10",  "MAX",     "MID",     "MIN",     
 | |
|                                         "NUM",     "PERC",   "PI_CHECK","POW",     "RIGHT",
 | |
|                                         "ROUND",   "SCORP",  "SIN",     "SQR",     "SQRT",   
 | |
|                                         "STR",     "SUBSTR", "TAN",     "TRIM",    "TRUNC",  
 | |
|                                         "UPPER",   "YEAR",   "ZEROFILL" };
 | |
|   
 | |
|   HIDDEN TCodesym fntok[MAX_TOK] =    { _ansi,   _between, _ceil,     _cfcheck,  _cos,    
 | |
|                                         _exp,    _exp10,   _if,       _left,     _len,      
 | |
|                                         _log,    _log10,   _max,      _mid,      _min,      
 | |
|                                         _num,    _perc,    _picheck,  _pow,      _right,    
 | |
|                                         _round,  _scorp,   _sin,      _sqr,      _sqrt,   
 | |
|                                         _str,    _substr,  _tan,      _trim,     _trunc,  
 | |
|                                         _upper,  _year,    _zerofill };
 | |
|                                   
 | |
|   int f = 0, l = MAX_TOK-1, i = MAX_TOK/2;                    
 | |
|   for (;;)
 | |
|   {
 | |
| //    i = (f+l)>>1;                  
 | |
|     i = f + (toupper(*tok) - *fnstr[f]) * (l - f + 1) / (*fnstr[l] - *fnstr[f] + 1);
 | |
|       
 | |
|     if (i < f || i > l)
 | |
|       return _invalid;
 | |
|       
 | |
|     const int cmp = xvt_str_compare_ignoring_case(tok, fnstr[i]);
 | |
| 
 | |
|     if (cmp == 0) 
 | |
|       break;
 | |
|     if (cmp > 0) f = i+1;
 | |
|     else         l = i-1;
 | |
|     
 | |
|     if (f > l) 
 | |
|       return _invalid;
 | |
|   }      
 | |
|   CHECKD(i >= 0 && i < MAX_TOK, "Invalid function index ", i);
 | |
|   return fntok[i];
 | |
| }
 | |
| 
 | |
|                                   
 | |
| 
 | |
| TCodesym TExpression::__gettoken()
 | |
| {
 | |
|   TCodesym sym = _invalid;
 | |
|   int i = 0;
 | |
|   
 | |
|   _tok[0] = '\0';
 | |
|   while (isspace(*_s)) _s++;
 | |
|   if (!*_s) return _endsym;
 | |
| 
 | |
|   bool square_bracket = FALSE;  
 | |
|   
 | |
|   if (isdigit(*_s) || (*_s == '.'))
 | |
|   {
 | |
|     sym = _number;
 | |
|     while (isalnum(*_s) || (*_s == '_') || (*_s == '.') ||
 | |
|            (*_s == ':')  || (*_s == '@')  || 
 | |
|            (sym == _variable && *_s == '[') || 
 | |
|            (sym == _number && *_s == '-' ) || 
 | |
|            (square_bracket && (*_s == ',' || *_s == ']'))
 | |
|           )
 | |
|     {
 | |
|       if (*_s == '-')
 | |
|       {
 | |
|         if (_s[1] != '>') break;
 | |
|         _tok[i++] = *(_s++);
 | |
|         _tok[i++] = *(_s++);
 | |
|         sym = _variable;
 | |
|       } 
 | |
|       else 
 | |
|       {   
 | |
|         if (sym == _number)
 | |
|         {
 | |
|           if (isalpha(*_s) || (*_s == '_') || (*_s == ':')
 | |
|               /* || (*_s == '[') || (*_s == ']') || (*_s == ',')  */ 
 | |
|              ) 
 | |
|             sym = _variable;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|           if (sym == _variable && *_s == '[')
 | |
|             square_bracket = TRUE;
 | |
|         }
 | |
|         _tok[i++] = *(_s++);
 | |
|       } 
 | |
|     } 
 | |
|     _tok[i] = '\0';
 | |
|     return sym;
 | |
|   }
 | |
|   if (isalpha(*_s) || (*_s == '#') || (*_s == '_'))
 | |
|   {                               
 | |
|     if (*_s == '#')
 | |
|       sym = _variable;
 | |
|     
 | |
|     _tok[i++] = *(_s++); 
 | |
|     
 | |
|     if (sym == _variable && *_s == '-')
 | |
|     {
 | |
|       _tok[i++] = *(_s++);
 | |
|       if (*_s == '>' || isdigit(*_s)) 
 | |
| 	      _tok[i++] = *(_s++);
 | |
| 			else
 | |
| 				return _invalid;                // Non e' una -> (freccia)
 | |
|     }
 | |
|     while (isalnum(*_s) || (*_s == '-') || (*_s == '[') ||
 | |
|            (*_s == ':') || (*_s == '_') || (*_s == '.') ||
 | |
|            (square_bracket && (*_s == ',' || *_s == ']'))
 | |
|           ) 
 | |
|     {
 | |
|       if (*_s == '-')
 | |
|       {
 | |
|         if (_s[1] != '>') break;
 | |
|         _tok[i++] = *(_s++);
 | |
|         _tok[i++] = *(_s++);
 | |
|         sym = _variable;
 | |
|       } else 
 | |
|       if (*_s == '[')
 | |
|       {
 | |
|         sym = _variable;
 | |
|         square_bracket = TRUE;
 | |
|       } else
 | |
|       if (*_s == '.')
 | |
|         sym = _variable;
 | |
| 
 | |
|       _tok[i++] = *(_s++);
 | |
|     } 
 | |
|     _tok[i] = '\0';
 | |
|     
 | |
|     if (sym != _variable)     // So gia' tutto, inutile controllare oltre
 | |
|     {
 | |
|       sym = tok2fun(_tok);    
 | |
|       if (sym != _invalid)
 | |
|         return sym;
 | |
|   		const char * p;
 | |
|   		
 | |
|       for (p = _s; isspace(*p); p++);
 | |
|       if (*p == '(')
 | |
|         return _userfunc; 
 | |
|     }
 | |
|     
 | |
|     return _variable;
 | |
|   }
 | |
|   switch (*_s)
 | |
|   {
 | |
|   case '"' :
 | |
|   case '\'' :
 | |
|     {
 | |
|       const char sep = *_s;
 | |
|       _s++;
 | |
|       while ((*_s) && (*_s != sep))
 | |
|         _tok[i++] = *(_s++);
 | |
|       _tok[i] = '\0';
 | |
|       if (*_s == sep) _s++;
 | |
|       return _string;
 | |
|     }
 | |
|   case ',' :
 | |
|     _tok[i++] = *(_s++);
 | |
|     _tok[i] = '\0';
 | |
|     return _comma;
 | |
|   case ';' :
 | |
|     _tok[i++] = *(_s++);
 | |
|     _tok[i] = '\0';
 | |
|     return _semicolon;
 | |
|   case '(' :
 | |
|   case '{' :
 | |
|     _tok[i++] = *(_s++);
 | |
|     _tok[i] = '\0';
 | |
|     return _lpar;
 | |
|   case ')' :
 | |
|   case '}' :
 | |
|     _tok[i++] = *(_s++);
 | |
|     _tok[i] = '\0';
 | |
|     return _rpar;
 | |
|   case '+' :
 | |
|     _tok[i++] = *(_s++);
 | |
|     _tok[i] = '\0';
 | |
|     return _plus;
 | |
|   case '-' :
 | |
|     _tok[i++] = *(_s++);
 | |
|     _tok[i] = '\0';
 | |
|     return _minus;
 | |
|   case '*' :
 | |
|     _tok[i++] = *(_s++);
 | |
|     _tok[i] = '\0';
 | |
|     return _multiply;
 | |
|   case '/' :
 | |
|     _tok[i++] = *(_s++);
 | |
|     _tok[i] = '\0';
 | |
|     return _divide;
 | |
|   case '&' :
 | |
|     _tok[i++] = *(_s++);
 | |
|     if (*_s != '&') break;
 | |
|     _tok[i++] = *(_s++);
 | |
|     _tok[i] = '\0';
 | |
|     return _and;
 | |
|   case '|' :
 | |
|     _tok[i++] = *(_s++);
 | |
|     if (*_s != '|') break;
 | |
|     _tok[i++] = *(_s++);
 | |
|     _tok[i] = '\0';
 | |
|     return _or;
 | |
|   case '!' :
 | |
|     _tok[i++] = *(_s++);
 | |
|     _tok[i] = '\0';
 | |
|     if (*_s == '=')
 | |
|     {
 | |
|       _tok[i++] = *(_s++);
 | |
|       _tok[i] = '\0';
 | |
|       return _noteq;
 | |
|     }
 | |
|     return _not;
 | |
|   case '=' :
 | |
|     _tok[i++] = *(_s++);
 | |
|     if (*_s == '=') _tok[i++] = *(_s++);
 | |
|     _tok[i] = '\0';
 | |
|     return _equal;
 | |
|   case '?' :
 | |
|     _tok[i++] = *(_s++);
 | |
|     if (*_s == '=') _tok[i++] = *(_s++);
 | |
|     _tok[i] = '\0';
 | |
|     return _match;
 | |
|   case '>' :
 | |
|     _tok[i++] = *(_s++);
 | |
|     _tok[i] = '\0';
 | |
|     if (*_s == '=')
 | |
|     {           
 | |
|       _tok[i++] = *(_s++);
 | |
|       _tok[i] = '\0';
 | |
|       return _gteq;
 | |
|     }
 | |
|     return _gt;
 | |
|   case '<' :
 | |
|     _tok[i++] = *(_s++);
 | |
|     _tok[i] = '\0';
 | |
|     if (*_s == '=')
 | |
|     {
 | |
|       _tok[i++] = *(_s++);
 | |
|       _tok[i] = '\0';
 | |
|       return _lteq;
 | |
|     }
 | |
|     return _lt;
 | |
|   default:
 | |
|     break;
 | |
|   }
 | |
|   return _invalid;
 | |
| }
 | |
| 
 | |
| TCodesym TExpression::__factor(TCodesym startsym)
 | |
| {
 | |
|   TCodesym sym = _invalid;
 | |
|   
 | |
|   switch (startsym)
 | |
|   {
 | |
|   case _lpar:
 | |
|     sym = __gettoken();
 | |
|     sym = __expression(sym);
 | |
|     if (sym == _rpar) sym = __gettoken(); 
 | |
|     else sym = _invalid;
 | |
|     break;
 | |
|   case _variable:
 | |
|     {     
 | |
|       int from = 1, to = 0;
 | |
|       char* quadra = strchr(_tok, '[');
 | |
|       if (quadra)
 | |
|       {
 | |
|         if (sscanf(quadra, "[%d,%d]", &from, &to) == 0)
 | |
|         {
 | |
|           sym = _invalid;
 | |
|           break;
 | |
|         }
 | |
|         *quadra = '\0';
 | |
|       }                        
 | |
|       _code.add(_variable, _tok);
 | |
|       if (quadra)
 | |
|       {
 | |
|         _code.add(_number, real(from));
 | |
|         _code.add(_number, real(to >= from ? to-from+1 : 0));
 | |
|         _code.add(_mid);
 | |
|       } 
 | |
|       
 | |
|       int i;
 | |
|        
 | |
|       for (i = numvar()-1 ; i >= 0; i--)
 | |
|         if (strcmp(_tok, varname(i)) == 0) break;
 | |
|       if (i < 0) _var.add(_tok);
 | |
|       sym = __gettoken();
 | |
|     }
 | |
|     break;
 | |
|   case _number:
 | |
|   case _string:
 | |
|     _code.add(startsym, _tok);
 | |
|     sym = __gettoken();
 | |
|     break;  
 | |
|   case _userfunc:
 | |
|     {       
 | |
|       TValue val (_tok);
 | |
|       int parms_found = 0;
 | |
|       sym = __function(-1, &parms_found);
 | |
|       if (sym != _invalid)
 | |
|       {
 | |
|         
 | |
|         const int index = parse_user_func(val.string(), parms_found);
 | |
|         if (index < 0) 
 | |
|         {
 | |
|           strncpy(_tok, val.string(), sizeof(_tok));
 | |
|           sym = _invalid;
 | |
|         }
 | |
|         else
 | |
|         {                     
 | |
|           _code.add(_number, real(parms_found));
 | |
|           val = real(index);
 | |
|           _code.add(startsym, val);
 | |
|           _user_func_defined = TRUE;                      
 | |
|         }  
 | |
|       }
 | |
|     }
 | |
|     break;  
 | |
|   case _sqrt:
 | |
|   case _sqr:
 | |
|   case _exp10:
 | |
|   case _exp:
 | |
|   case _log10:
 | |
|   case _log:
 | |
|   case _sin:
 | |
|   case _cos:
 | |
|   case _tan:
 | |
|   case _ansi:
 | |
|   case _upper:
 | |
|   case _trim:
 | |
|   case _len:
 | |
|   case _year:
 | |
|     sym = __function(1);
 | |
|     _code.add(startsym);
 | |
|     break;
 | |
|   case _left:
 | |
|   case _right:
 | |
|   case _pow:
 | |
|   case _min:
 | |
|   case _max:
 | |
|   case _round: 
 | |
|   case _ceil:
 | |
|   case _trunc:
 | |
|   case _perc:
 | |
|   case _scorp:
 | |
|   case _cfcheck:
 | |
|   case _picheck:
 | |
|   case _zerofill:
 | |
|     sym = __function(2);
 | |
|     _code.add(startsym);
 | |
|     break;
 | |
|   case _between:
 | |
|   case _mid:
 | |
|   case _substr:
 | |
|     sym = __function(3);
 | |
|     _code.add(startsym);
 | |
|     break;    
 | |
|   case _if:
 | |
|     sym = __function(3);
 | |
|     _code.add(startsym);
 | |
|     break;    
 | |
|   case _num:
 | |
|   case _str:
 | |
|     _code.add(startsym);
 | |
|     sym = __function(1);
 | |
|     _code.add(startsym == _num ? _endnum : _endstr);
 | |
|     break;
 | |
|   default:
 | |
|     break;
 | |
|   }
 | |
|   return sym;
 | |
| }
 | |
| 
 | |
| 
 | |
| TCodesym TExpression::__term(TCodesym startsym)
 | |
| {
 | |
|   TCodesym sym = __factor(startsym);
 | |
|   while (sym == _multiply || sym == _divide)
 | |
|   {
 | |
|     const TCodesym savedsym = sym;
 | |
|     sym = __gettoken();
 | |
|     sym = __factor(sym);
 | |
|     _code.add(savedsym);
 | |
|   }
 | |
|   return sym;
 | |
| }
 | |
| 
 | |
| 
 | |
| TCodesym TExpression::__expression(TCodesym startsym)
 | |
| {
 | |
|   TCodesym sym;
 | |
|   if ((startsym == _minus) || (startsym == _not) || (startsym == _plus))
 | |
|     sym =__gettoken();
 | |
|   else 
 | |
|     sym = startsym;
 | |
|   sym = __term(sym);
 | |
|   if ((startsym == _minus) || (startsym == _not))
 | |
|     _code.add(startsym == _not ? startsym  : _chgs);
 | |
|   while ((sym != _endsym) &&
 | |
|          ((sym == _minus) || (sym == _plus) || (sym == _equal) ||
 | |
|           (sym == _noteq) || (sym == _gt) || (sym == _lt) ||
 | |
|           (sym == _gteq) || (sym == _lteq) || (sym == _and) ||
 | |
|           (sym == _or) || (sym == _match)))
 | |
|   {
 | |
|     TCodesym savedsym   = sym;
 | |
|     sym = __gettoken();
 | |
|     sym = __term(sym);
 | |
|     _code.add(savedsym);
 | |
|   }
 | |
|   return(sym);
 | |
| }
 | |
| 
 | |
| TCodesym TExpression::__function(int nparms, int* actual_nparams)
 | |
| {
 | |
|   TCodesym sym = __gettoken();
 | |
|   if (sym != _lpar)
 | |
|     return _invalid;
 | |
|   int found = 0;
 | |
|   sym = __gettoken();
 | |
|   if (sym == _rpar)
 | |
|   {
 | |
|     if (actual_nparams) 
 | |
|       *actual_nparams = found;
 | |
|     return nparms <= 0 ? __gettoken() : _invalid;
 | |
|   }
 | |
|   found++;
 | |
|   sym = __expression(sym);
 | |
|   while (sym == _comma || sym == _semicolon)
 | |
|   {
 | |
|     sym = __gettoken();
 | |
|     found++;
 | |
|     sym = __expression(sym);
 | |
|   }
 | |
|   
 | |
|   if (sym == _rpar)
 | |
|   {
 | |
|     if (actual_nparams) 
 | |
|       *actual_nparams = found;
 | |
|     return nparms < 0 || found == nparms ? __gettoken() : _invalid;
 | |
|   }
 | |
|   return _invalid;
 | |
| }
 | |
| 
 | |
| bool TExpression::set(const char* expression, TTypeexp type) 
 | |
| { 
 | |
|   _original = expression; 
 | |
|   _type = type; 
 | |
|   _dirty = TRUE; 
 | |
|   return compile(_original, type); 
 | |
| }
 | |
| 
 | |
| bool TExpression::compile(const TString& expression, TTypeexp type)
 | |
| {                      
 | |
|   _user_func_defined = FALSE;                      
 | |
|   _s = expression;
 | |
|   _type = type;  
 | |
|   _val = ZERO;
 | |
|   _code.clear();
 | |
|   if (expression.blank()) 
 | |
|     return true;
 | |
|  
 | |
|   TCodesym currsym = __gettoken();                      
 | |
|   bool ok = currsym != _invalid;
 | |
|   if (ok) 
 | |
|     ok = __expression(currsym) == _endsym;
 | |
|   if (!ok)
 | |
|   {
 | |
|     _error = 2;
 | |
|     if (!_ignore_error)
 | |
|     {
 | |
|       TString msg; 
 | |
|       msg << "Espressione errata : " << _original;
 | |
|       print_error(msg);
 | |
|     }
 | |
|   }  
 | |
| 
 | |
|   return ok;
 | |
| }
 | |
| 
 | |
| const char* TExpression::last_token() const
 | |
| {
 | |
|   return _tok;
 | |
| }
 |