我想让用户尽可能自然地输入x
和y
方面的数学表达式。例如,我更喜欢只使用Complex.Sin(x)
。
Sin(x)
例如,当Sin(x)
由用户定义时,以下代码失败。
using Microsoft.CodeAnalysis.CSharp.Scripting;
using System;
using System.Numerics;
using static System.Console;
using static System.Numerics.Complex;
namespace MathEvaluator
{
public class Globals
{
public Complex x;
public Complex y;
}
class Program
{
async static void JobAsync(Microsoft.CodeAnalysis.Scripting.Script<Complex> script)
{
Complex x = new Complex(1, 0);
Complex y = new Complex(0, 1);
try
{
var result = await script.RunAsync(new Globals { x = x, y = y });
WriteLine($"{x} * {y} = {result.ReturnValue}\n");
}
catch (Exception e)
{
WriteLine(e.Message);
}
}
static void Main(string[] args)
{
Console.Write("Define your expression in x and y: ");
string expression = Console.ReadLine(); //user input
var script = CSharpScript.Create<Complex>(expression, globalsType: typeof(Globals));
script.Compile();
JobAsync(script);
}
}
}
如何使用
using static
指令动态生成代码?
答案 0 :(得分:3)
您可以为Create
函数提供脚本选项,以定义应为脚本设置的引用和导入:
var scriptOptions = ScriptOptions.Default
.WithReferences("System.Numerics")
.WithImports("System.Numerics.Complex");
var script = CSharpScript.Create<Complex>(expression, options: scriptOptions, globalsType: typeof(Globals));
这样,您可以在输入中使用Sin(x)
:
Define your expression in x and y: Sin(x) (1, 0) * (0, 1) = (0,841470984807897, 0)
但是,在处理用户输入时,您应该考虑编写自己的解析器。这允许您一方面为函数定义自己的“别名”(例如小写sin
)或甚至更宽松的语法;另一方面,它也增加了更多的安全性,因为现在,没有什么能阻止我这样做:
Define your expression in x and y: System.Console.WriteLine("I hacked this calculator!") I hacked this calculator! (1, 0) * (0, 1) = (0, 0)
我使用Roslyn的语法树解析创建了一个快速(和脏)的解析器。显然这是相当有限的(例如,因为它要求子表达式的所有返回值都为Complex
),但这可以让您了解这是如何工作的:
void Main()
{
string input = "y + 3 * Sin(x)";
var options = CSharpParseOptions.Default.WithKind(Microsoft.CodeAnalysis.SourceCodeKind.Script);
var expression = CSharpSyntaxTree.ParseText(input, options).GetRoot().DescendantNodes().OfType<ExpressionStatementSyntax>().FirstOrDefault()?.Expression;
Console.WriteLine(EvaluateExpression(expression));
}
Complex EvaluateExpression(ExpressionSyntax expr)
{
if (expr is BinaryExpressionSyntax)
{
var binExpr = (BinaryExpressionSyntax)expr;
var left = EvaluateExpression(binExpr.Left);
var right = EvaluateExpression(binExpr.Right);
switch (binExpr.OperatorToken.ValueText)
{
case "+":
return left + right;
case "-":
return left - right;
case "*":
return left * right;
case "/":
return left / right;
default:
throw new NotSupportedException(binExpr.OperatorToken.ValueText);
}
}
else if (expr is IdentifierNameSyntax)
{
return GetValue(((IdentifierNameSyntax)expr).Identifier.ValueText);
}
else if (expr is LiteralExpressionSyntax)
{
var value = ((LiteralExpressionSyntax)expr).Token.Value;
return float.Parse(value.ToString());
}
else if (expr is InvocationExpressionSyntax)
{
var invocExpr = (InvocationExpressionSyntax)expr;
var args = invocExpr.ArgumentList.Arguments.Select(arg => EvaluateExpression(arg.Expression)).ToArray();
return Call(((IdentifierNameSyntax)invocExpr.Expression).Identifier.ValueText, args);
}
else
throw new NotSupportedException(expr.GetType().Name);
}
Complex Call(string identifier, Complex[] args)
{
switch (identifier.ToLower())
{
case "sin":
return Complex.Sin(args[0]);
default:
throw new NotImplementedException(identifier);
}
}
Complex GetValue(string identifier)
{
switch (identifier)
{
case "x":
return new Complex(1, 0);
case "y":
return new Complex(0, 1);
default:
throw new ArgumentException("Identifier not found", nameof(identifier));
}
}