如何将“使用静态”指令用于动态生成的代码

本文关键字:动态 用于 指令 代码 使用静态 静态 | 更新日期: 2023-09-27 17:56:14

我想让用户尽可能自然地输入xy的数学表达式。例如,与其键入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指令用于动态生成的代码?

如何将“使用静态”指令用于动态生成的代码

您可以向 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));
    }
}