#pragma warning(disable: 4786) #include "TCoreCalc.h" #pragma hdrstop namespace Upp { #define LLOG(x) // LOG(x) String CalcType::Describe() { return t_("lambda-expression"); } RegisterStdCalcTypeName(const CalcNode *) String CalcType::Describe() { return t_("lambda-expression"); } RegisterStdCalcTypeName(CalcNodePtr) int CharFilterCalcIdent(int c) { return IsIdent(c) ? c : 0; } int CharFilterCalcUpperIdent(int c) { return IsIdent(c) ? ToUpper(c) : 0; } RegisterHelpTopicTitle("CalcNode", t_("Expression evaluator")) enum { MAX_NESTING_DEPTH = 1000, }; HelpCalc::HelpCalc(CalcGate proc, const char *ident_, const char *topic_, String (*groupfn_)(), const char *function_) //: generator(gen) { Init(proc, ident_, topic_, groupfn_, function_); AddHelpCalc(*this); // ord = HelpCalcIndex::Get().Add(*this); } void HelpCalc::Init(CalcGate proc_, const char *ident_, const char *topic_, String (*groupfn_)(), const char *function_) { proc = proc_; ident = ident_; topic = topic_; groupfn = groupfn_; function = function_; /* sortname = id = _id; const char *p = (head ? head : ""); const char *b = p; if(*p == '|') { // sort name b = ++p; sortname.Clear(); while(*p && *p != '|' && *p != '\n') { if((*p == '\\' || *p == '`') && p[1]) p++; sortname << *p++; } if(*p == '|') p++; while(*p && *p != '\n' && *p != '\t' && (byte)*p <= ' ') p++; b = p; } while(*p && *p != '\n' && *p != '\t' && *p != '(') p++; const char *e = p; while(e > b && e[-1] == ' ') e--; if(e > b) result = String(b, e); if(*p == '(') { // arguments given p++; do { while(*p && (byte)*p <= ' ') p++; if(*p == ')') { p++; break; } if(*p == ':') { // everything is type b = ++p; while(*p && *p != '\n' && *p != ',' && *p != ')') p++; arg_types.Add(String(b, p)); arg_names.Add(""); } else { // name or type + name const char *s = b = p; while(*p && *p != '\n' && *p != ',' && *p != ')') if(*p == '\1') { // skip inline part while(*p && *p++ != '\1') ; } else if((*p == '`' || *p == '\\') && p[1]) p += 2; else if(*p == '[') { // skip QTF leader p++; while(*p && *p++ != ' ') ; } else if(*p++ == ' ') s = p; for(e = p; e > b && (byte)e[-1] <= ' '; e--) ; if(s == b) { // name only arg_types.Add(""); arg_names.Add(String(b, e)); } else { // type name arg_names.Add(String(s, e)); while(s > b && (byte)*s <= ' ') s--; arg_types.Add(String(b, s)); } } } while(*p++ == ','); if(p[-1] != ')') { p--; NEVER(); // missing right parenthesis in function declaration (found p) } } while(*p && *p != '\t' && *p != '\n' && (byte)*p <= ' ') p++; if(*p == '\t') { // group / subgroup given while(*++p && (byte)*p <= ' ') p++; b = p; while(*p && *p != '/' && *p != '\n') if((*p == '\\' || *p == '`') && *++p) p++; // explicit char else if(*p == '[') { // skip qtf tags while(*p && *p++ != ' ') ; } else p++; for(e = p; e > b && (byte)e[-1] <= ' '; e--) ; group = String(b, e); stripped_group = StripQtf(group); if(*p == '/') p++; while(*p && *p != '\n' && (byte)*p <= ' ') p++; b = p; while(*p && *p != '\n') p++; for(e = p; e > b && (byte)e[-1] <= ' '; e--) ; subgroup = String(b, e); stripped_subgroup = StripQtf(subgroup); } if(*p && *p != '\n') NEVER(); // end of line expected after arguments & group spec (found p) if(*p == '\n') p++; index_entries = p; CalcPacket packet(*(CalcContext *)0, id); packet.help = true; VERIFY(proc(packet)); if(packet.args.GetCount() > arg_types.GetCount()) arg_types.SetCount(packet.args.GetCount()); if(arg_types.GetCount() > arg_names.GetCount()) arg_names.SetCount(arg_types.GetCount()); else arg_types.SetCount(arg_names.GetCount()); for(int i = 0; i < packet.args.GetCount(); i++) if(IsNull(arg_types[i])) arg_types[i] = packet.args[i].Format(); if(IsNull(result)) result = packet.result.Format(); */ } String HelpCalc::GetTitle() const { CalcPacket packet(*(CalcContext *)0, ident); packet.help = true; VERIFY(proc(packet)); Vector arg_types; arg_types.SetCount(packet.args.GetCount()); for(int i = 0; i < packet.args.GetCount(); i++) arg_types[i] = StdFormat(packet.args[i]); String result = StdFormat(packet.result); String out; if(!IsAlpha(*ident)) out << "operátor \1" << ident << "\1 "; else out << "\1" << ident << "\1"; if(!arg_types.IsEmpty()) { for(int i = 0; i < arg_types.GetCount(); i++) out << (i ? "\1, \1" : "(\1") << arg_types[i]; out << "\1)"; } out << " [S \xAE] \1" << result << "\1"; return out; } HelpCalcMap& GetHelpCalcMap() { static HelpCalcMap map; return map; } void AddHelpCalc(const HelpCalc& calc) { GetHelpCalcMap().Add(&calc); } Vector GetHelpCalcGroups() { Index group; const HelpCalcMap& map = GetHelpCalcMap(); for(int i = 0; i < map.GetCount(); i++) if(map[i]->groupfn) group.FindAdd(map[i]->groupfn()); Vector gnames = group.PickKeys(); Sort(gnames, GetLanguageInfo()); if(gnames.GetCount() >= 2 && gnames[0].IsEmpty()) { gnames.Remove(0); gnames.Add(Null); } return gnames; } static void HelpCalcAutoIndex(Index& entries, Vector& drls) { const HelpCalcMap& map = GetHelpCalcMap(); for(int i = 0; i < map.GetCount(); i++) { const HelpCalc& hc = *map[i]; WString entry; entry << WString("[/ \1") << hc.ident.ToWString() << WString(IsAlpha(*hc.ident) ? "\1], funkce" : "\1], operátor"); int f = entries.Find(entry); String drl = HelpAppDPP(hc.topic); if(f >= 0) drls[f] << ";!" << drl; else { entries.Add(entry); drls.Add('!' + drl); } } } HELPINITBLOCK { RegisterHelpAutoIndex(&HelpCalcAutoIndex); } ////////////////////////////////////////////////////////////////////// // CalcFunctionNode:: Value CalcFunctionNode::Calc(CalcContext& context) const { One packet = new CalcPacket(context, name); ScanArgs(*packet); return context.Calc(*packet); } String CalcFunctionNode::Format() const { String out; bool is_oper = !IsAlpha(*name); if(is_oper) out << "operator "; out << name; if(!args.IsEmpty()) { for(int i = 0; i < args.GetCount(); i++) { String s = args[i]->Format(); if(IsNull(s)) return Null; out << (i ? ", " : "(") << s; } out << ")"; } else if(is_oper) out << "()"; return out; } String CalcConstNode::Format() const { if(IsNull(value)) return "null"; if(IsNumber(value)) return FormatDouble(value, 15); if(IsString(value)) return AsCString(String(value)); if(IsDateTime(value)) { Time tm(value); if(tm.hour == 0 && tm.minute == 0 && tm.second == 0) return NFormat("date(%d, %d, %d)", tm.year, tm.month, tm.day); else return NFormat("time(%d, %d, %d, %d, %d, %d)", tm.year, tm.month, tm.day, tm.hour, tm.minute, tm.second); } return Null; } Value CalcConstNode::Calc(CalcContext& context) const { return value; } Value CalcLambdaNode::Calc(CalcContext& context) const { return CalcType::ToValue(~args[0]); } String CalcLambdaNode::Format() const { String e = args[0]->Format(); if(IsNull(e)) return Null; return NFormat("#(%s)", e); } String CalcLogOrNode::Format() const { String l = args[0]->Format(), r = args[1]->Format(); if(IsNull(l) || IsNull(r)) return Null; return NFormat("(%s) || (%s)", l, r); } CalcNodePtr CalcLogOrNode::Optimize(CalcContext& context) { if(!args[0]->IsConst()) return this; if(CalcType::ValueTo(args[0]->Calc(context))) return new CalcConstNode(1); return args[1]; } Value CalcLogOrNode::Calc(CalcContext& context) const { Value var = args[0]->Calc(context); if(!CalcType::IsType(var)) Expect(t_("boolean value"), context.FormatNull(var)); if(CalcType::ValueTo(var)) return CalcType::ToValue(true); var = args[1]->Calc(context); if(!CalcType::IsType(var)) Expect(t_("boolean value"), context.FormatNull(var)); return CalcType::ToValue(CalcType::ValueTo(var)); } String CalcLogAndNode::Format() const { String l = args[0]->Format(), r = args[1]->Format(); if(IsNull(l) || IsNull(r)) return Null; return NFormat("(%s) && (%s)", l, r); } Value CalcLogAndNode::Calc(CalcContext& context) const { Value var = args[0]->Calc(context); if(!CalcType::IsType(var)) Expect(t_("boolean value"), context.FormatNull(var)); if(!CalcType::ValueTo(var)) return CalcType::ToValue(false); var = args[1]->Calc(context); if(!CalcType::IsType(var)) Expect(t_("boolean value"), context.FormatNull(var)); return CalcType::ToValue(CalcType::ValueTo(var)); } CalcNodePtr CalcLogAndNode::Optimize(CalcContext& context) { if(!args[0]->IsConst()) return this; if(!CalcType::ValueTo(args[0]->Calc(context))) return new CalcConstNode(0); return args[1]; } String CalcSelectNode::Format() const { String c = args[COND]->Format(), t = args[IFTRUE]->Format(), f = args[IFFALSE]->Format(); if(IsNull(c) || IsNull(t) || IsNull(f)) return Null; return NFormat("(%s) ? (%s) : (%s)", c, t, f); } Value CalcSelectNode::Calc(CalcContext& context) const { Value var = args[COND]->Calc(context); if(!CalcType::IsType(var)) Expect(t_("boolean value"), context.FormatNull(var)); return CalcType::ValueTo(var) ? args[IFTRUE]->Calc(context) : args[IFFALSE]->Calc(context); } CalcNodePtr CalcSelectNode::Optimize(CalcContext& context) { if(!args[0]->IsConst()) return this; return args[CalcType::ValueTo(args[0]->Calc(context)) ? 1 : 2]; } String CalcSwitchNode::Format() const { String out; String c = args[0]->Format(); if(IsNull(c)) return Null; out = NFormat("(%s){", c); for(int i = 1; i < args.GetCount(); i++) { String s = args[i]->Format(); if(IsNull(s)) return Null; out << (i == 1 ? "(" : ", (") << s << ')'; } out << '}'; return out; } Value CalcSwitchNode::Calc(CalcContext& context) const { Value value = args[0]->Calc(context); if(args.GetCount() == 2) { // special case - 1-arg switch works as Nvl if(!IsNull(value)) return value; return args[1]->Calc(context); } int i = 0, t = (args.GetCount() - 1) & ~1; for(; i < t; i += 2) if(value == args[i + 1]->Calc(context)) return args[i + 2]->Calc(context); if(i + 1 < args.GetCount()) return args[i + 1]->Calc(context); else return value; } CalcNodePtr CalcSwitchNode::Optimize(CalcContext& context) { if(!args[0]->IsConst()) return this; Value value = args[0]->Calc(context); if(args.GetCount() == 2) { // special case - 1-arg switch works as Nvl if(!IsNull(value)) return new CalcConstNode(value); return args[1]; } int i = 0, t = (args.GetCount() - 1) & ~1; for(; i < t; i += 2) { if(!args[i + 1]->IsConst()) return this; if(value == args[i + 1]->Calc(context)) return args[i + 2]; } if(i + 1 < args.GetCount()) return args[i + 1]; else return new CalcConstNode(value); } CalcSequenceNode::CalcSequenceNode(pick_ Vector nodes) : CalcNode(",") { args = nodes; } CalcSequenceNode::CalcSequenceNode(CalcNodePtr node1, CalcNodePtr node2) : CalcNode(",") { args.SetCount(2); args[0] = node1; args[1] = node2; } Value CalcSequenceNode::Calc(CalcContext& context) const { Value v; for(int i = 0; i < args.GetCount(); i++) if(!!args[i]) v = args[i]->Calc(context); return v; } String CalcSequenceNode::Format() const { StringBuffer out; for(int i = 0; i < args.GetCount(); i++) out << (i ? ", (" : "(") << args[i]->Format() << ')'; return out; } CalcNodePtr CalcSequenceNode::Optimize(CalcContext& context) { Vector opt; bool diff = false; opt.SetCount(args.GetCount()); for(int i = 0; i < args.GetCount(); i++) { if(!!args[i] && args[i] != (opt[i] = args[i]->Optimize(context))) diff = true; } if(diff) return new CalcSequenceNode(opt); return this; } void CalcSymbols::Clear() { var_index.Clear(); var_value.Clear(); var_const.Clear(); functions.Clear(); } void CalcSymbols::Remove(String name) { int i = var_index.Find(name); if(i >= 0) { var_index.Remove(i); var_value.Remove(i); var_const.Remove(i); } } CalcContext::Global::Global(const String& name, CalcProc proc) { GetGlobals().Add(name, proc); } CalcContext::CalcContext() : nesting(0) , opt_const(false) { language = GetCurrentLanguage(); UseCalcBasic(); stack.Add(); } CalcContext::CalcContext(const CalcContext& context, int) : nesting(context.nesting), stack(context.stack, 0) {} CalcContext::~CalcContext() {} Value CalcContext::Get(String name) const { for(int l = stack.GetCount(); --l >= 0;) { const CalcSymbols& level = stack[l]; int f = level.var_index.Find(name); if(f >= 0) return level.var_value[f]; } return Value(); } Value CalcContext::TryEvaluate(String expr) { try { return Evaluate(expr); } catch(Exc e) { return ErrorValue(e); } } Value CalcContext::Evaluate(String expr) { CalcNodePtr node = CalcParser().ScanVoid(expr); return !!node ? node->Calc(*this) : Value(); } double CalcContext::EvaluateDouble(String expr) { CalcNodePtr node = CalcParser().ScanVoid(expr); return !!node ? node->CalcDouble(*this) : double(Null); } String CalcContext::EvaluateString(String expr) { CalcNodePtr node = CalcParser().ScanVoid(expr); return !!node ? node->CalcString(*this) : String(Null); } int CalcContext::EvaluateInt(String expr) { CalcNodePtr node = CalcParser().ScanVoid(expr); return !!node ? node->CalcInt(*this) : int(Null); } int64 CalcContext::EvaluateInt64(String expr) { CalcNodePtr node = CalcParser().ScanVoid(expr); return !!node ? node->CalcInt64(*this) : int(Null); } Date CalcContext::EvaluateDate(String expr) { CalcNodePtr node = CalcParser().ScanVoid(expr); return !!node ? node->CalcDate(*this) : Date(Null); } Time CalcContext::EvaluateTime(String expr) { CalcNodePtr node = CalcParser().ScanVoid(expr); return !!node ? node->CalcTime(*this) : Time(Null); } bool CalcContext::EvaluateBool(String expr, bool null_value) { CalcNodePtr node = CalcParser().ScanVoid(expr); return !!node ? node->CalcBool(*this) : null_value; } String CalcContext::OptimizeConstant(String expr) { CalcNodePtr in_expr = CalcParser().ScanVoid(expr); if(!in_expr) return expr; CalcNodePtr out_expr = OptimizeConstant(in_expr); if(out_expr == in_expr) return expr; return Nvl(out_expr->Format(), expr); } static CalcNodePtr OptimizeConstantRaw(CalcNodePtr src, CalcContext& context) { CalcNodePtr clone = src->Clone(); if(!clone) return src; bool modf = false; for(int i = 0; i < clone->args.GetCount(); i++) { CalcNodePtr opt = OptimizeConstantRaw(src->args[i], context); if(!!opt && opt != src->args[i]) { modf = true; clone->args[i] = opt; } } if(modf) src = clone; src = src->Optimize(context); if(src->IsConst()) return src; try { Value v = src->Calc(context); if(!v.IsError()) return new CalcConstNode(v); } catch(Exc e) { } return src; } CalcNodePtr CalcContext::OptimizeConstant(CalcNodePtr src) { if(!src) return src; try { opt_const = true; CalcNodePtr out = OptimizeConstantRaw(src, *this); opt_const = false; return out; } catch(Exc e) { opt_const = false; return src; } } String CalcContext::Convert(String s, bool throw_errors, const UPP::Convert& convert) { if(s.IsEmpty()) return Null; Vector sparts; Vector cparts; ParseConvert(s, sparts, cparts, throw_errors); return CalcConvert(sparts, cparts, throw_errors, convert); } void CalcContext::ParseConvert(String s, Vector& sparts, Vector& cparts, bool throw_errors) { Vector ctexts; ParseConvert(s, sparts, ctexts); cparts.SetCount(ctexts.GetCount()); for(int i = 0; i < ctexts.GetCount(); i++) try { cparts[i] = CalcParser().ScanVoid(ctexts[i]); } catch(Exc e) { if(throw_errors) throw; cparts[i] = new CalcConstNode(e); } } void CalcContext::ParseConvert(String s, Vector& sparts, Vector& cparts) { const char *p = s; sparts.Add(); while(*p) { const char *b = p; while(*p && (*p != '<' || p[1] != ':')) p++; sparts.Top().Cat(b, p - b); if(*p) { p += 2; b = p; while(*p && (*p != ':' || p[1] != '>')) if(*p++ == '\"') { while(*p && *p != '\"') if(*p++ == '\\' && *p) p++; if(*p) p++; } cparts.Add(String(b, p)); sparts.Add(); if(*p) p += 2; } } } String CalcContext::CalcConvert(const Vector& sparts, const Vector& cparts, bool throw_errors, const UPP::Convert& convert) { String out; for(int i = 0; i < sparts.GetCount(); i++) { out.Cat(sparts[i]); if(i < cparts.GetCount()) try { out.Cat(FormatText(cparts[i]->Calc(*this), convert)); } catch(Exc e) { if(throw_errors) throw; out.Cat(e); } } return out; } Value CalcContext::Calc(CalcPacket& packet) { try { #ifdef _DEBUG int level = nesting; Value out = TryCalc(packet); ASSERT(nesting == level); return out; #else return TryCalc(packet); #endif } catch(Exc e) { LOG(e); throw Exc(packet.name + ": " + e); } } Value CalcContext::TryCalc(CalcPacket& packet) { LLOG("TryCalc <- " << packet.name << " + " << packet.args.GetCount() << " args" << BeginIndent); // calculate locals & globals if(++nesting > MAX_NESTING_DEPTH) { nesting--; throw Exc(NFormat(t_("function nesting too deep (%d levels)"), MAX_NESTING_DEPTH)); } bool none = true; for(int l = stack.GetCount(); --l >= 0;) { const CalcSymbols& level = stack[l]; int i = level.functions.Find(packet.name); if(i >= 0) { // try functions none = false; for(; i >= 0; i = level.functions.FindNext(i)) try { if(level.functions[i](packet)) { LLOG(EndIndent << "//TryCalc->" << StdFormat(packet.result)); nesting--; return packet.result; } } catch(...) { nesting--; throw; } } if(packet.args.IsEmpty() && (i = level.var_index.Find(packet.name)) >= 0) { if(opt_const && !level.var_const[i]) { nesting--; throw Exc(NFormat(t_("%s is not a constant"), packet.name)); } LLOG(EndIndent << "//TryCalc->" << StdFormat(level.var_value[i])); nesting--; return level.var_value[i]; } } for(int e = externals.GetCount(); --e >= 0;) { const CalcSymbols& level = *externals[e]; int i = level.functions.Find(packet.name); if(i >= 0) { // try functions none = false; for(; i >= 0; i = level.functions.FindNext(i)) try { if(level.functions[i](packet)) { LLOG(EndIndent << "//TryCalc->" << StdFormat(packet.result)); nesting--; return packet.result; } } catch(...) { nesting--; throw; } } if(packet.args.IsEmpty() && (i = level.var_index.Find(packet.name)) >= 0) { if(opt_const && !level.var_const[i]) { nesting--; throw Exc(NFormat(t_("%s is not a constant"), packet.name)); } LLOG(EndIndent << "//TryCalc->" << StdFormat(level.var_value[i])); nesting--; return level.var_value[i]; } } { // try globals const CalcProcMap& globals = GetGlobals(); int i = globals.Find(packet.name); if(i >= 0) { none = false; for(; i >= 0; i = globals.FindNext(i)) try { if(globals[i](packet)) { LLOG(EndIndent << "//TryCalc->" << StdFormat(packet.result)); nesting--; return packet.result; } } catch(...) { nesting--; throw; } } } nesting--; if(none) throw Exc((packet.args.IsEmpty() ? t_("variable/constant '") : t_("function '")) + packet.name + t_("' not found")); String s = packet.name; if(!packet.args.IsEmpty()) { bool no_text = (s == "text"); s << '(' << FormatNull(packet.args[0], no_text); for(const Value *p = packet.args.Begin() + 1, *e = packet.args.End(); p < e; p++) s << ", " << FormatNull(*p, no_text); s << ')'; } s << ": "; switch(packet.args.GetCount()) { case 0: s << t_("missing arguments"); break; case 1: s << t_("illegal type ") << packet.GetTypeNames() << t_(" or number of parameters"); break; default: s << t_("illegal type combination ") << packet.GetTypeNames(); break; } throw Exc(s); } CalcProcMap& CalcContext::GetGlobals() { static CalcProcMap list; return list; } String CalcContext::GetTypeName(Value value) const { if(IsNull(value)) return "Null"; /* switch(value.GetType()) { case INT_V: return CalcType::Describe(); case DOUBLE_V: return CalcType::Describe(); case STRING_V: return CalcType::Describe(); case TIME_V: return CalcType