/*##############################################################################
Copyright (C) 2011 HPCC Systems.
All rights reserved. This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
############################################################################## */
#include "platform.h"
#include "jlib.hpp"
#include "jset.hpp"
#include "hql.hpp"
#include "hqlutil.hpp"
#include "hqltrans.ipp"
#include "hqlalias.hpp"
//---------------------------------------------------------------------------------------------------------------------
static bool isSubsetOf(IHqlExpression * search, IHqlExpression * expr)
{
loop
{
if (search->queryBody() == expr->queryBody())
return true;
if (expr->getOperator() != no_and)
return false;
//Check in order most likely to no recurse too deeply.
if (isSubsetOf(search, expr->queryChild(1)))
return true;
expr = expr->queryChild(0);
}
}
ConditionItem::ConditionItem(IHqlExpression * _expr, ConditionSet * _parent)
: expr(_expr), parent(_parent)
{
}
bool ConditionItem::isAlwaysConditionalOn(IHqlExpression * search) const
{
if (isSubsetOf(search, expr))
return true;
if (!parent)
return false;
return parent->isAlwaysConditionalOn(search);
}
//---------------------------------------------------------------------------------------------------------------------
bool ConditionSet::addOrCondition(IHqlExpression * expr, ConditionSet * parent)
{
if (unconditional)
return false;
if (!expr)
{
setUnconditional();
return true;
}
ForEachItemIn(iMerge, conditions)
{
ConditionItem & cur = conditions.item(iMerge);
if (cur.equals(expr, parent))
return false;
}
conditions.append(*new ConditionItem(expr, parent));
return true;
}
void ConditionSet::setUnconditional()
{
unconditional = true;
conditions.kill();
}
/*
IHqlExpression * ConditionSet::getGuardCondition() const
{
if (unconditional)
return NULL;
HqlExprArray values;
ForEachItemIn(i, conditions)
values.append(*conditions.item(i).getCondition());
OwnedITypeInfo boolType = makeBoolType();
return createBalanced(no_or, boolType, values);
}
*/
bool ConditionSet::isAlwaysConditionalOn(IHqlExpression * expr)
{
if (!expr)
return true;
if (unconditional)
return false;
//Cache the previous search value to avoid a potentially exponential search of the caller tree.
if (expr == isAlwaysCache.search)
return isAlwaysCache.value;
bool matches = true;
ForEachItemIn(i, conditions)
{
ConditionItem & cur = conditions.item(i);
if (!cur.isAlwaysConditionalOn(expr))
{
matches = false;
break;
}
}
isAlwaysCache.search.set(expr);
isAlwaysCache.value = matches;
return matches;
}
//---------------------------------------------------------------------------------------------------------------------
void ConditionTracker::pushCondition(IHqlExpression * expr, ConditionSet * parent)
{
assertex(expr);
conditionStack.append(expr);
parentStack.append(parent);
}
void ConditionTracker::popCondition()
{
conditionStack.pop();
parentStack.pop();
}
bool ConditionTracker::addActiveCondition(ConditionSet & conditions)
{
if (conditionStack.ordinality() == 0)
return conditions.addOrCondition(NULL, NULL);
return conditions.addOrCondition(conditionStack.tos(), parentStack.tos());
}
//---------------------------------------------------------------------------------------------------------------------
class NestedIfInfo : public NewTransformInfo
{
public:
NestedIfInfo(IHqlExpression * _original) : NewTransformInfo(_original)
{
isShared = false;
containsIf = false;
conditions = NULL;
}
~NestedIfInfo() { delete conditions; }
ConditionSet * queryConditions()
{
if (!conditions)
conditions = new ConditionSet;
return conditions;
}
public:
ConditionSet * conditions;
bool isShared;
bool containsIf;
};
//MORE: Could remove dependancy on insideCompound if it was ok to have compound operators scattered through the
// contents of a compound item. Probably would cause few problems, and would make life simpler
class NestedIfTransformer : public NewHqlTransformer
{
public:
NestedIfTransformer();
IHqlExpression * process(IHqlExpression * expr);
bool process(const HqlExprArray & exprs, HqlExprArray & transformed);
protected:
void analyseGatherIfs(IHqlExpression * expr);
void analyseNoteConditions(IHqlExpression * expr);
virtual void analyseExpr(IHqlExpression * expr);
virtual IHqlExpression * createTransformed(IHqlExpression * expr);
virtual ANewTransformInfo * createTransformInfo(IHqlExpression * expr)
{
return new NestedIfInfo(expr);
}
inline NestedIfInfo * queryBodyExtra(IHqlExpression * expr) { return static_cast(queryTransformExtra(expr->queryBody())); }
protected:
unsigned numIfs;
ConditionTracker tracker;
};
static HqlTransformerInfo nestedIfTransformerInfo("NestedIfTransformer");
NestedIfTransformer::NestedIfTransformer() : NewHqlTransformer(nestedIfTransformerInfo)
{
numIfs = 0;
}
void NestedIfTransformer::analyseExpr(IHqlExpression * expr)
{
IHqlExpression * body = expr->queryBody();
switch (pass)
{
case 0:
analyseGatherIfs(body);
break;
case 1:
analyseNoteConditions(body);
break;
}
}
void NestedIfTransformer::analyseGatherIfs(IHqlExpression * expr)
{
if (expr->getOperator() == no_if)
numIfs++;
NestedIfInfo * extra = queryBodyExtra(expr);
if (alreadyVisited(expr))
{
extra->isShared = true;
if (extra->containsIf)
numIfs++;
return;
}
unsigned prevIfCount = numIfs;
NewHqlTransformer::analyseExpr(expr);
if (prevIfCount != numIfs)
extra->containsIf = true;
}
void NestedIfTransformer::analyseNoteConditions(IHqlExpression * expr)
{
node_operator op = expr->getOperator();
NestedIfInfo * extra = queryBodyExtra(expr);
if (extra->isShared || (op == no_if))
{
if (!tracker.addActiveCondition(*extra->queryConditions()))
return;
}
if (!extra->containsIf)
return;
if (op == no_if)
{
IHqlExpression * cond = expr->queryChild(0);
OwnedHqlExpr normalCond = getNormalizedCondition(cond);
analyseExpr(cond);
tracker.pushCondition(normalCond, extra->queryConditions());
analyseExpr(expr->queryChild(1));
tracker.popCondition();
IHqlExpression * falseExpr = queryRealChild(expr, 2);
if (falseExpr)
{
OwnedHqlExpr inverseCond = getInverse(normalCond);
tracker.pushCondition(inverseCond, extra->queryConditions());
analyseExpr(falseExpr);
tracker.popCondition();
}
}
else
{
NewHqlTransformer::analyseExpr(expr);
}
}
IHqlExpression * NestedIfTransformer::createTransformed(IHqlExpression * expr)
{
if (expr->getOperator() == no_if)
{
IHqlExpression * cond = expr->queryChild(0);
IHqlExpression * falseExpr = queryRealChild(expr, 2);
OwnedHqlExpr normalCond = getNormalizedCondition(cond);
NestedIfInfo * extra = queryBodyExtra(expr);
IHqlExpression * selected = NULL;
if (extra->queryConditions()->isAlwaysConditionalOn(normalCond))
{
selected = expr->queryChild(1);
}
else if (falseExpr)
{
OwnedHqlExpr inverseCond = getInverse(normalCond);
if (extra->queryConditions()->isAlwaysConditionalOn(inverseCond))
selected = falseExpr;
}
if (selected)
{
const char * branch = (selected == falseExpr) ? "false" : "true";
StringBuffer exprText, locationText;
appendLocation(locationText, queryLocation(expr), ": ");
DBGLOG("%s%s replaced with %s branch since condition always %s", locationText.str(), queryChildNodeTraceText(exprText, expr), branch, branch);
OwnedHqlExpr ret = transform(selected);
return cloneMissingAnnotations(expr, ret);
}
}
return NewHqlTransformer::createTransformed(expr);
}
IHqlExpression * NestedIfTransformer::process(IHqlExpression * expr)
{
analyse(expr, 0);
if (numIfs < 2)
return LINK(expr);
analyse(expr, 1);
return transformRoot(expr);
}
bool NestedIfTransformer::process(const HqlExprArray & exprs, HqlExprArray & transformed)
{
ForEachItemIn(i1, exprs)
analyse(&exprs.item(i1), 0);
if (numIfs < 2)
return false;
ForEachItemIn(i2, exprs)
analyse(&exprs.item(i2), 1);
transformRoot(exprs, transformed);
return true;
}
IHqlExpression * optimizeNestedConditional(IHqlExpression * expr)
{
NestedIfTransformer transformer;
return transformer.process(expr);
}
void optimizeNestedConditional(HqlExprArray & exprs)
{
NestedIfTransformer transformer;
HqlExprArray transformed;
if (transformer.process(exprs, transformed))
exprs.swapWith(transformed);
}