using System;
|
using System.Collections.Generic;
|
using System.Linq;
|
using System.Text;
|
using Jace.Execution;
|
using Jace.Operations;
|
using Jace.Tokenizer;
|
using Jace.Util;
|
|
namespace Jace
|
{
|
public class AstBuilder
|
{
|
private readonly IFunctionRegistry functionRegistry;
|
private readonly IConstantRegistry localConstantRegistry;
|
private readonly bool caseSensitive;
|
private Dictionary<char, int> operationPrecedence = new Dictionary<char, int>();
|
private Stack<Operation> resultStack = new Stack<Operation>();
|
private Stack<Token> operatorStack = new Stack<Token>();
|
private Stack<int> parameterCount = new Stack<int>();
|
|
public AstBuilder(IFunctionRegistry functionRegistry, bool caseSensitive, IConstantRegistry compiledConstants = null)
|
{
|
if (functionRegistry == null)
|
throw new ArgumentNullException("functionRegistry");
|
|
this.functionRegistry = functionRegistry;
|
this.localConstantRegistry = compiledConstants ?? new ConstantRegistry(caseSensitive);
|
this.caseSensitive = caseSensitive;
|
|
operationPrecedence.Add('(', 0);
|
operationPrecedence.Add('&', 1);
|
operationPrecedence.Add('|', 1);
|
operationPrecedence.Add('<', 2);
|
operationPrecedence.Add('>', 2);
|
operationPrecedence.Add('≤', 2);
|
operationPrecedence.Add('≥', 2);
|
operationPrecedence.Add('≠', 2);
|
operationPrecedence.Add('=', 2);
|
operationPrecedence.Add('+', 3);
|
operationPrecedence.Add('-', 3);
|
operationPrecedence.Add('*', 4);
|
operationPrecedence.Add('/', 4);
|
operationPrecedence.Add('%', 4);
|
operationPrecedence.Add('_', 6);
|
operationPrecedence.Add('^', 5);
|
}
|
|
public Operation Build(IList<Token> tokens)
|
{
|
resultStack.Clear();
|
operatorStack.Clear();
|
|
parameterCount.Clear();
|
|
foreach (Token token in tokens)
|
{
|
object value = token.Value;
|
|
switch (token.TokenType)
|
{
|
case TokenType.Integer:
|
resultStack.Push(new IntegerConstant((int)token.Value));
|
break;
|
case TokenType.FloatingPoint:
|
resultStack.Push(new FloatingPointConstant((double)token.Value));
|
break;
|
case TokenType.Text:
|
if (functionRegistry.IsFunctionName((string)token.Value))
|
{
|
operatorStack.Push(token);
|
parameterCount.Push(1);
|
}
|
else
|
{
|
string tokenValue = (string)token.Value;
|
if (localConstantRegistry.IsConstantName(tokenValue))
|
{
|
resultStack.Push(new FloatingPointConstant(localConstantRegistry.GetConstantInfo(tokenValue).Value));
|
}
|
else
|
{
|
if (!caseSensitive)
|
{
|
tokenValue = tokenValue.ToLowerFast();
|
}
|
resultStack.Push(new Variable(tokenValue));
|
}
|
}
|
break;
|
case TokenType.LeftBracket:
|
operatorStack.Push(token);
|
break;
|
case TokenType.RightBracket:
|
PopOperations(true, token);
|
//parameterCount.Pop();
|
break;
|
case TokenType.ArgumentSeparator:
|
PopOperations(false, token);
|
parameterCount.Push(parameterCount.Pop() + 1);
|
break;
|
case TokenType.Operation:
|
Token operation1Token = token;
|
char operation1 = (char)operation1Token.Value;
|
|
while (operatorStack.Count > 0 && (operatorStack.Peek().TokenType == TokenType.Operation ||
|
operatorStack.Peek().TokenType == TokenType.Text))
|
{
|
Token operation2Token = operatorStack.Peek();
|
bool isFunctionOnTopOfStack = operation2Token.TokenType == TokenType.Text;
|
|
if (!isFunctionOnTopOfStack)
|
{
|
char operation2 = (char)operation2Token.Value;
|
|
if ((IsLeftAssociativeOperation(operation1) &&
|
operationPrecedence[operation1] <= operationPrecedence[operation2]) ||
|
(operationPrecedence[operation1] < operationPrecedence[operation2]))
|
{
|
operatorStack.Pop();
|
resultStack.Push(ConvertOperation(operation2Token));
|
}
|
else
|
{
|
break;
|
}
|
}
|
else
|
{
|
operatorStack.Pop();
|
resultStack.Push(ConvertFunction(operation2Token));
|
}
|
}
|
|
operatorStack.Push(operation1Token);
|
break;
|
}
|
}
|
|
PopOperations(false, null);
|
|
VerifyResultStack();
|
|
return resultStack.First();
|
}
|
|
private void PopOperations(bool untillLeftBracket, Token? currentToken)
|
{
|
if (untillLeftBracket && !currentToken.HasValue)
|
throw new ArgumentNullException("currentToken", "If the parameter \"untillLeftBracket\" is set to true, " +
|
"the parameter \"currentToken\" cannot be null.");
|
|
while (operatorStack.Count > 0 && operatorStack.Peek().TokenType != TokenType.LeftBracket)
|
{
|
Token token = operatorStack.Pop();
|
|
switch (token.TokenType)
|
{
|
case TokenType.Operation:
|
resultStack.Push(ConvertOperation(token));
|
break;
|
case TokenType.Text:
|
resultStack.Push(ConvertFunction(token));
|
break;
|
}
|
}
|
|
if (untillLeftBracket)
|
{
|
if (operatorStack.Count > 0 && operatorStack.Peek().TokenType == TokenType.LeftBracket)
|
operatorStack.Pop();
|
else
|
throw new ParseException(string.Format("No matching left bracket found for the right " +
|
"bracket at position {0}.", currentToken.Value.StartPosition));
|
}
|
else
|
{
|
if (operatorStack.Count > 0 && operatorStack.Peek().TokenType == TokenType.LeftBracket
|
&& !(currentToken.HasValue && currentToken.Value.TokenType == TokenType.ArgumentSeparator))
|
throw new ParseException(string.Format("No matching right bracket found for the left " +
|
"bracket at position {0}.", operatorStack.Peek().StartPosition));
|
}
|
}
|
|
private Operation ConvertOperation(Token operationToken)
|
{
|
try
|
{
|
DataType dataType;
|
Operation argument1;
|
Operation argument2;
|
Operation divisor;
|
Operation divident;
|
|
switch ((char)operationToken.Value)
|
{
|
case '+':
|
argument2 = resultStack.Pop();
|
argument1 = resultStack.Pop();
|
dataType = RequiredDataType(argument1, argument2);
|
|
return new Addition(dataType, argument1, argument2);
|
case '-':
|
argument2 = resultStack.Pop();
|
argument1 = resultStack.Pop();
|
dataType = RequiredDataType(argument1, argument2);
|
|
return new Subtraction(dataType, argument1, argument2);
|
case '*':
|
argument2 = resultStack.Pop();
|
argument1 = resultStack.Pop();
|
dataType = RequiredDataType(argument1, argument2);
|
|
return new Multiplication(dataType, argument1, argument2);
|
case '/':
|
divisor = resultStack.Pop();
|
divident = resultStack.Pop();
|
|
return new Division(DataType.FloatingPoint, divident, divisor);
|
case '%':
|
divisor = resultStack.Pop();
|
divident = resultStack.Pop();
|
|
return new Modulo(DataType.FloatingPoint, divident, divisor);
|
case '_':
|
argument1 = resultStack.Pop();
|
|
return new UnaryMinus(argument1.DataType, argument1);
|
case '^':
|
Operation exponent = resultStack.Pop();
|
Operation @base = resultStack.Pop();
|
|
return new Exponentiation(DataType.FloatingPoint, @base, exponent);
|
case '&':
|
argument2 = resultStack.Pop();
|
argument1 = resultStack.Pop();
|
dataType = RequiredDataType(argument1, argument2);
|
|
return new And(dataType, argument1, argument2);
|
case '|':
|
argument2 = resultStack.Pop();
|
argument1 = resultStack.Pop();
|
dataType = RequiredDataType(argument1, argument2);
|
|
return new Or(dataType, argument1, argument2);
|
case '<':
|
argument2 = resultStack.Pop();
|
argument1 = resultStack.Pop();
|
dataType = RequiredDataType(argument1, argument2);
|
|
return new LessThan(dataType, argument1, argument2);
|
case '≤':
|
argument2 = resultStack.Pop();
|
argument1 = resultStack.Pop();
|
dataType = RequiredDataType(argument1, argument2);
|
|
return new LessOrEqualThan(dataType, argument1, argument2);
|
case '>':
|
argument2 = resultStack.Pop();
|
argument1 = resultStack.Pop();
|
dataType = RequiredDataType(argument1, argument2);
|
|
return new GreaterThan(dataType, argument1, argument2);
|
case '≥':
|
argument2 = resultStack.Pop();
|
argument1 = resultStack.Pop();
|
dataType = RequiredDataType(argument1, argument2);
|
|
return new GreaterOrEqualThan(dataType, argument1, argument2);
|
case '=':
|
argument2 = resultStack.Pop();
|
argument1 = resultStack.Pop();
|
dataType = RequiredDataType(argument1, argument2);
|
|
return new Equal(dataType, argument1, argument2);
|
case '≠':
|
argument2 = resultStack.Pop();
|
argument1 = resultStack.Pop();
|
dataType = RequiredDataType(argument1, argument2);
|
|
return new NotEqual(dataType, argument1, argument2);
|
default:
|
throw new ArgumentException(string.Format("Unknown operation \"{0}\".", operationToken), "operation");
|
}
|
}
|
catch (InvalidOperationException)
|
{
|
// If we encounter a Stack empty issue this means there is a syntax issue in
|
// the mathematical formula
|
throw new ParseException(string.Format("There is a syntax issue for the operation \"{0}\" at position {1}. " +
|
"The number of arguments does not match with what is expected.", operationToken.Value, operationToken.StartPosition));
|
}
|
}
|
|
private Operation ConvertFunction(Token functionToken)
|
{
|
try
|
{
|
string functionName = ((string)functionToken.Value).ToLowerInvariant();
|
|
if (functionRegistry.IsFunctionName(functionName))
|
{
|
FunctionInfo functionInfo = functionRegistry.GetFunctionInfo(functionName);
|
|
int numberOfParameters;
|
|
if (functionInfo.IsDynamicFunc) {
|
numberOfParameters = parameterCount.Pop();
|
}
|
else {
|
parameterCount.Pop();
|
numberOfParameters = functionInfo.NumberOfParameters;
|
}
|
|
List<Operation> operations = new List<Operation>();
|
for (int i = 0; i < numberOfParameters; i++)
|
operations.Add(resultStack.Pop());
|
operations.Reverse();
|
|
return new Function(DataType.FloatingPoint, functionName, operations, functionInfo.IsIdempotent);
|
}
|
else
|
{
|
throw new ArgumentException(string.Format("Unknown function \"{0}\".", functionToken.Value), "function");
|
}
|
}
|
catch (InvalidOperationException)
|
{
|
// If we encounter a Stack empty issue this means there is a syntax issue in
|
// the mathematical formula
|
throw new ParseException(string.Format("There is a syntax issue for the function \"{0}\" at position {1}. " +
|
"The number of arguments does not match with what is expected.", functionToken.Value, functionToken.StartPosition));
|
}
|
}
|
|
private void VerifyResultStack()
|
{
|
if(resultStack.Count > 1)
|
{
|
Operation[] operations = resultStack.ToArray();
|
|
for (int i = 1; i < operations.Length; i++)
|
{
|
Operation operation = operations[i];
|
|
if (operation.GetType() == typeof(IntegerConstant))
|
{
|
IntegerConstant constant = (IntegerConstant)operation;
|
throw new ParseException(string.Format("Unexpected integer constant \"{0}\" found.", constant.Value));
|
}
|
else if (operation.GetType() == typeof(FloatingPointConstant))
|
{
|
FloatingPointConstant constant = (FloatingPointConstant)operation;
|
throw new ParseException(string.Format("Unexpected floating point constant \"{0}\" found.", constant.Value));
|
}
|
}
|
|
throw new ParseException("The syntax of the provided formula is not valid.");
|
}
|
}
|
|
private bool IsLeftAssociativeOperation(char character)
|
{
|
return character == '*' || character == '+' || character == '-' || character == '/';
|
}
|
|
private DataType RequiredDataType(Operation argument1, Operation argument2)
|
{
|
return (argument1.DataType == DataType.FloatingPoint || argument2.DataType == DataType.FloatingPoint) ? DataType.FloatingPoint : DataType.Integer;
|
}
|
}
|
}
|