mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-05-15 22:02:49 -06:00
1564 lines
34 KiB
C++
1564 lines
34 KiB
C++
#pragma warning(disable: 4786)
|
|
|
|
#include "TCoreCalc.h"
|
|
#pragma hdrstop
|
|
|
|
NAMESPACE_UPP
|
|
|
|
#define LLOG(x) // LOG(x)
|
|
|
|
String CalcType<const CalcNode *>::Describe() { return "lambda-výraz"; }
|
|
RegisterStdCalcTypeName(const CalcNode *)
|
|
|
|
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_)())
|
|
//: generator(gen)
|
|
{
|
|
Init(proc, ident_, topic_, groupfn_);
|
|
AddHelpCalc(*this);
|
|
// ord = HelpCalcIndex::Get().Add(*this);
|
|
}
|
|
|
|
void HelpCalc::Init(CalcGate proc_, const char *ident_, const char *topic_, String (*groupfn_)())
|
|
{
|
|
proc = proc_;
|
|
ident = ident_;
|
|
topic = topic_;
|
|
groupfn = groupfn_;
|
|
/*
|
|
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<String> 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<String> GetHelpCalcGroups()
|
|
{
|
|
Index<String> group;
|
|
const HelpCalcMap& map = GetHelpCalcMap();
|
|
for(int i = 0; i < map.GetCount(); i++)
|
|
if(map[i]->groupfn)
|
|
group.FindAdd(map[i]->groupfn());
|
|
Vector<String> 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<WString>& entries, Vector<String>& 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<CalcPacket> 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<const CalcNode *>::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<bool>::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<bool>::IsType(var))
|
|
Expect(t_("boolean value"), context.FormatNull(var));
|
|
if(CalcType<bool>::ValueTo(var))
|
|
return CalcType<bool>::ToValue(true);
|
|
var = args[1]->Calc(context);
|
|
if(!CalcType<bool>::IsType(var))
|
|
Expect(t_("boolean value"), context.FormatNull(var));
|
|
return CalcType<bool>::ToValue(CalcType<bool>::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<bool>::IsType(var))
|
|
Expect(t_("boolean value"), context.FormatNull(var));
|
|
if(!CalcType<bool>::ValueTo(var))
|
|
return CalcType<bool>::ToValue(false);
|
|
var = args[1]->Calc(context);
|
|
if(!CalcType<bool>::IsType(var))
|
|
Expect(t_("boolean value"), context.FormatNull(var));
|
|
return CalcType<bool>::ToValue(CalcType<bool>::ValueTo(var));
|
|
}
|
|
|
|
CalcNodePtr CalcLogAndNode::Optimize(CalcContext& context)
|
|
{
|
|
if(!args[0]->IsConst())
|
|
return this;
|
|
if(!CalcType<bool>::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<bool>::IsType(var))
|
|
Expect(t_("boolean value"), context.FormatNull(var));
|
|
return CalcType<bool>::ValueTo(var) ? args[IFTRUE]->Calc(context) : args[IFFALSE]->Calc(context);
|
|
}
|
|
|
|
CalcNodePtr CalcSelectNode::Optimize(CalcContext& context)
|
|
{
|
|
if(!args[0]->IsConst())
|
|
return this;
|
|
return args[CalcType<bool>::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);
|
|
}
|
|
|
|
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::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::Evaluate(String expr)
|
|
{
|
|
CalcParser parser;
|
|
try
|
|
{
|
|
CalcNodePtr cn = parser.ScanVoid(expr);
|
|
if(!cn)
|
|
return Value();
|
|
return cn->Calc(*this);
|
|
}
|
|
catch(Exc e)
|
|
{
|
|
return ErrorValue(e);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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)
|
|
{
|
|
CalcNodePtr node = CalcParser().ScanVoid(expr);
|
|
return !!node && node->CalcBool(*this);
|
|
}
|
|
|
|
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<String> sparts;
|
|
Vector<CalcNodePtr> cparts;
|
|
ParseConvert(s, sparts, cparts, throw_errors);
|
|
return CalcConvert(sparts, cparts, throw_errors, convert);
|
|
}
|
|
|
|
void CalcContext::ParseConvert(String s, Vector<String>& sparts, Vector<CalcNodePtr>& cparts, bool throw_errors)
|
|
{
|
|
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++;
|
|
}
|
|
try
|
|
{
|
|
CalcNodePtr cn = CalcParser().ScanVoid(String(b, p));
|
|
if(!!cn)
|
|
{
|
|
cparts.Add() = cn;
|
|
sparts.Add();
|
|
}
|
|
}
|
|
catch(Exc e)
|
|
{
|
|
if(throw_errors)
|
|
throw;
|
|
sparts.Top().Cat(e);
|
|
}
|
|
if(*p)
|
|
p += 2;
|
|
}
|
|
}
|
|
}
|
|
|
|
String CalcContext::CalcConvert(const Vector<String>& sparts, const Vector<CalcNodePtr>& 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<int>::Describe();
|
|
case DOUBLE_V: return CalcType<double>::Describe();
|
|
case STRING_V: return CalcType<String>::Describe();
|
|
case TIME_V: return CalcType<Time>::Describe();
|
|
case DATE_V: return CalcType<Date>::Describe();
|
|
}
|
|
*/ return CalcTypeNameConvert::Format(typeid(*value.GetVoidPtr()).name());
|
|
}
|
|
|
|
String CalcContext::FormatNull(Value value, bool no_text)
|
|
{
|
|
if(value.IsError())
|
|
return GetErrorText(value);
|
|
if(IsNull(value))
|
|
return "Null";
|
|
if(IsString(value))
|
|
return '\"' + (String)value + '\"';
|
|
if(no_text)
|
|
{
|
|
if(IsDateTime(value) || IsNumber(value))
|
|
return StdFormat(value);
|
|
return GetTypeName(value) + "(?)";
|
|
}
|
|
|
|
try
|
|
{
|
|
return FormatText(value);
|
|
}
|
|
catch(Exc e)
|
|
{
|
|
return e;
|
|
}
|
|
}
|
|
|
|
String CalcContext::FormatText(Value value, const UPP::Convert& convert)
|
|
{
|
|
if(value.IsError())
|
|
throw Exc(GetErrorText(value));
|
|
Value v = convert.Format(value);
|
|
if(IsNull(v) || IsString(v))
|
|
return v;
|
|
CalcPacket packet(*this, "text");
|
|
packet.args.SetCount(1);
|
|
packet.args[0] = v;
|
|
return StdFormat(Calc(packet));
|
|
}
|
|
|
|
void CalcContext::PushLevel()
|
|
{
|
|
if(stack.GetCount() >= MAX_STACK_DEPTH)
|
|
throw Exc(t_("Maximum nesting depth (") + IntStr(MAX_STACK_DEPTH) + t_(") exceeded (infinite recursion?)"));
|
|
stack.Add();
|
|
}
|
|
|
|
void CalcContext::PopLevel()
|
|
{
|
|
if(stack.GetCount() <= 1)
|
|
throw Exc(t_("Expression stack underflow"));
|
|
stack.Drop();
|
|
}
|
|
|
|
void CalcContext::RemoveExternal(CalcSymbols& e)
|
|
{
|
|
int i = FindIndex(externals, &e);
|
|
if(i >= 0)
|
|
externals.Remove(i);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// CalcPacket::
|
|
|
|
String CalcPacket::GetTypeNames() const
|
|
{
|
|
String result;
|
|
result << '(';
|
|
for(int i = 0; i < args.GetCount(); i++)
|
|
{
|
|
if(i)
|
|
result << ", ";
|
|
result << context.GetTypeName(args[i]);
|
|
}
|
|
result << ')';
|
|
return result;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// CalcNode::
|
|
|
|
CalcNode::~CalcNode()
|
|
{
|
|
}
|
|
|
|
Value CalcNode::TryCalc(CalcContext& context) const
|
|
{
|
|
try
|
|
{
|
|
return Calc(context);
|
|
}
|
|
catch(Exc e)
|
|
{
|
|
return ErrorValue(e);
|
|
}
|
|
}
|
|
|
|
double CalcNode::CalcDouble(CalcContext& context) const
|
|
{
|
|
if(this == 0)
|
|
return Null;
|
|
Value v = Calc(context);
|
|
if(!IsNull(v) && !IsNumber(v))
|
|
throw Exc(t_("Number expected, found: ") + context.FormatNull(v));
|
|
return v;
|
|
}
|
|
|
|
String CalcNode::CalcString(CalcContext& context) const
|
|
{
|
|
if(this == 0)
|
|
return Null;
|
|
Value v = Calc(context);
|
|
if(!IsNull(v) && !IsString(v))
|
|
throw Exc(t_("String expected, found: ") + context.FormatNull(v));
|
|
return v;
|
|
}
|
|
|
|
int CalcNode::CalcInt(CalcContext& context) const
|
|
{
|
|
if(this == 0)
|
|
return Null;
|
|
Value v = Calc(context);
|
|
if(!IsNull(v) && !IsNumber(v))
|
|
throw Exc(t_("Number expected, found: ") + context.FormatNull(v));
|
|
return v;
|
|
}
|
|
|
|
Date CalcNode::CalcDate(CalcContext& context) const
|
|
{
|
|
if(this == 0)
|
|
return Null;
|
|
Value v = Calc(context);
|
|
if(!IsNull(v) && !IsDateTime(v))
|
|
throw Exc(t_("Date expected, found: ") + context.FormatNull(v));
|
|
return v;
|
|
}
|
|
|
|
Time CalcNode::CalcTime(CalcContext& context) const
|
|
{
|
|
if(this == 0)
|
|
return Null;
|
|
Value v = Calc(context);
|
|
if(!IsNull(v) && !IsDateTime(v))
|
|
throw Exc(t_("Date/time expected, found: ") + context.FormatNull(v));
|
|
return v;
|
|
}
|
|
|
|
bool CalcNode::CalcBool(CalcContext& context) const
|
|
{
|
|
if(this == 0)
|
|
return false;
|
|
Value v = Calc(context);
|
|
if(IsNull(v))
|
|
return false;
|
|
if(IsString(v))
|
|
return !String(v).IsEmpty();
|
|
if(IsNumber(v))
|
|
return double(v) != 0;
|
|
throw Exc(t_("Date/time expected, found: ") + context.FormatNull(v));
|
|
}
|
|
|
|
void CalcNode::Expect(const char *s, String found) const
|
|
{
|
|
String e;
|
|
e << name << t_(": expected: ") << s << t_(", found: ") << found;
|
|
throw Exc(e);
|
|
}
|
|
|
|
CalcNodePtr GetVariableNode(String varname)
|
|
{
|
|
return new CalcFunctionNode(varname);
|
|
}
|
|
|
|
CalcNodePtr GetFunctionNode(String fnname, pick_ Vector<CalcNodePtr>& args)
|
|
{
|
|
return new CalcFunctionNode(fnname, args);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// CalcFunctionNode::
|
|
|
|
void CalcFunctionNode::ScanArgs(CalcPacket& packet) const
|
|
{
|
|
packet.args.SetCount(args.GetCount());
|
|
Value *a = packet.args.Begin();
|
|
for(const CalcNodePtr *p = args.Begin(), *e = args.End(); p < e; p++, a++)
|
|
if((*a = (*p)->Calc(packet.context)).IsError())
|
|
throw Exc(GetErrorText(*a));
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// CalcParser::
|
|
|
|
CalcParser::CalcParser()
|
|
: start(0), pos(0)
|
|
, op_begin(0), op_end(0), op_last(OP_NONE)
|
|
{
|
|
}
|
|
|
|
int CalcParser::Skip()
|
|
{
|
|
REPEAT:
|
|
while(*pos && *pos <= ' ')
|
|
pos++;
|
|
if(*pos == '/')
|
|
if(pos[1] == '/')
|
|
{ // line comment
|
|
while(*pos && *pos != '\n')
|
|
pos++;
|
|
goto REPEAT;
|
|
}
|
|
else if(pos[1] == '*')
|
|
{
|
|
const char *comment = pos++;
|
|
while(*++pos && !(*pos == '*' && pos[1] == '/'))
|
|
;
|
|
if(*pos)
|
|
{
|
|
pos += 2;
|
|
goto REPEAT;
|
|
}
|
|
else
|
|
throw Exc(t_("/* without matching */"));
|
|
}
|
|
return *pos;
|
|
}
|
|
|
|
bool CalcParser::Expect(const String& s)
|
|
{
|
|
String err = t_("expected ");
|
|
err << (s.GetLength() > 1 ? t_("symbol") : t_("character")) << t_(" '") << s << t_("', found: ");
|
|
int l = strlen(pos);
|
|
if(l > 15)
|
|
err.Cat(pos, 15);
|
|
else if(l)
|
|
err.Cat(pos);
|
|
else
|
|
err.Cat(t_("end of line"));
|
|
throw Exc(err);
|
|
}
|
|
|
|
String CalcParser::GetIdent()
|
|
{
|
|
Skip();
|
|
const char *begin = pos;
|
|
while(IsIdent(*pos))
|
|
pos++;
|
|
const char *end = pos;
|
|
while(end > begin && end[-1] == ' ')
|
|
end--;
|
|
if(end - begin == 8 && !memcmp(begin, "operator", 8))
|
|
{
|
|
while(*pos && (byte)*pos <= ' ')
|
|
pos++;
|
|
begin = pos;
|
|
while(*pos && (byte)*pos > ' ' && *pos != '(')
|
|
pos++;
|
|
end = pos;
|
|
}
|
|
return String(begin, end);
|
|
}
|
|
|
|
Value CalcParser::GetNumberOrDate()
|
|
{
|
|
Skip();
|
|
ASSERT(IsDigit(*pos));
|
|
const char *begin = pos;
|
|
bool comma = false;
|
|
while(IsDigit(*pos))
|
|
pos++;
|
|
if(*pos == '.' && pos[1] != '.')
|
|
{ // try date
|
|
if(!IsDigit(*++pos) && *pos != 'E' && *pos != 'e')
|
|
{ // day
|
|
Date date = GetSysDate();
|
|
int day = atoi(begin);
|
|
if(day < 1 || day > LastDayOfMonth(date).day)
|
|
throw Exc(NFormat(t_("invalid day number ("), day));
|
|
else
|
|
date.day = day;
|
|
return date;
|
|
}
|
|
// might be a decimal
|
|
const char *begin2 = pos;
|
|
while(IsDigit(*++pos))
|
|
;
|
|
if(*pos == '.')
|
|
{ // no, it's a date
|
|
Date date = GetSysDate();
|
|
int day = atoi(begin);
|
|
int month = atoi(begin2);
|
|
int year = date.year;
|
|
if(*++pos == '-' || *pos == '+' || IsDigit(*pos))
|
|
{ // year number given
|
|
const char *begin3 = pos;
|
|
while(IsDigit(*++pos))
|
|
;
|
|
year = atoi(begin3);
|
|
if(pos - begin3 <= 2)
|
|
date.year = year + (year < 80 ? 2000 : 1900);
|
|
else if(date.year >= -4000 && date.year <= 4000)
|
|
date.year = year;
|
|
else
|
|
throw Exc(NFormat("neplatné èíslo roku (%d)", year));
|
|
}
|
|
if(month >= 1 && month <= 12)
|
|
date.month = month;
|
|
else
|
|
throw Exc(NFormat("neplatné èíslo mìsíce (%d)", month));
|
|
if(day >= 1 && day <= LastDayOfMonth(date).day)
|
|
date.day = day;
|
|
else
|
|
throw Exc(NFormat("neplatné èíslo dne (%d)", day));
|
|
return date;
|
|
}
|
|
}
|
|
/* else if(*pos == ',' && IsDigit(pos[1]))
|
|
{ // alternative decimal separator
|
|
comma = true;
|
|
while(IsDigit(*++pos))
|
|
;
|
|
}
|
|
*/
|
|
if(*pos == 'E' || *pos == 'e')
|
|
{ // exponent
|
|
if(*++pos == '+' || *pos == '-')
|
|
pos++;
|
|
while(IsDigit(*pos))
|
|
pos++;
|
|
}
|
|
|
|
const char *temp = NULL;
|
|
errno = 0;
|
|
double value;
|
|
String copy(begin, pos);
|
|
value = ScanDouble(copy, &temp);
|
|
if(IsNull(value) || temp != copy.End())
|
|
throw Exc(NFormat("neplatná èíselná konstanta '%s\'", copy));
|
|
return value;
|
|
}
|
|
|
|
String CalcParser::GetString()
|
|
{
|
|
CParser parser(pos);
|
|
try
|
|
{
|
|
String result = parser.ReadOneString();
|
|
pos = parser.GetPtr();
|
|
return result;
|
|
}
|
|
catch(CParser::Error e)
|
|
{
|
|
throw Exc(e);
|
|
}
|
|
}
|
|
|
|
CalcNodePtr CalcParser::Scan(const char *t)
|
|
{
|
|
Clear(t);
|
|
CalcNodePtr result = ScanAny();
|
|
if(Skip())
|
|
throw Exc(NFormat(t_("illegal characters after expression: %s (expression: %s)"), StringSample(pos, 10), StringSample(t, 10)));
|
|
return result;
|
|
}
|
|
|
|
CalcNodePtr CalcParser::ScanVoid(const char *t)
|
|
{
|
|
Clear(t);
|
|
if(!Skip())
|
|
return CalcNodePtr();
|
|
CalcNodePtr result = ScanAny();
|
|
if(Skip())
|
|
throw Exc(NFormat(t_("illegal characters after expression: %s (expression: %s)"), StringSample(pos, 10), StringSample(t, 10)));
|
|
return result;
|
|
}
|
|
|
|
void CalcParser::Clear(const char *t)
|
|
{
|
|
start = pos = t;
|
|
}
|
|
|
|
CalcNodePtr CalcParser::ScanAny()
|
|
{
|
|
return ScanSelect();
|
|
}
|
|
|
|
/*
|
|
CalcNodePtr CalcParser::ScanLambda()
|
|
{
|
|
return Check(OP_LAMBDA) ? new CalcLambdaNode(ScanAny()) : ScanSelect();
|
|
}
|
|
*/
|
|
|
|
CalcNodePtr CalcParser::ScanSelect()
|
|
{
|
|
CalcNodePtr node = ScanLogOr();
|
|
if(Check(OP_QUESTION))
|
|
{ // ?:
|
|
CalcNodePtr when1 = ScanAny();
|
|
Force(OP_COLON, ":");
|
|
CalcNodePtr when0 = ScanAny();
|
|
node = new CalcSelectNode(node, when1, when0);
|
|
}
|
|
return node;
|
|
}
|
|
|
|
CalcNodePtr CalcParser::ScanLogOr()
|
|
{
|
|
CalcNodePtr node = ScanLogAnd();
|
|
while(Check(OP_LOG_OR))
|
|
node = new CalcLogOrNode(node, ScanLogAnd());
|
|
return node;
|
|
}
|
|
|
|
CalcNodePtr CalcParser::ScanLogAnd()
|
|
{
|
|
CalcNodePtr node = ScanEq();
|
|
while(Check(OP_LOG_AND))
|
|
node = new CalcLogAndNode(node, ScanEq());
|
|
return node;
|
|
}
|
|
|
|
CalcNodePtr CalcParser::ScanEq()
|
|
{
|
|
CalcNodePtr node = ScanCompare();
|
|
for(;;)
|
|
if(Check(OP_EQ))
|
|
node = new CalcFunctionNode("==", node, ScanCompare());
|
|
else if(Check(OP_NE))
|
|
node = new CalcFunctionNode("!=", node, ScanCompare());
|
|
else
|
|
return node;
|
|
}
|
|
|
|
CalcNodePtr CalcParser::ScanCompare()
|
|
{
|
|
CalcNodePtr node = ScanBitOr();
|
|
for(;;)
|
|
if(Check(OP_LE))
|
|
node = new CalcFunctionNode("<=", node, ScanBitOr());
|
|
else if(Check(OP_GE))
|
|
node = new CalcFunctionNode(">=", node, ScanBitOr());
|
|
else if(Check(OP_LT))
|
|
node = new CalcFunctionNode("<", node, ScanBitOr());
|
|
else if(Check(OP_GT))
|
|
node = new CalcFunctionNode(">", node, ScanBitOr());
|
|
else
|
|
return node;
|
|
}
|
|
|
|
CalcNodePtr CalcParser::ScanBitOr()
|
|
{
|
|
CalcNodePtr node = ScanBitXor();
|
|
while(Check(OP_BIT_OR))
|
|
node = new CalcFunctionNode("|", node, ScanBitXor());
|
|
return node;
|
|
}
|
|
|
|
CalcNodePtr CalcParser::ScanBitXor()
|
|
{
|
|
CalcNodePtr node = ScanBitAnd();
|
|
while(Check(OP_BIT_XOR))
|
|
node = new CalcFunctionNode("^", node, ScanBitAnd());
|
|
return node;
|
|
}
|
|
|
|
CalcNodePtr CalcParser::ScanBitAnd()
|
|
{
|
|
CalcNodePtr node = ScanAdditive();
|
|
while(Check(OP_BIT_AND))
|
|
node = new CalcFunctionNode("&", node, ScanAdditive());
|
|
return node;
|
|
}
|
|
|
|
CalcNodePtr CalcParser::ScanAdditive()
|
|
{
|
|
CalcNodePtr node = ScanShift();
|
|
for(;;)
|
|
if(Check(OP_ADD))
|
|
node = new CalcFunctionNode("+", node, ScanShift());
|
|
else if(Check(OP_SUB))
|
|
node = new CalcFunctionNode("-", node, ScanShift());
|
|
else
|
|
return node;
|
|
}
|
|
|
|
CalcNodePtr CalcParser::ScanShift()
|
|
{
|
|
CalcNodePtr node = ScanMultiplicative();
|
|
for(;;)
|
|
if(Check(OP_LSHIFT))
|
|
node = new CalcFunctionNode("<<", node, ScanMultiplicative());
|
|
else if(Check(OP_RSHIFT))
|
|
node = new CalcFunctionNode(">>", node, ScanMultiplicative());
|
|
else
|
|
return node;
|
|
}
|
|
|
|
CalcNodePtr CalcParser::ScanMultiplicative()
|
|
{
|
|
CalcNodePtr node = ScanPow();
|
|
for(;;)
|
|
if(Check(OP_MUL))
|
|
node = new CalcFunctionNode("*", node, ScanPow());
|
|
else if(Check(OP_DIV))
|
|
node = new CalcFunctionNode("/", node, ScanPow());
|
|
else if(Check(OP_MOD))
|
|
node = new CalcFunctionNode("%", node, ScanPow());
|
|
else
|
|
return node;
|
|
}
|
|
|
|
CalcNodePtr CalcParser::ScanPow()
|
|
{
|
|
CalcNodePtr node = ScanPrefix();
|
|
while(Check(OP_POW))
|
|
node = new CalcFunctionNode("pow", node, ScanPow());
|
|
return node;
|
|
}
|
|
|
|
CalcNodePtr CalcParser::ScanPrefix()
|
|
{
|
|
Skip();
|
|
const char *start = pos;
|
|
switch(GetOperator())
|
|
{
|
|
case OP_LAMBDA: SkipOperator(); return new CalcLambdaNode(ScanPrefix());
|
|
case OP_ADD: SkipOperator(); return new CalcFunctionNode("+", ScanPrefix());
|
|
case OP_SUB: SkipOperator(); return new CalcFunctionNode("-", ScanPrefix());
|
|
case OP_BIT_NOT: SkipOperator(); return new CalcFunctionNode("~", ScanPrefix());
|
|
case OP_LOG_NOT: SkipOperator(); return new CalcFunctionNode("!", ScanPrefix());
|
|
case OP_LPAR:
|
|
{ // parenthesised subexpression
|
|
const char *begin = pos;
|
|
SkipOperator();
|
|
CalcNodePtr exp = ScanAny();
|
|
if(!Force(OP_RPAR, ")"))
|
|
throw Exc(NFormat(t_("left parenthesis not matched; subexpression starts at %s"), StringSample(begin, 20)));
|
|
return ScanPostfix(exp);
|
|
}
|
|
case OP_LBRACKET:
|
|
SkipOperator();
|
|
{ // array constructor
|
|
CalcNodePtr ctr;
|
|
if(Check(OP_RBRACKET))
|
|
ctr = new CalcFunctionNode("[,,]");
|
|
else
|
|
{
|
|
CalcNodePtr arg = ScanAny();
|
|
if(Check(OP_DOTS))
|
|
{
|
|
CalcNodePtr maxrng = ScanAny();
|
|
ctr = new CalcFunctionNode("[..]", arg, maxrng);
|
|
}
|
|
else
|
|
{
|
|
Vector<CalcNodePtr> arglist;
|
|
arglist.Add() = arg;
|
|
while(Check(OP_COMMA))
|
|
arglist.Add() = ScanAny();
|
|
ctr = new CalcFunctionNode("[,,]", arglist);
|
|
}
|
|
if(!Force(OP_RBRACKET, "]"))
|
|
throw Exc("chybí pravá hranatá závorka ']'");
|
|
}
|
|
return ScanPostfix(ctr);
|
|
}
|
|
}
|
|
if(*pos == '\"')
|
|
{
|
|
return ScanPostfix(new CalcConstNode(GetString()));
|
|
}
|
|
if(IsDigit(*pos)) // numeric or date constant
|
|
return ScanPostfix(new CalcConstNode(GetNumberOrDate()));
|
|
if(IsIdent(*pos))
|
|
{
|
|
String ident = GetIdent();
|
|
Vector<CalcNodePtr> arguments;
|
|
ScanArgs(arguments);
|
|
return ScanPostfix(new CalcFunctionNode(ident, arguments));
|
|
}
|
|
const char *p = pos;
|
|
if(*p == 0)
|
|
p = t_("end of line");
|
|
throw Exc(t_("constant or function expected, found: ") + StringSample(p, 15));
|
|
return 0;
|
|
}
|
|
|
|
CalcNodePtr CalcParser::ScanPostfix(CalcNodePtr node)
|
|
{
|
|
for(;;)
|
|
if(Check(OP_DOT))
|
|
{ // member access
|
|
byte c = Skip();
|
|
String id = GetIdent();
|
|
Vector<CalcNodePtr> nodes;
|
|
nodes | node;
|
|
ScanArgs(nodes);
|
|
node = new CalcFunctionNode(id, nodes);
|
|
}
|
|
else if(Check(OP_LBRACE))
|
|
{
|
|
Vector<CalcNodePtr> list;
|
|
do
|
|
{
|
|
list.Add() = ScanAny();
|
|
if(!Check(OP_COMMA)) // default value
|
|
break;
|
|
list.Add() = ScanAny();
|
|
}
|
|
while(Check(OP_COMMA));
|
|
Force(OP_RBRACE, "}");
|
|
node = new CalcSwitchNode(node, list);
|
|
}
|
|
else if(Check(OP_LBRACKET))
|
|
{
|
|
CalcNodePtr index = ScanAny();
|
|
Force(OP_RBRACKET, "]");
|
|
node = new CalcFunctionNode("[]", node, index);
|
|
}
|
|
else
|
|
return node;
|
|
}
|
|
|
|
void CalcParser::ScanArgs(Vector<CalcNodePtr>& dest)
|
|
{
|
|
if(Check(OP_LPAR) && !Check(OP_RPAR))
|
|
{
|
|
const char *begin = pos;
|
|
do
|
|
dest | ScanAny();
|
|
while(Check(OP_COMMA));
|
|
if(!Check(OP_RPAR))
|
|
Expect(NFormat("')' (zaèátek podvýrazu: %s)", StringSample(begin - 1, 20)));
|
|
}
|
|
}
|
|
|
|
int CalcParser::GetOperator()
|
|
{
|
|
if(op_begin == pos)
|
|
return op_last;
|
|
Skip();
|
|
op_begin = op_end = pos;
|
|
switch(*op_end++)
|
|
{
|
|
case '?': op_last = OP_QUESTION; break;
|
|
case ':': op_last = OP_COLON; break;
|
|
case '&': op_last = (*op_end == '&' ? op_end++, OP_LOG_AND : OP_BIT_AND); break;
|
|
case '|': op_last = (*op_end == '|' ? op_end++, OP_LOG_OR : OP_BIT_OR); break;
|
|
case '^': op_last = OP_BIT_XOR; break;
|
|
case '<': op_last = (*op_end == '=' ? op_end++, OP_LE : *op_end == '<' ? op_end++, OP_LSHIFT : OP_LT); break;
|
|
case '>': op_last = (*op_end == '=' ? op_end++, OP_GE : *op_end == '>' ? op_end++, OP_RSHIFT : OP_GT); break;
|
|
case '!': op_last = (*op_end == '=' ? op_end++, OP_NE : OP_LOG_NOT); break;
|
|
case '=': op_last = (*op_end == '=' ? op_end++, OP_EQ : (op_end--, OP_NONE)); break;
|
|
case '+': op_last = OP_ADD; break;
|
|
case '-': op_last = OP_SUB; break;
|
|
case '*': op_last = (*op_end == '*' ? op_end++, OP_POW : OP_MUL); break;
|
|
case '/': op_last = OP_DIV; break;
|
|
case '%': op_last = OP_MOD; break;
|
|
case '~': op_last = OP_BIT_NOT; break;
|
|
case '#': op_last = OP_LAMBDA; break;
|
|
case '(': op_last = OP_LPAR; break;
|
|
case ')': op_last = OP_RPAR; break;
|
|
case '[': op_last = OP_LBRACKET; break;
|
|
case ']': op_last = OP_RBRACKET; break;
|
|
case '{': op_last = OP_LBRACE; break;
|
|
case '}': op_last = OP_RBRACE; break;
|
|
case ',': op_last = OP_COMMA; break;
|
|
case '.': op_last = (*op_end == '.' ? op_end++, OP_DOTS : OP_DOT); break;
|
|
default: op_end--; op_last = OP_NONE; break;
|
|
}
|
|
return op_last;
|
|
}
|
|
|
|
int CalcParser::SkipOperator()
|
|
{
|
|
int op = GetOperator();
|
|
pos = op_end;
|
|
return op;
|
|
}
|
|
|
|
bool CalcParser::Check(int op)
|
|
{
|
|
if(op != GetOperator())
|
|
return false;
|
|
SkipOperator();
|
|
return true;
|
|
}
|
|
|
|
bool CalcParser::Force(int op, const char *expect)
|
|
{
|
|
if(Check(op))
|
|
return true;
|
|
Expect(expect);
|
|
return false;
|
|
}
|
|
|
|
END_UPP_NAMESPACE
|