1020 lines
36 KiB
MQL5
1020 lines
36 KiB
MQL5
|
//+------------------------------------------------------------------+
|
||
|
//| ruleparser.mqh |
|
||
|
//| Copyright 2000-2025, MetaQuotes Ltd. |
|
||
|
//| https://www.mql5.com |
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Implementation of Fuzzy library in MetaQuotes Language 5 |
|
||
|
//| |
|
||
|
//| The features of the library include: |
|
||
|
//| - Create Mamdani fuzzy model |
|
||
|
//| - Create Sugeno fuzzy model |
|
||
|
//| - Normal membership function |
|
||
|
//| - Triangular membership function |
|
||
|
//| - Trapezoidal membership function |
|
||
|
//| - Constant membership function |
|
||
|
//| - Defuzzification method of center of gravity (COG) |
|
||
|
//| - Defuzzification method of bisector of area (BOA) |
|
||
|
//| - Defuzzification method of mean of maxima (MeOM) |
|
||
|
//| |
|
||
|
//| This file is free software; you can redistribute it and/or |
|
||
|
//| modify it under the terms of the GNU General Public License as |
|
||
|
//| published by the Free Software Foundation (www.fsf.org); either |
|
||
|
//| version 2 of the License, or (at your option) any later version. |
|
||
|
//| |
|
||
|
//| This program is distributed in the hope that it will be useful, |
|
||
|
//| but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||
|
//| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||
|
//| GNU General Public License for more details. |
|
||
|
//+------------------------------------------------------------------+
|
||
|
#include <Arrays\List.mqh>
|
||
|
#include <Arrays\ArrayObj.mqh>
|
||
|
#include "Dictionary.mqh"
|
||
|
#include "FuzzyRule.mqh"
|
||
|
#include "Helper.mqh"
|
||
|
#include "InferenceMethod.mqh"
|
||
|
#include "SugenoVariable.mqh"
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Purpose: Analysis of the fuzzy rules |
|
||
|
//+------------------------------------------------------------------+
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Base class for all expression |
|
||
|
//+------------------------------------------------------------------+
|
||
|
class IExpression : public CObject
|
||
|
{
|
||
|
public:
|
||
|
//--- method gets the text of expression
|
||
|
virtual string Text(void)=NULL;
|
||
|
//--- method to check type
|
||
|
virtual bool IsTypeOf(EnLexem type) { return(type==TYPE_CLASS_IExpression); }
|
||
|
};
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Class for creating lexem |
|
||
|
//+------------------------------------------------------------------+
|
||
|
class CLexem : public IExpression
|
||
|
{
|
||
|
public:
|
||
|
//--- method gets the text of expression
|
||
|
virtual string Text(void)=NULL;
|
||
|
//--- method to check type
|
||
|
virtual bool IsTypeOf(EnLexem type) { return(type==TYPE_CLASS_Lexem); }
|
||
|
};
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Class condition expression |
|
||
|
//+------------------------------------------------------------------+
|
||
|
class CConditionExpression : public IExpression
|
||
|
{
|
||
|
private:
|
||
|
CArrayObj *m_expressions; // List of expression
|
||
|
CFuzzyCondition *m_condition; // Fuzzy condition
|
||
|
|
||
|
public:
|
||
|
CConditionExpression(CArrayObj *expressions,CFuzzyCondition *condition);
|
||
|
~CConditionExpression(void);
|
||
|
//--- methods gets or sets array of expression
|
||
|
CArrayObj *Expressions(void) { return (m_expressions); }
|
||
|
void Expressions(CArrayObj *value) { m_expressions=value; }
|
||
|
//--- methods gets or sets fuzzy condition
|
||
|
CFuzzyCondition *Condition(void) { return m_condition; }
|
||
|
void Condition(CFuzzyCondition *value) { m_condition=value; }
|
||
|
//--- method gets the text of expressions
|
||
|
string Text(void);
|
||
|
//--- method to check type
|
||
|
virtual bool IsTypeOf(EnLexem type) { return(type==TYPE_CLASS_ConditionExpression); }
|
||
|
};
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Constructor with parameters |
|
||
|
//+------------------------------------------------------------------+
|
||
|
CConditionExpression::CConditionExpression(CArrayObj *expressions,CFuzzyCondition *condition)
|
||
|
{
|
||
|
m_expressions=expressions;
|
||
|
m_condition=condition;
|
||
|
}
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Destructor |
|
||
|
//+------------------------------------------------------------------+
|
||
|
CConditionExpression::~CConditionExpression(void)
|
||
|
{
|
||
|
|
||
|
}
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Convert all expressions to text(string) |
|
||
|
//+------------------------------------------------------------------+
|
||
|
string CConditionExpression::Text(void)
|
||
|
{
|
||
|
string sb;
|
||
|
for(int i=0; i<m_expressions.Total(); i++)
|
||
|
{
|
||
|
IExpression *ex=m_expressions.At(i);
|
||
|
string str=ex.Text();
|
||
|
StringAdd(sb,str);
|
||
|
}
|
||
|
//--- return text of all expressions
|
||
|
return ((string)sb);
|
||
|
}
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Class keyword lexem |
|
||
|
//+------------------------------------------------------------------+
|
||
|
class CKeywordLexem : public CLexem
|
||
|
{
|
||
|
private:
|
||
|
string m_name; // Name of keyword lexem
|
||
|
|
||
|
public:
|
||
|
CKeywordLexem(const string name);
|
||
|
~CKeywordLexem(void);
|
||
|
//--- method gets the text of lexem
|
||
|
string Text(void) { return(m_name); }
|
||
|
//--- method to check type
|
||
|
virtual bool IsTypeOf(EnLexem type) { return(type==TYPE_CLASS_KeywordLexem); }
|
||
|
};
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Constructor with parameters |
|
||
|
//+------------------------------------------------------------------+
|
||
|
CKeywordLexem::CKeywordLexem(const string name)
|
||
|
{
|
||
|
m_name=name;
|
||
|
}
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Destructor |
|
||
|
//+------------------------------------------------------------------+
|
||
|
CKeywordLexem::~CKeywordLexem(void)
|
||
|
{
|
||
|
|
||
|
}
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Class for handling variable lexem |
|
||
|
//+------------------------------------------------------------------+
|
||
|
class CVarLexem : public CLexem
|
||
|
{
|
||
|
private:
|
||
|
INamedVariable *m_var; // Variable type
|
||
|
bool m_input; // Confirmation of the variable
|
||
|
|
||
|
public:
|
||
|
CVarLexem(INamedVariable *var,bool in);
|
||
|
~CVarLexem(void);
|
||
|
//--- methods gets or sets the varriable
|
||
|
INamedVariable *Var(void) { return (m_var); }
|
||
|
void Var(INamedVariable *var) { m_var=var; }
|
||
|
//--- method gets the text of lexem
|
||
|
string Text(void) { return(m_var.Name()); }
|
||
|
//--- methods gets or sets mark is input varriable
|
||
|
bool Input(void) { return (m_input); }
|
||
|
void Input(bool value) { m_input=value; }
|
||
|
//--- method to check type
|
||
|
virtual bool IsTypeOf(EnLexem type) { return(type==TYPE_CLASS_VarLexem); }
|
||
|
};
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Constructor with parameters |
|
||
|
//+------------------------------------------------------------------+
|
||
|
CVarLexem::CVarLexem(INamedVariable *var,bool in)
|
||
|
{
|
||
|
m_var=var;
|
||
|
m_input=in;
|
||
|
}
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Destructor |
|
||
|
//+------------------------------------------------------------------+
|
||
|
CVarLexem::~CVarLexem(void)
|
||
|
{
|
||
|
|
||
|
}
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Class alternative lexem |
|
||
|
//+------------------------------------------------------------------+
|
||
|
class IAltLexem : public CLexem
|
||
|
{
|
||
|
public:
|
||
|
//--- methods gets or sets alternative lexem
|
||
|
virtual IAltLexem *Alternative(void)=NULL;
|
||
|
virtual void Alternative(IAltLexem *value)=NULL;
|
||
|
//--- method to check type
|
||
|
virtual bool IsTypeOf(EnLexem type) { return(type==TYPE_CLASS_AltLexem); }
|
||
|
};
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Class term lexem |
|
||
|
//+------------------------------------------------------------------+
|
||
|
class CTermLexem : public IAltLexem
|
||
|
{
|
||
|
private:
|
||
|
INamedValue *m_term; // Value type
|
||
|
IAltLexem *m_alternative; // Alternative lexem
|
||
|
bool m_input;
|
||
|
|
||
|
public:
|
||
|
CTermLexem(INamedValue *term,bool in);
|
||
|
~CTermLexem(void);
|
||
|
//--- methods gets or sets the term
|
||
|
INamedValue *Term(void) { return(m_term); }
|
||
|
void Term(INamedValue *value) { m_term=value; }
|
||
|
//--- method gets the text of lexem
|
||
|
string Text(void) { return m_term.Name(); }
|
||
|
//--- methods gets or sets the alternative lexem
|
||
|
IAltLexem *Alternative(void) { return(m_alternative); }
|
||
|
void Alternative(IAltLexem *value) { m_alternative=value; }
|
||
|
//--- method to check type
|
||
|
virtual bool IsTypeOf(EnLexem type) { return(type==TYPE_CLASS_TermLexem); }
|
||
|
};
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Constructor with parameters |
|
||
|
//+------------------------------------------------------------------+
|
||
|
CTermLexem::CTermLexem(INamedValue *term,bool in)
|
||
|
{
|
||
|
m_term=term;
|
||
|
m_input=in;
|
||
|
}
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Destructor |
|
||
|
//+------------------------------------------------------------------+
|
||
|
CTermLexem::~CTermLexem(void)
|
||
|
{
|
||
|
if(CheckPointer(m_alternative)==POINTER_DYNAMIC)
|
||
|
delete m_alternative;
|
||
|
}
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Class responsible for parsing |
|
||
|
//+------------------------------------------------------------------+
|
||
|
class CRuleParser : public INamedVariable
|
||
|
{
|
||
|
public:
|
||
|
//--- methods of parsing
|
||
|
static IParsableRule *Parse(const string rule,IParsableRule *emptyRule,CList *in,CList *out);
|
||
|
private:
|
||
|
static CList *BuildLexemsList(CList *in,CList *out);
|
||
|
static void BuildLexemsList(INamedVariable *var,bool in,CList *lexems);
|
||
|
static CArrayObj *ParseLexems(const string rule,CList *lexems);
|
||
|
static CArrayObj *ExtractSingleCondidtions(CArrayObj *conditionExpression,CList *in,CList *lexems);
|
||
|
static CConditions *ParseConditions(CArrayObj *conditionExpression,CList *in,CList *lexems);
|
||
|
static int FindPairBracket(CArrayObj *expressions,CList *lexems);
|
||
|
static ICondition *ParseConditionsRecurse(CArrayObj *expressions,CList *lexems);
|
||
|
static CSingleCondition *ParseConclusion(CArrayObj *conditionExpression,CList *out,CList *lexems);
|
||
|
};
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Parse |
|
||
|
//+------------------------------------------------------------------+
|
||
|
static IParsableRule *CRuleParser::Parse(const string rule,IParsableRule *emptyRule,CList *in,CList *out)
|
||
|
{
|
||
|
if(StringLen(rule)==0)
|
||
|
{
|
||
|
Print("Rule cannot be empty.");
|
||
|
//--- return
|
||
|
return (NULL);
|
||
|
}
|
||
|
//--- Surround brakes with spaces, remove double spaces
|
||
|
string sb=NULL;
|
||
|
char ch;
|
||
|
for(int i=0; i<StringLen(rule); i++)
|
||
|
{
|
||
|
ch=(char)StringGetCharacter(rule,i);
|
||
|
if((ch=='(') || (ch==')'))
|
||
|
{
|
||
|
if(StringLen(sb)>0 && StringGetCharacter(sb,StringLen(sb)-1)==' ')
|
||
|
{
|
||
|
//--- Do not duplicate spaces
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
sb+=CharToString(' ');
|
||
|
}
|
||
|
sb+=CharToString(ch);
|
||
|
sb+=CharToString(' ');
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if(ch==' ' && StringLen(sb)>0 && StringGetCharacter(sb,StringLen(sb)-1)==' ')
|
||
|
{
|
||
|
// Do not duplicate spaces
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
sb+=CharToString(ch);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
//--- Remove spaces
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Use conditional compilation to determine |
|
||
|
//| the type of program MQL4 or MQL5 because they have |
|
||
|
//| different realization of StringTrimRight() and StringTrimLeft() |
|
||
|
//+------------------------------------------------------------------+
|
||
|
#ifdef __MQL5__
|
||
|
StringTrimRight(sb);
|
||
|
StringTrimLeft(sb);
|
||
|
#else
|
||
|
#ifdef __MQL4__
|
||
|
sb=StringTrimRight(sb);
|
||
|
sb=StringTrimLeft(sb);
|
||
|
#endif
|
||
|
#endif
|
||
|
string prepRule=sb;
|
||
|
//--- Build lexems dictionary
|
||
|
CList *lexemsDict=BuildLexemsList(in,out);
|
||
|
//--- At first we parse lexems
|
||
|
CArrayObj *expressions=ParseLexems(prepRule,lexemsDict);
|
||
|
if(expressions.Total()==0)
|
||
|
{
|
||
|
Print("No valid identifiers found.");
|
||
|
//--- return
|
||
|
return (NULL);
|
||
|
}
|
||
|
//--- Find condition & conclusion parts part
|
||
|
CDictionary_String_Obj *p_so;
|
||
|
for(int i=0; i<lexemsDict.Total(); i++)
|
||
|
{
|
||
|
p_so=lexemsDict.GetNodeAtIndex(i);
|
||
|
if(p_so.Key()=="if")
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if(expressions.At(0)!=p_so.Value())
|
||
|
{
|
||
|
Print("'if' should be the first identifier.");
|
||
|
//--- return
|
||
|
return (NULL);
|
||
|
}
|
||
|
int thenIndex=-1;
|
||
|
for(int i=1; i<expressions.Total(); i++)
|
||
|
{
|
||
|
for(int j=0; j<lexemsDict.Total(); j++)
|
||
|
{
|
||
|
p_so=lexemsDict.GetNodeAtIndex(j);
|
||
|
if(p_so.Key()=="then"){break;}
|
||
|
}
|
||
|
if(expressions.At(i)==p_so.Value())
|
||
|
{
|
||
|
thenIndex=i;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if(thenIndex==-1)
|
||
|
{
|
||
|
Print("'then' identifier not found.");
|
||
|
//--- return
|
||
|
return (NULL);
|
||
|
}
|
||
|
int conditionLen=thenIndex-1;
|
||
|
if(conditionLen<1)
|
||
|
{
|
||
|
Print("Condition part of the rule not found.");
|
||
|
//--- return
|
||
|
return (NULL);
|
||
|
}
|
||
|
int conclusionLen=expressions.Total()-thenIndex-1;
|
||
|
if(conclusionLen<1)
|
||
|
{
|
||
|
Print("Conclusion part of the rule not found.");
|
||
|
//--- return
|
||
|
return (NULL);
|
||
|
}
|
||
|
CArrayObj *conditionExpressions=GetRange(expressions,1,conditionLen);
|
||
|
CArrayObj *conclusionExpressions=GetRange(expressions,thenIndex+1,conclusionLen);
|
||
|
CConditions *conditions=ParseConditions(conditionExpressions,in,lexemsDict);
|
||
|
delete emptyRule.Condition();
|
||
|
emptyRule.Condition(conditions);
|
||
|
CSingleCondition *conclusion=ParseConclusion(conclusionExpressions,out,lexemsDict);
|
||
|
if(emptyRule.IsTypeOf(TYPE_CLASS_MamdaniFuzzyRule))
|
||
|
{
|
||
|
CMamdaniFuzzyRule *mamdani_rule=emptyRule;
|
||
|
mamdani_rule.Conclusion(conclusion);
|
||
|
emptyRule=mamdani_rule;
|
||
|
}
|
||
|
if(emptyRule.IsTypeOf(TYPE_CLASS_SugenoFuzzyRule))
|
||
|
{
|
||
|
CSugenoFuzzyRule *sugeno_rule=emptyRule;
|
||
|
sugeno_rule.Conclusion(conclusion);
|
||
|
emptyRule=sugeno_rule;
|
||
|
}
|
||
|
//--- return empty rule
|
||
|
conditionExpressions.FreeMode(false);
|
||
|
delete conditionExpressions;
|
||
|
conclusionExpressions.FreeMode(false);
|
||
|
delete conclusionExpressions;
|
||
|
expressions.FreeMode(false);
|
||
|
delete expressions;
|
||
|
delete lexemsDict;
|
||
|
return (emptyRule);
|
||
|
}
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Build lexems list |
|
||
|
//+------------------------------------------------------------------+
|
||
|
static CList *CRuleParser::BuildLexemsList(CList *in,CList *out)
|
||
|
{
|
||
|
CList *lexems=new CList();
|
||
|
for(int i=0;i<ArraySize(KEYWORDS); i++)
|
||
|
{
|
||
|
string keyword=KEYWORDS[i];
|
||
|
CKeywordLexem *keywordLexem=new CKeywordLexem(keyword);
|
||
|
CDictionary_String_Obj *p_so=new CDictionary_String_Obj;
|
||
|
p_so.SetAll(keywordLexem.Text(),keywordLexem);
|
||
|
lexems.Add(p_so);
|
||
|
}
|
||
|
for(int i=0; i<in.Total(); i++)
|
||
|
{
|
||
|
INamedVariable *var=in.GetNodeAtIndex(i);
|
||
|
BuildLexemsList(var,true,lexems);
|
||
|
}
|
||
|
for(int i=0; i<out.Total(); i++)
|
||
|
{
|
||
|
INamedVariable *var=out.GetNodeAtIndex(i);
|
||
|
BuildLexemsList(var,false,lexems);
|
||
|
}
|
||
|
//--- return lexems
|
||
|
return (lexems);
|
||
|
}
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Build lexems list |
|
||
|
//+------------------------------------------------------------------+
|
||
|
static void CRuleParser::BuildLexemsList(INamedVariable *var,bool in,CList *lexems)
|
||
|
{
|
||
|
CVarLexem *varLexem=new CVarLexem(var,in);
|
||
|
CDictionary_String_Obj *p_so_var=new CDictionary_String_Obj;
|
||
|
p_so_var.SetAll(varLexem.Text(),varLexem);
|
||
|
lexems.Add(p_so_var);
|
||
|
for(int i=0; i<var.Values().Total(); i++)
|
||
|
{
|
||
|
INamedValue *term=var.Values().GetNodeAtIndex(i);
|
||
|
CTermLexem *termLexem=new CTermLexem(term,in);
|
||
|
CLexem *foundLexem;
|
||
|
bool contain=false;
|
||
|
for(int j=0; j<lexems.Total(); j++)
|
||
|
{
|
||
|
CDictionary_String_Obj *p_so=lexems.GetNodeAtIndex(j);
|
||
|
foundLexem=p_so.Value();
|
||
|
if(p_so.Key()==termLexem.Text())
|
||
|
{
|
||
|
contain=true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if(contain==false)
|
||
|
{
|
||
|
CDictionary_String_Obj *p_so_val=new CDictionary_String_Obj;
|
||
|
p_so_val.SetAll(termLexem.Text(),termLexem);
|
||
|
lexems.Add(p_so_val);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if(foundLexem.IsTypeOf(TYPE_CLASS_TermLexem))
|
||
|
{
|
||
|
//--- There can be more than one terms with the same name.
|
||
|
//--- TODO: But only if they belong to defferent variables.
|
||
|
CTermLexem *foundTermLexem=foundLexem;
|
||
|
while(foundTermLexem.Alternative()!=NULL)
|
||
|
{
|
||
|
foundTermLexem=foundTermLexem.Alternative();
|
||
|
}
|
||
|
foundTermLexem.Alternative(termLexem);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//--- Only terms of different vatiables can have the same name
|
||
|
Print("Found more than one lexems with the same name");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Parse lexems |
|
||
|
//+------------------------------------------------------------------+
|
||
|
static CArrayObj *CRuleParser::ParseLexems(const string rule,CList *lexems)
|
||
|
{
|
||
|
CArrayObj *expressions=new CArrayObj;
|
||
|
string words[];
|
||
|
StringSplit(rule,' ',words);
|
||
|
int index=0;
|
||
|
for(int i=0; i<ArraySize(words); i++)
|
||
|
{
|
||
|
string word=words[i];
|
||
|
CObject *lexem=NULL;
|
||
|
if(TryGetValue(lexems,word,lexem))
|
||
|
{
|
||
|
expressions.Add(lexem);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Print(StringFormat("Unknown identifier : %s",word));
|
||
|
//--- return NULL
|
||
|
return(NULL);
|
||
|
}
|
||
|
}
|
||
|
//--- return expressions
|
||
|
return(expressions);
|
||
|
}
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Extract single condidtions |
|
||
|
//+------------------------------------------------------------------+
|
||
|
static CArrayObj *CRuleParser::ExtractSingleCondidtions(CArrayObj *conditionExpression,CList *in,CList *lexems)
|
||
|
{
|
||
|
CArrayObj *copyExpressions=conditionExpression;
|
||
|
CArrayObj *expressions=new CArrayObj;
|
||
|
int index=0;
|
||
|
while(copyExpressions.Total()-index>0)
|
||
|
{
|
||
|
IExpression *expr0=copyExpressions.At(index);
|
||
|
if(expr0.IsTypeOf(TYPE_CLASS_VarLexem))
|
||
|
{
|
||
|
//--- Parse variable
|
||
|
CVarLexem *varLexem=copyExpressions.At(index);
|
||
|
if(copyExpressions.Total()<3)
|
||
|
{
|
||
|
Print(StringFormat("Condition strated with '%s' is incorrect.",varLexem.Text()));
|
||
|
//--- return
|
||
|
return(NULL);
|
||
|
}
|
||
|
if(varLexem.Input()==false)
|
||
|
{
|
||
|
Print("The variable in condition part must be an input variable.");
|
||
|
//--- return
|
||
|
return(NULL);
|
||
|
}
|
||
|
//--- Parse 'is' lexem
|
||
|
CLexem *exprIs=copyExpressions.At(index+1);
|
||
|
CDictionary_String_Obj *p_so;
|
||
|
for(int i=0;i<lexems.Total();i++)
|
||
|
{
|
||
|
p_so=lexems.GetNodeAtIndex(i);
|
||
|
if(p_so.Key()=="is")
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if(exprIs!=p_so.Value())
|
||
|
{
|
||
|
Print(StringFormat("'is' keyword must go after '%s' identifier.",varLexem.Text()));
|
||
|
//--- return
|
||
|
return(NULL);
|
||
|
}
|
||
|
//--- Parse 'not' lexem (if exists)
|
||
|
int cur=2;
|
||
|
bool not=false;
|
||
|
for(int i=0;i<lexems.Total();i++)
|
||
|
{
|
||
|
p_so=lexems.GetNodeAtIndex(i);
|
||
|
if(p_so.Key()=="not")
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if(copyExpressions.At(cur+index)==p_so.Value())
|
||
|
{
|
||
|
not=true;
|
||
|
cur++;
|
||
|
if(copyExpressions.Total()-index<=cur)
|
||
|
{
|
||
|
Print("Error at 'not' in condition part of the rule.");
|
||
|
//--- return
|
||
|
return(NULL);
|
||
|
}
|
||
|
}
|
||
|
//--- "slightly"
|
||
|
//--- "somewhat"
|
||
|
//--- "very"
|
||
|
//--- "extremely"
|
||
|
//--- Parse hedge modifier (if exists)
|
||
|
HedgeType hedge=None;
|
||
|
for(int i=0;i<lexems.Total();i++)
|
||
|
{
|
||
|
p_so=lexems.GetNodeAtIndex(i);
|
||
|
if(p_so.Key()=="slightly")
|
||
|
{
|
||
|
if(copyExpressions.At(cur+index)==p_so.Value())
|
||
|
{
|
||
|
hedge=Slightly;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
else if(p_so.Key()=="somewhat")
|
||
|
{
|
||
|
if(copyExpressions.At(cur+index)==p_so.Value())
|
||
|
{
|
||
|
hedge=Somewhat;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
else if(p_so.Key()=="very")
|
||
|
{
|
||
|
if(copyExpressions.At(cur+index)==p_so.Value())
|
||
|
{
|
||
|
hedge=Very;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
else if(p_so.Key()=="extremely")
|
||
|
{
|
||
|
if(copyExpressions.At(cur+index)==p_so.Value())
|
||
|
{
|
||
|
hedge=Extremely;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if(hedge!=None)
|
||
|
{
|
||
|
cur++;
|
||
|
if(copyExpressions.Total()-index<=cur)
|
||
|
{
|
||
|
Print("Error in condition part of the rule.");
|
||
|
//--- return
|
||
|
return(NULL);
|
||
|
}
|
||
|
}
|
||
|
//--- Parse term
|
||
|
CLexem *exprTerm=copyExpressions.At(cur+index);
|
||
|
if(!exprTerm.IsTypeOf(TYPE_CLASS_TermLexem))
|
||
|
{
|
||
|
Print(StringFormat("Wrong identifier '%s' in conditional part of the rule.",exprTerm.Text()));
|
||
|
//--- return
|
||
|
return(NULL);
|
||
|
}
|
||
|
IAltLexem *altLexem=exprTerm;
|
||
|
CTermLexem *termLexem=NULL;
|
||
|
do
|
||
|
{
|
||
|
if(!altLexem.IsTypeOf(TYPE_CLASS_TermLexem))
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
termLexem=altLexem;
|
||
|
if(varLexem.Var().Values().IndexOf(termLexem.Term())==-1)
|
||
|
{
|
||
|
termLexem=NULL;
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
while((altLexem=altLexem.Alternative())!=NULL && termLexem==NULL);
|
||
|
if(termLexem==NULL)
|
||
|
{
|
||
|
Print(StringFormat("Wrong identifier '%s' in conditional part of the rule.",exprTerm.Text()));
|
||
|
//--- return
|
||
|
return(NULL);
|
||
|
}
|
||
|
//--- Add new condition expression
|
||
|
CFuzzyCondition *condition=new CFuzzyCondition(varLexem.Var(),termLexem.Term(),not,hedge);
|
||
|
CArrayObj *cutExpression=GetRange(copyExpressions,index,cur+1);
|
||
|
expressions.Add(new CConditionExpression(cutExpression,condition));
|
||
|
cutExpression.FreeMode(false);
|
||
|
delete cutExpression;
|
||
|
index=index+cur+1;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
CDictionary_String_Obj *p_so;
|
||
|
IExpression *expr=copyExpressions.At(index);
|
||
|
bool contain=false;
|
||
|
for(int i=0; i<lexems.Total(); i++)
|
||
|
{
|
||
|
p_so=lexems.GetNodeAtIndex(i);
|
||
|
if(p_so.Key()=="and" || p_so.Key()=="or" || p_so.Key()=="(" || p_so.Key()==")")
|
||
|
{
|
||
|
if(expr==p_so.Value())
|
||
|
{
|
||
|
contain=true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if(contain==true)
|
||
|
{
|
||
|
expressions.Add(copyExpressions.At(index));
|
||
|
index=index+1;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
CLexem *unknownLexem=expr;
|
||
|
Print(StringFormat("CLexem '%s' found at the wrong place in condition part of the rule.",unknownLexem.Text()));
|
||
|
//--- return
|
||
|
return (NULL);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
//--- return expressions
|
||
|
return (expressions);
|
||
|
}
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Parse conditions |
|
||
|
//+------------------------------------------------------------------+
|
||
|
static CConditions *CRuleParser::ParseConditions(CArrayObj *conditionExpression,CList *in,CList *lexems)
|
||
|
{
|
||
|
//--- Extract single conditions
|
||
|
CArrayObj *expressions=ExtractSingleCondidtions(conditionExpression,in,lexems);
|
||
|
if(expressions.Total()==0)
|
||
|
{
|
||
|
Print("No valid conditions found in conditions part of the rule.");
|
||
|
//--- return
|
||
|
return (NULL);
|
||
|
}
|
||
|
ICondition *cond=ParseConditionsRecurse(expressions,lexems);
|
||
|
delete expressions;
|
||
|
if(cond.IsTypeOf(TYPE_CLASS_Conditions))
|
||
|
{
|
||
|
//--- return conditions
|
||
|
return (cond);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
delete cond;
|
||
|
delete expressions;
|
||
|
CConditions *conditions=new CConditions();
|
||
|
//--- return conditions
|
||
|
return (conditions);
|
||
|
}
|
||
|
}
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Find pair bracket |
|
||
|
//+------------------------------------------------------------------+
|
||
|
static int CRuleParser::FindPairBracket(CArrayObj *expressions,CList *lexems)
|
||
|
{
|
||
|
//--- Assume that '(' stands at first place
|
||
|
int bracketsOpened=1;
|
||
|
int closeBracket=-1;
|
||
|
CDictionary_String_Obj *p_so_open;
|
||
|
CDictionary_String_Obj *p_so_close;
|
||
|
for(int i=0; i<lexems.Total(); i++)
|
||
|
{
|
||
|
CDictionary_String_Obj *p_so=lexems.GetNodeAtIndex(i);
|
||
|
if(p_so.Key()=="(")
|
||
|
{
|
||
|
p_so_open=p_so;
|
||
|
}
|
||
|
if(p_so.Key()==")")
|
||
|
{
|
||
|
p_so_close=p_so;
|
||
|
}
|
||
|
}
|
||
|
for(int i=1; i<expressions.Total(); i++)
|
||
|
{
|
||
|
if(expressions.At(i)==p_so_open.Value())
|
||
|
{
|
||
|
bracketsOpened++;
|
||
|
}
|
||
|
else if(expressions.At(i)==p_so_close.Value())
|
||
|
{
|
||
|
bracketsOpened--;
|
||
|
if(bracketsOpened==0)
|
||
|
{
|
||
|
closeBracket=i;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
//--- return index of bracket
|
||
|
return (closeBracket);
|
||
|
}
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Parse conditions recurse |
|
||
|
//+------------------------------------------------------------------+
|
||
|
static ICondition *CRuleParser::ParseConditionsRecurse(CArrayObj *expressions,CList *lexems)
|
||
|
{
|
||
|
if(expressions.Total()<1)
|
||
|
{
|
||
|
Print("Empty condition found.");
|
||
|
//--- return
|
||
|
return(NULL);
|
||
|
}
|
||
|
CDictionary_String_Obj *p_so;
|
||
|
for(int i=0; i<lexems.Total();i++)
|
||
|
{
|
||
|
p_so=lexems.GetNodeAtIndex(i);
|
||
|
if(p_so.Key()=="(")
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
IExpression *expr=expressions.At(0);
|
||
|
if(expressions.At(0)==p_so.Value() && FindPairBracket(expressions,lexems)==expressions.Total())
|
||
|
{
|
||
|
//--- Remove extra brackets
|
||
|
//-- return conditions
|
||
|
CArrayObj *cutExpression=GetRange(expressions,1,expressions.Total()-2);
|
||
|
ICondition *cond=ParseConditionsRecurse(cutExpression,lexems);
|
||
|
cutExpression.FreeMode(false);
|
||
|
delete cutExpression;
|
||
|
return cond;
|
||
|
}
|
||
|
else if(expressions.Total()==1 && expr.IsTypeOf(TYPE_CLASS_ConditionExpression))
|
||
|
{
|
||
|
//-- return single conditions
|
||
|
CConditionExpression *condExp=expressions.At(0);
|
||
|
return condExp.Condition();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//--- Parse list of one level conditions connected by or/and
|
||
|
CArrayObj *copyExpressions=expressions;
|
||
|
CConditionExpression *condExp;
|
||
|
CConditions *conds=new CConditions();
|
||
|
int index=0;
|
||
|
bool setOrAnd=false;
|
||
|
while(copyExpressions.Total()-index>0)
|
||
|
{
|
||
|
ICondition *cond=NULL;
|
||
|
for(int i=0; i<lexems.Total();i++)
|
||
|
{
|
||
|
p_so=lexems.GetNodeAtIndex(i);
|
||
|
if(p_so.Key()=="(")
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if(copyExpressions.At(0)==p_so.Value())
|
||
|
{
|
||
|
//--- Find pair bracket
|
||
|
int closeBracket=FindPairBracket(copyExpressions,lexems);
|
||
|
if(closeBracket==-1)
|
||
|
{
|
||
|
Print("Parenthesis error.");
|
||
|
//--- return
|
||
|
return (NULL);
|
||
|
}
|
||
|
CArrayObj *cutExpression=GetRange(copyExpressions,index+1,closeBracket-1);
|
||
|
cond=ParseConditionsRecurse(cutExpression,lexems);
|
||
|
cutExpression.FreeMode(false);
|
||
|
delete cutExpression;
|
||
|
index=index+closeBracket+1;
|
||
|
}
|
||
|
else if(expr.IsTypeOf(TYPE_CLASS_ConditionExpression))
|
||
|
{
|
||
|
condExp=copyExpressions.At(index);
|
||
|
cond=condExp.Condition();
|
||
|
index=index+1;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
condExp=copyExpressions.At(index);
|
||
|
Print(StringFormat("Wrong expression in condition part at '%s'",condExp.Text()));
|
||
|
//--- return
|
||
|
return (NULL);
|
||
|
}
|
||
|
//--- And condition to the list
|
||
|
conds.ConditionsList().Add(cond);
|
||
|
p_so=NULL;
|
||
|
CDictionary_String_Obj *p_so_and;
|
||
|
CDictionary_String_Obj *p_so_or;
|
||
|
for(int i=0; i<lexems.Total(); i++)
|
||
|
{
|
||
|
p_so=lexems.GetNodeAtIndex(i);
|
||
|
if(p_so.Key()=="and")
|
||
|
{
|
||
|
p_so_and=p_so;
|
||
|
}
|
||
|
if(p_so.Key()=="or")
|
||
|
{
|
||
|
p_so_or=p_so;
|
||
|
}
|
||
|
}
|
||
|
if(copyExpressions.Total()-index>0)
|
||
|
{
|
||
|
if((copyExpressions.At(index)==p_so_and.Value() && p_so_and.Key()=="and") || (copyExpressions.At(index)==p_so_or.Value() && p_so_or.Key()=="or"))
|
||
|
{
|
||
|
if(copyExpressions.Total()-index<2)
|
||
|
{
|
||
|
condExp=copyExpressions.At(index);
|
||
|
Print(StringFormat("Error at %s in condition part.",condExp.Text()));
|
||
|
//--- return
|
||
|
return (NULL);
|
||
|
}
|
||
|
//--- Set and/or for conditions list
|
||
|
OperatorType newOp=NULL;
|
||
|
if(copyExpressions.At(index)==p_so_and.Value() && p_so_and.Key()=="and")
|
||
|
{
|
||
|
newOp=And;
|
||
|
}
|
||
|
if(copyExpressions.At(index)==p_so_or.Value() && p_so_or.Key()=="or")
|
||
|
{
|
||
|
newOp=Or;
|
||
|
}
|
||
|
if(setOrAnd)
|
||
|
{
|
||
|
if(conds.Op()!=newOp)
|
||
|
{
|
||
|
Print("At the one nesting level cannot be mixed and/or operations.");
|
||
|
//--- return
|
||
|
return (NULL);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
conds.Op(newOp);
|
||
|
setOrAnd=true;
|
||
|
}
|
||
|
index=index+1;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
string str;
|
||
|
condExp=copyExpressions.At(index);
|
||
|
str=condExp.Text();
|
||
|
condExp=copyExpressions.At(index+1);
|
||
|
Print(StringFormat("%s cannot goes after %s",str,condExp.Text()));
|
||
|
//--- return
|
||
|
return (NULL);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
//--- return conditions
|
||
|
return (conds);
|
||
|
}
|
||
|
}
|
||
|
//+------------------------------------------------------------------+
|
||
|
//| Parse conclusion |
|
||
|
//+------------------------------------------------------------------+
|
||
|
static CSingleCondition *CRuleParser::ParseConclusion(CArrayObj *conditionExpression,CList *out,CList *lexems)
|
||
|
{
|
||
|
CArrayObj *copyExpression=conditionExpression;
|
||
|
//--- Remove extra brackets
|
||
|
CDictionary_String_Obj *p_so;
|
||
|
CDictionary_String_Obj *p_so_open;
|
||
|
CDictionary_String_Obj *p_so_close;
|
||
|
for(int i=0; i<lexems.Total(); i++)
|
||
|
{
|
||
|
p_so=lexems.GetNodeAtIndex(i);
|
||
|
if(p_so.Key()=="(")
|
||
|
{
|
||
|
p_so_open=p_so;
|
||
|
}
|
||
|
if(p_so.Key()==")")
|
||
|
{
|
||
|
p_so_close=p_so;
|
||
|
}
|
||
|
}
|
||
|
int index=0;
|
||
|
int Total=copyExpression.Total();
|
||
|
while(Total>=2 && (copyExpression.At(index)==p_so_open.Value() && copyExpression.At(index+conditionExpression.Total()-1)==p_so_close.Value()))
|
||
|
{
|
||
|
index=index+1;
|
||
|
Total=Total-2;
|
||
|
}
|
||
|
if(Total!=3)
|
||
|
{
|
||
|
Print("Conclusion part of the rule should be in form: 'variable is term'");
|
||
|
//--- return
|
||
|
return (NULL);
|
||
|
}
|
||
|
//--- Parse variable
|
||
|
CLexem *exprVariable=copyExpression.At(index);
|
||
|
if(!exprVariable.IsTypeOf(TYPE_CLASS_VarLexem))
|
||
|
{
|
||
|
Print(StringFormat("Wrong identifier '%s' in conclusion part of the rule.",exprVariable.Text()));
|
||
|
//--- return
|
||
|
return (NULL);
|
||
|
}
|
||
|
CVarLexem *varLexem=exprVariable;
|
||
|
if(varLexem.Input()==true)
|
||
|
{
|
||
|
Print("The variable in conclusion part must be an output variable.");
|
||
|
//--- return
|
||
|
return (NULL);
|
||
|
}
|
||
|
//--- Parse 'is' lexem
|
||
|
CLexem *exprIs=copyExpression.At(index+1);
|
||
|
for(int i=0; i<lexems.Total(); i++)
|
||
|
{
|
||
|
p_so=lexems.GetNodeAtIndex(i);
|
||
|
if(p_so.Key()=="is")
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if(exprIs!=p_so.Value())
|
||
|
{
|
||
|
Print(StringFormat("'is' keyword must go after %s identifier.",varLexem.Text()));
|
||
|
//--- return
|
||
|
return (NULL);
|
||
|
}
|
||
|
//--- Parse term
|
||
|
CLexem *exprTerm=copyExpression.At(index+2);
|
||
|
if(!exprTerm.IsTypeOf(TYPE_CLASS_TermLexem))
|
||
|
{
|
||
|
Print(StringFormat("Wrong identifier '%s' in conclusion part of the rule.",exprTerm.Text()));
|
||
|
}
|
||
|
IAltLexem *altLexem=exprTerm;
|
||
|
CTermLexem *termLexem=NULL;
|
||
|
do
|
||
|
{
|
||
|
if(!altLexem.IsTypeOf(TYPE_CLASS_TermLexem))
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
termLexem=altLexem;
|
||
|
if(varLexem.Var().Values().IndexOf(termLexem.Term())==-1)
|
||
|
{
|
||
|
termLexem=NULL;
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
while((altLexem=altLexem.Alternative())!=NULL && termLexem==NULL);
|
||
|
if(termLexem==NULL)
|
||
|
{
|
||
|
Print(StringFormat("Wrong identifier '%s' in conclusion part of the rule.",exprTerm.Text()));
|
||
|
//--- return
|
||
|
return (NULL);
|
||
|
}
|
||
|
//--- Return fuzzy rule's conclusion
|
||
|
INamedVariable *var=varLexem.Var();
|
||
|
INamedValue *term=termLexem.Term();
|
||
|
//--- return single condition
|
||
|
return new CSingleCondition(var, term, false);
|
||
|
}
|
||
|
//+------------------------------------------------------------------+
|