namespace Bloxstrap
{
///
/// FastFlag Expression Parser
///
public class FlexParser
{
private readonly string _expression;
private readonly List _tokens = new();
private int _tokenPos = 0;
private static readonly Dictionary _staticTokenMap = new()
{
{ "null", FlexTokenType.NULL },
{ "true", FlexTokenType.BOOL },
{ "false", FlexTokenType.BOOL },
{ "==", FlexTokenType.COMPARE_EQ },
{ "!=", FlexTokenType.COMPARE_NEQ },
{ ">", FlexTokenType.COMPARE_GT },
{ "<", FlexTokenType.COMPARE_LT },
{ ">=", FlexTokenType.COMPARE_GEQ },
{ "<=", FlexTokenType.COMPARE_LEQ },
{ "&&", FlexTokenType.LOGIC_AND },
{ "||", FlexTokenType.LOGIC_OR },
{ "(", FlexTokenType.BRACKET_OPEN },
{ ")", FlexTokenType.BRACKET_CLOSE }
};
private static readonly Dictionary _regexTokenMap = new()
{
{ @"^\d+", FlexTokenType.NUMBER },
{ @"^('[^']+')", FlexTokenType.STRING },
{ @"^([a-zA-Z0-9_\.]+)", FlexTokenType.FLAG }
};
public FlexParser(string expression)
{
_expression = expression;
Tokenize();
}
public bool Evaluate() => EvaluateExpression();
private void Tokenize()
{
int position = 0;
while (position < _expression.Length)
{
string exprSlice = _expression.Substring(position);
if (exprSlice[0] == ' ')
{
position++;
continue;
}
string exprSliceLower = exprSlice.ToLowerInvariant();
var mapMatch = _staticTokenMap.FirstOrDefault(x => exprSliceLower.StartsWith(x.Key));
if (mapMatch.Key is null)
{
bool matched = false;
foreach (var entry in _regexTokenMap)
{
var match = Regex.Match(exprSlice, entry.Key);
if (match.Success)
{
matched = true;
string phrase = match.Groups[match.Groups.Count > 1 ? 1 : 0].Value;
_tokens.Add(new(entry.Value, phrase, position));
position += phrase.Length;
break;
}
}
if (!matched)
throw new FlexParseException(_expression, "unknown identifier", position);
}
else
{
_tokens.Add(new(mapMatch.Value, mapMatch.Key, position));
position += mapMatch.Key.Length;
}
}
}
///
/// The brackets in this example expression are instances of subexpressions: "[FLogNetwork == 7] || [false]"
///
///
///
private bool EvaluateSubExpression()
{
var token = _tokens.ElementAtOrDefault(_tokenPos++);
if (token?.Type == FlexTokenType.FLAG)
{
var compToken = _tokens.ElementAtOrDefault(_tokenPos++);
if (compToken is null || compToken.Value is null || !compToken.IsComparisonOperator)
throw new FlexParseException(_expression, "expected comparison operator", compToken);
var valueToken = _tokens.ElementAtOrDefault(_tokenPos++);
if (valueToken is null || !valueToken.IsDataType)
throw new FlexParseException(_expression, "expected data", valueToken);
string? flagValue = token.GetActualValue();
if (compToken.IsInequalityOperator)
{
if (flagValue is null || valueToken.Value is null)
return false;
if (valueToken.Type != FlexTokenType.NUMBER)
throw new FlexParseException(_expression, "expected integer", valueToken);
if (!long.TryParse(flagValue, out long intFlagValue))
return false;
long intValue = long.Parse(valueToken.Value);
switch (compToken.Type)
{
case FlexTokenType.COMPARE_GT:
return intFlagValue > intValue;
case FlexTokenType.COMPARE_LT:
return intFlagValue < intValue;
case FlexTokenType.COMPARE_GEQ:
return intFlagValue >= intValue;
case FlexTokenType.COMPARE_LEQ:
return intFlagValue <= intValue;
}
}
else
{
if (valueToken.Type == FlexTokenType.NULL)
return flagValue is null;
bool result = string.Compare(flagValue, valueToken.GetActualValue(), StringComparison.InvariantCultureIgnoreCase) == 0;
if (compToken.Type == FlexTokenType.COMPARE_EQ)
return result;
else
return !result;
}
}
else if (token?.Type == FlexTokenType.BOOL)
{
return token.BoolValue;
}
return false;
}
private bool EvaluateExpression(int finalPos = 0)
{
bool result = false;
if (finalPos == 0)
finalPos = _tokens.Count;
while (_tokenPos < finalPos)
{
var token = _tokens.ElementAtOrDefault(_tokenPos);
if (token is null)
break;
if (token.Type == FlexTokenType.FLAG || token.Type == FlexTokenType.BOOL)
{
result = EvaluateSubExpression();
}
else if (token.Type == FlexTokenType.BRACKET_OPEN)
{
var closeBracketToken = _tokens.Find(x => x.Type == FlexTokenType.BRACKET_CLOSE);
if (closeBracketToken is null)
throw new FlexParseException(_expression, "expected closing bracket");
_tokenPos++;
result = EvaluateExpression(_tokens.IndexOf(closeBracketToken));
_tokenPos++;
}
else
{
throw new FlexParseException(_expression, "identifier was unexpected here", token);
}
var nextToken = _tokens.ElementAtOrDefault(_tokenPos++);
if (nextToken is null)
break;
if (!nextToken.IsLogicToken)
throw new FlexParseException(_expression, "expected boolean operator", nextToken);
if (nextToken.Type == FlexTokenType.LOGIC_AND)
{
if (result)
{
continue;
}
else
{
int bracketNesting = 0;
while (_tokenPos < finalPos)
{
token = _tokens[_tokenPos++];
if (token.Type == FlexTokenType.BRACKET_OPEN)
bracketNesting++;
else if (token.Type == FlexTokenType.BRACKET_CLOSE)
bracketNesting--;
else if (bracketNesting == 0 && token.Type == FlexTokenType.LOGIC_OR)
break;
}
if (bracketNesting != 0)
throw new FlexParseException(_expression, "unclosed bracket");
}
}
else if (nextToken.Type == FlexTokenType.LOGIC_OR && result)
{
break;
}
}
return result;
}
}
}