如何使用“using static”指令动态生成代码?

时间:2016-07-11 11:58:34

标签: c# roslyn-code-analysis

我想让用户尽可能自然地输入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指令动态生成代码?

1 个答案:

答案 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));
    }
}