我有一个过程可以创建很长的数学表达式作为运行计算的机制。
但是,用户现在正在生成多个计算表达式,并且部分表达式被零除存在问题。可悲的是,他们希望在这些计算中具有3/0 = 0的行为,因此它不会停止其余的计算。即使在技术上不正确。
这是他们想要看1的东西的一个例子:
SELECT (3/0)+1 FROM DUAL
我曾建议将分隔符包装在一个函数中,但被告知这对用户来说太困难了,应该在后端处理。
我不会立即执行这些数学表达式,我只想更改它们。
有人有什么建议吗?我看到了很多有关如何执行动态表达式的示例,但没有有关如何修改它们的示例。
理想情况下,我想转换任何这样的表达式:
SELECT (3/0)+1 FROM DUAL
进入
SELECT DIVIDE(3,0)+1 FROM DUAL
请记住,它可能是非常复杂的解析树的一部分,而不是您看到的简单示例。
我不太确定从哪开始。
该表达式最终将在oracle中执行...我只需要修改该表达式...不更改执行它的方式即可。
答案 0 :(得分:0)
您可以使用一些基于ANTLR的PL / SQL解析器,例如this。然后构造AST树,对其进行转换,对其进行序列化,等等。
出于缩进查询重写的目的,我使用了类似的方法。
但是无论如何,成就是对甲骨文“敌视”,换句话说,您正试图“自拔腿”。
答案 1 :(得分:0)
我想我已经知道了。我下载并修改了Mathos Math解析器。现在它将返回字符串而不是数字。
这里的关键部分是操作员。当达到分隔线时,它将返回我需要的字符串,而不是典型的“ a / b”。
Operators = new Dictionary<string, Func<string, string, string>>(7)
{
["^"] = (a, b) => "Power(" + a + "," + b + ")",
["%"] = (a, b) => "(" + a + "%" + b + ")",
[":"] = (a, b) => "(" + a + "/" + b + ")",
["/"] = (a, b) => "(Divide(" + a + "," + b + "))",
["*"] = (a, b) => "(" + a + "*" + b + ")",
["-"] = (a, b) => "(" + a + "-" + b + ")",
["+"] = (a, b) => "(" + a + "+" + b + ")",
};
这是该课程的完整修订版
/*
* Copyright (C) 2012-2018, Mathos Project.
* All rights reserved.
*
* Please see the license file in the project folder,
* or go to https://github.com/MathosProject/Mathos-Parser/blob/master/LICENSE.md.
*
* Please feel free to ask me directly at my email!
* artem@artemlos.net
*/
using System;
using System.Linq;
using System.Globalization;
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace Mathos.Parser
{
/// <summary>
/// This is a mathematical expression parser that allows you to perform calculations on string values.
/// </summary>
public class MathParserRevised
{
#region Properties
/// <summary>
/// All operators that you want to define should be inside this property.
/// </summary>
public Dictionary<string, Func<string, string, string>> Operators { get; set; }
/// <summary>
/// All functions that you want to define should be inside this property.
/// </summary>
public Dictionary<string, Func<string[], string>> LocalFunctions { get; set; }
/// <summary>
/// All variables that you want to define should be inside this property.
/// </summary>
public Dictionary<string, string> LocalVariables { get; set; }
/// <summary>
/// When converting the result from the Parse method or ProgrammaticallyParse method ToString(),
/// please use this culture info.
/// </summary>
public CultureInfo CultureInfo { get; }
#endregion
/// <summary>
/// Initializes a new instance of the MathParser class, and optionally with
/// predefined functions, operators, and variables.
/// </summary>
/// <param name="loadPreDefinedFunctions">This will load abs, cos, cosh, arccos, sin, sinh, arcsin, tan, tanh, arctan, sqrt, rem, and round.</param>
/// <param name="loadPreDefinedOperators">This will load %, *, :, /, +, -, >, <, and =</param>
/// <param name="loadPreDefinedVariables">This will load pi, tao, e, phi, major, minor, pitograd, and piofgrad.</param>
/// <param name="cultureInfo">The culture info to use when parsing. If null, defaults to invariant culture.</param>
public MathParserRevised(bool loadPreDefinedFunctions = true, bool loadPreDefinedOperators = true, bool loadPreDefinedVariables = true, CultureInfo cultureInfo = null)
{
if (loadPreDefinedOperators)
{
Operators = new Dictionary<string, Func<string, string, string>>(7)
{
["^"] = (a, b) => "Power(" + a + "," + b + ")",
["%"] = (a, b) => "(" + a + "%" + b + ")",
[":"] = (a, b) => "(" + a + "/" + b + ")",
["/"] = (a, b) => "(Divide(" + a + "," + b + "))",
["*"] = (a, b) => "(" + a + "*" + b + ")",
["-"] = (a, b) => "(" + a + "-" + b + ")",
["+"] = (a, b) => "(" + a + "+" + b + ")",
};
}
else
Operators = new Dictionary<string, Func<string, string, string>>();
if (loadPreDefinedFunctions)
{
LocalFunctions = new Dictionary<string, Func<string[], string>>(4)
{
["Min"] = inputs => "Least(" + String.Join(", ", inputs) + ")",
["Max"] = inputs => "Greatest(" + String.Join(", ", inputs) + ")",
["Ztn"] = inputs => "Ztn(" + String.Join(", ", inputs) + ")",
["Nvl"] = inputs => "Nvl(" + String.Join(", ", inputs) + ")"
};
}
else
LocalFunctions = new Dictionary<string, Func<string[], string>>();
if (loadPreDefinedVariables)
{
LocalVariables = new Dictionary<string, string>(8)
{
["pi"] = Math.PI.ToString(),
["tao"] = "6.28318530717959",
["e"] = Math.E.ToString(),
["phi"] = "1.61803398874989",
["major"] = "0.61803398874989",
["minor"] = "0.38196601125011",
["pitograd"] = "57.2957795130823",
["piofgrad"] = "0.01745329251994"
};
}
else
LocalVariables = new Dictionary<string, string>();
CultureInfo = cultureInfo ?? CultureInfo.InvariantCulture;
}
/// <summary>
/// Enter the math expression in form of a string.
/// </summary>
/// <param name="mathExpression">The math expression to parse.</param>
/// <returns>The result of executing <paramref name="mathExpression"/>.</returns>
public string Parse(string mathExpression)
{
return MathParserLogic(Lexer(mathExpression));
}
/// <summary>
/// Enter the math expression in form of a list of tokens.
/// </summary>
/// <param name="mathExpression">The math expression to parse.</param>
/// <returns>The result of executing <paramref name="mathExpression"/>.</returns>
public string Parse(ReadOnlyCollection<string> mathExpression)
{
return MathParserLogic(new List<string>(mathExpression));
}
/// <summary>
/// Enter the math expression in form of a string. You might also add/edit variables using "let" keyword.
/// For example, "let sampleVariable = 2+2".
///
/// Another way of adding/editing a variable is to type "varName := 20"
///
/// Last way of adding/editing a variable is to type "let varName be 20"
/// </summary>
/// <param name="mathExpression">The math expression to parse.</param>
/// <param name="correctExpression">If true, correct <paramref name="correctExpression"/> of any typos.</param>
/// <param name="identifyComments">If true, treat "#", "#{", and "}#" as comments.</param>
/// <returns>The result of executing <paramref name="mathExpression"/>.</returns>
public string ProgrammaticallyParse(string mathExpression, bool correctExpression = true, bool identifyComments = true)
{
if (identifyComments)
{
// Delete Comments #{Comment}#
mathExpression = System.Text.RegularExpressions.Regex.Replace(mathExpression, "#\\{.*?\\}#", "");
// Delete Comments #Comment
mathExpression = System.Text.RegularExpressions.Regex.Replace(mathExpression, "#.*$", "");
}
if (correctExpression)
{
// this refers to the Correction function which will correct stuff like artn to arctan, etc.
mathExpression = Correction(mathExpression);
}
string varName;
string varValue;
if (mathExpression.Contains("let"))
{
if (mathExpression.Contains("be"))
{
varName = mathExpression.Substring(mathExpression.IndexOf("let", StringComparison.Ordinal) + 3,
mathExpression.IndexOf("be", StringComparison.Ordinal) -
mathExpression.IndexOf("let", StringComparison.Ordinal) - 3);
mathExpression = mathExpression.Replace(varName + "be", "");
}
else
{
varName = mathExpression.Substring(mathExpression.IndexOf("let", StringComparison.Ordinal) + 3,
mathExpression.IndexOf("=", StringComparison.Ordinal) -
mathExpression.IndexOf("let", StringComparison.Ordinal) - 3);
mathExpression = mathExpression.Replace(varName + "=", "");
}
varName = varName.Replace(" ", "");
mathExpression = mathExpression.Replace("let", "");
varValue = Parse(mathExpression);
if (LocalVariables.ContainsKey(varName))
LocalVariables[varName] = varValue;
else
LocalVariables.Add(varName, varValue);
return varValue;
}
if (!mathExpression.Contains(":="))
return Parse(mathExpression);
//mathExpression = mathExpression.Replace(" ", ""); // remove white space
varName = mathExpression.Substring(0, mathExpression.IndexOf(":=", StringComparison.Ordinal));
mathExpression = mathExpression.Replace(varName + ":=", "");
varValue = Parse(mathExpression);
varName = varName.Replace(" ", "");
if (LocalVariables.ContainsKey(varName))
LocalVariables[varName] = varValue;
else
LocalVariables.Add(varName, varValue);
return varValue;
}
/// <summary>
/// This will convert a string expression into a list of tokens that can be later executed by Parse or ProgrammaticallyParse methods.
/// </summary>
/// <param name="mathExpression">The math expression to tokenize.</param>
/// <returns>The resulting tokens of <paramref name="mathExpression"/>.</returns>
public ReadOnlyCollection<string> GetTokens(string mathExpression)
{
return Lexer(mathExpression).AsReadOnly();
}
#region Core
/// <summary>
/// This will correct sqrt() and arctan() written in different ways only.
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
private string Correction(string input)
{
// Word corrections
input = System.Text.RegularExpressions.Regex.Replace(input, "\\b(sqr|sqrt)\\b", "sqrt",
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
input = System.Text.RegularExpressions.Regex.Replace(input, "\\b(atan2|arctan2)\\b", "arctan2",
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
//... and more
return input;
}
/// <summary>
/// Tokenizes <paramref name="expr"/>.
/// </summary>
/// <param name="expr">The expression to tokenize.</param>
/// <returns>The tokens.</returns>
private List<string> Lexer(string expr)
{
var token = "";
var tokens = new List<string>();
expr = expr.Replace("+-", "-");
expr = expr.Replace("-+", "-");
expr = expr.Replace("--", "+");
for (var i = 0; i < expr.Length; i++)
{
var ch = expr[i];
if (char.IsWhiteSpace(ch))
continue;
if (char.IsLetter(ch))
{
if (i != 0 && (char.IsDigit(expr[i - 1]) || expr[i - 1] == ')'))
tokens.Add("*");
token += ch;
while (i + 1 < expr.Length && char.IsLetterOrDigit(expr[i + 1]))
token += expr[++i];
tokens.Add(token);
token = "";
continue;
}
if (char.IsDigit(ch))
{
token += ch;
while (i + 1 < expr.Length && (char.IsDigit(expr[i + 1]) || expr[i + 1] == '.'))
token += expr[++i];
tokens.Add(token);
token = "";
continue;
}
if(ch == '.')
{
token += ch;
while (i + 1 < expr.Length && char.IsDigit(expr[i + 1]))
token += expr[++i];
tokens.Add(token);
token = "";
continue;
}
if (i + 1 < expr.Length && (ch == '-' || ch == '+') && char.IsDigit(expr[i + 1]) &&
(i == 0 || Operators.ContainsKey(expr[i - 1].ToString(
#if !NETSTANDARD1_4
CultureInfo
#endif
)) ||
i - 1 > 0 && expr[i - 1] == '('))
{
// if the above is true, then the token for that negative number will be "-1", not "-","1".
// to sum up, the above will be true if the minus sign is in front of the number, but
// at the beginning, for example, -1+2, or, when it is inside the brakets (-1).
// NOTE: this works for + as well!
token += ch;
while (i + 1 < expr.Length && (char.IsDigit(expr[i + 1]) || expr[i + 1] == '.'))
token += expr[++i];
tokens.Add(token);
token = "";
continue;
}
if (ch == '(')
{
if (i != 0 && (char.IsDigit(expr[i - 1]) || char.IsDigit(expr[i - 1]) || expr[i - 1] == ')'))
{
tokens.Add("*");
tokens.Add("(");
}
else
tokens.Add("(");
}
else
tokens.Add(ch.ToString());
}
return tokens;
}
private string MathParserLogic(List<string> tokens)
{
// Variables replacement
for (var i = 0; i < tokens.Count; i++)
{
if (LocalVariables.Keys.Contains(tokens[i]))
tokens[i] = LocalVariables[tokens[i]];
}
while (tokens.IndexOf("(") != -1)
{
// getting data between "(" and ")"
var open = tokens.LastIndexOf("(");
var close = tokens.IndexOf(")", open); // in case open is -1, i.e. no "(" // , open == 0 ? 0 : open - 1
if (open >= close)
throw new ArithmeticException("No closing bracket/parenthesis. Token: " + open.ToString(CultureInfo));
var roughExpr = new List<string>();
for (var i = open + 1; i < close; i++)
roughExpr.Add(tokens[i]);
string tmpResult;
var args = new List<string>();
var functionName = tokens[open == 0 ? 0 : open - 1];
if (LocalFunctions.Keys.Contains(functionName))
{
if (roughExpr.Contains(","))
{
// converting all arguments into a decimal array
for (var i = 0; i < roughExpr.Count; i++)
{
var defaultExpr = new List<string>();
var firstCommaOrEndOfExpression =
roughExpr.IndexOf(",", i) != -1
? roughExpr.IndexOf(",", i)
: roughExpr.Count;
while (i < firstCommaOrEndOfExpression)
defaultExpr.Add(roughExpr[i++]);
args.Add(defaultExpr.Count == 0 ? "0" : BasicArithmeticalExpression(defaultExpr));
}
// finally, passing the arguments to the given function
tmpResult = LocalFunctions[functionName](args.ToArray());
}
else
{
// but if we only have one argument, then we pass it directly to the function
tmpResult = LocalFunctions[functionName](new[]
{
BasicArithmeticalExpression(roughExpr)
});
}
}
else
{
// if no function is need to execute following expression, pass it
// to the "BasicArithmeticalExpression" method.
tmpResult = BasicArithmeticalExpression(roughExpr);
}
// when all the calculations have been done
// we replace the "opening bracket with the result"
// and removing the rest.
tokens[open] = tmpResult;
tokens.RemoveRange(open + 1, close - open);
if (LocalFunctions.Keys.Contains(functionName))
{
// if we also executed a function, removing
// the function name as well.
tokens.RemoveAt(open - 1);
}
}
// at this point, we should have replaced all brackets
// with the appropriate values, so we can simply
// calculate the expression. it's not so complex
// any more!
return BasicArithmeticalExpression(tokens);
}
private string BasicArithmeticalExpression(List<string> tokens)
{
// PERFORMING A BASIC ARITHMETICAL EXPRESSION CALCULATION
// THIS METHOD CAN ONLY OPERATE WITH NUMBERS AND OPERATORS
// AND WILL NOT UNDERSTAND ANYTHING BEYOND THAT.
switch (tokens.Count)
{
case 1:
return tokens[0];
case 2:
var op = tokens[0];
if (op == "-" || op == "+")
{
var first = op == "+" ? "" : (tokens[1].Substring(0, 1) == "-" ? "" : "-");
return first + tokens[1];
}
return Operators[op]("0", tokens[1]);
case 0:
return "0";
}
foreach (var op in Operators)
{
int opPlace;
while ((opPlace = tokens.IndexOf(op.Key)) != -1)
{
var numberA = tokens[opPlace - 1];
var numberB = tokens[opPlace + 1];
var result = op.Value(numberA, numberB);
tokens[opPlace - 1] = result;
tokens.RemoveRange(opPlace, 2);
}
}
return tokens[0];
}
#endregion
}
}