我如何在 dotnet 核心上创建自己的 DSL,有框架吗?

How do i create my own DSL on dotnet core, are there frameworks?

抱歉,如果我的标题偏离了,但我不知道从哪里开始。

我想出了一个小格式,允许我在 json 文件中写一些小步骤。知道azure resource templates的人,深受其启发。

{ "steps": [ { "command": "mpc-info -i [laz input directory] -c [number processes]", "outputs": { "AABB": "[split(regex(stdout,\"\('AABB: ', (.*?)\)\",))]" } } ] }

如果我想为文档中的那些“[]”字符串编写自己的解析器,我应该从哪里开始?

目标是让我可以轻松添加可用于编写表达式的新函数/变量。我想在 dotnet core netstandard 2.0 上的 c# 代码 运行ning 中定义所有这些。

因此在上面的特定实例中,主机程序将 运行 命令并在标准输出上产生一些输出。我现在需要我的代码来解析输出字符串 [split(regex(stdout,\"\('AABB: ', (.*?)\)\",))] 并将其转换为代码,该代码将 运行 正则表达式匹配变量 stdout 和 return 第一个捕获组作为将创建数组的拆分函数的参数 object 的项目并在末尾替换 JToken object.

中的字符串

关于阅读内容的任何指示或可以启动它的示例代码。

我的天真方法只是编写一些笨拙的代码来进行一些搜索、替换和解决这个小示例,但是如果我想用更多功能等改进我的小框架怎么办。

我从哪里开始?

在 Twitter 朋友的一些帮助下,我设法使用 Sprache 解决了这个问题。

stdout.txt

Completed 100.00%!()
('AABB: ', 480000, 6150000, -1580, 530000, 6200000, 755)
('#Points:', 20517377941)
('Average density [pts / m2]:', 8.2069511764)
('Suggested number of tiles: ', 16.0)
('Suggested Potree-OctTree CAABB: ', 480000, 6150000, -1580, 530000, 6200000, 48420)
('Suggested Potree-OctTree spacing: ', 205.0)
('Suggested Potree-OctTree number of levels: ', 11)
Suggested potreeconverter command:
$(which PotreeConverter) -o <potree output directory> -l 11 -s 205 --CAABB "480000 6150000 -1580 530000 6200000 48420" --output-format LAZ -i <laz input directory>
Finished in 7.63 seconds

以及展示其工作原理的单元测试

[TestMethod]
public void TestMethod1()
{

    var parser = new ExpressionParser();
    parser.Functions["add"] = (arguments) => 
        arguments.Aggregate(0.0, (acc, argument) => acc + argument.ToObject<double>());

    parser.Functions["split"] = (arguments) => JArray.FromObject(arguments.Single().ToString().Split(","));
    parser.Functions["regex"] = RegexFunc;

    Assert.AreEqual(4.0, parser.Evaluate("[add(2,2)]"));
    Assert.AreEqual(7.0, parser.Evaluate("[add(2,2,3)]"));
    Assert.AreEqual(3.0, parser.Evaluate("[add(2,2,-1)]"));
    Assert.AreEqual(4.0, parser.Evaluate("[add(2,2,0,0)]"));

    var stdout = File.ReadAllText("stdout.txt");

    var test = parser.Evaluate("[split(regex(\"testfoo\",\"test(.*)\",\"\"))]");

    Assert.AreEqual("[\"foo\"]",test.ToString( Newtonsoft.Json.Formatting.None));


    parser.Functions["stdout"] = (arguments) => stdout;
    parser.Functions["numbers"] = (arguments) => new JArray(arguments.SelectMany(c => c).Select(c => double.Parse(c.ToString())));

    var AABB = parser.Evaluate("[numbers(split(regex(stdout(2),\"\('AABB: ', (.*?)\)\",\"\")))]");

    CollectionAssert.AreEqual(new[] { 480000, 6150000, -1580, 530000, 6200000, 755 }, AABB.ToObject<int[]>());
}

和实际实施

public class ConstantEvaluator : IJTokenEvaluator
{
    private string k;

    public ConstantEvaluator(string k)
    {
        this.k = k;
    }

    public JToken Evaluate()
    {
        return JToken.Parse(k);
    }
}
public class DecimalConstantEvaluator : IJTokenEvaluator
{
    private decimal @decimal;

    public DecimalConstantEvaluator(decimal @decimal)
    {
        this.@decimal = @decimal;
    }

    public JToken Evaluate()
    {
        return JToken.FromObject(@decimal);
    }
}
public class StringConstantEvaluator : IJTokenEvaluator
{
    private string text;

    public StringConstantEvaluator(string text)
    {
        this.text = text;
    }

    public JToken Evaluate()
    {
        return text;
    }
}
public interface IJTokenEvaluator
{
    JToken Evaluate();
}
public class FunctionEvaluator : IJTokenEvaluator
{
    private string name;
    private IJTokenEvaluator[] parameters;
    private ExpressionParser evaluator;
    public FunctionEvaluator(ExpressionParser evaluator, string name, IJTokenEvaluator[] parameters)
    {
        this.name = name;
        this.parameters = parameters;
        this.evaluator = evaluator;
    }

    public JToken Evaluate()
    {
        return evaluator.Evaluate(name, parameters.Select(p => p.Evaluate()).ToArray());
    }


}

public class ExpressionParser
{
    public readonly Parser<IJTokenEvaluator> Function;
    public readonly Parser<IJTokenEvaluator> Constant;

    private static readonly Parser<char> DoubleQuote = Parse.Char('"');
    private static readonly Parser<char> Backslash = Parse.Char('\');

    private static readonly Parser<char> QdText =
        Parse.AnyChar.Except(DoubleQuote);
    private static readonly Parser<char> QuotedPair =
        from _ in Backslash
        from c in Parse.AnyChar
        select c;

    private static readonly Parser<StringConstantEvaluator> QuotedString =
        from open in DoubleQuote
        from text in QdText.Many().Text()
        from close in DoubleQuote
        select new StringConstantEvaluator(text);

    public Dictionary<string, Func<JToken[], JToken>> Functions { get; set; } = new Dictionary<string, Func<JToken[], JToken>>();

    private readonly Parser<IJTokenEvaluator> Number = from op in Parse.Optional(Parse.Char('-').Token())
                                                       from num in Parse.Decimal
                                                       from trailingSpaces in Parse.Char(' ').Many()
                                                       select new DecimalConstantEvaluator(decimal.Parse(num) * (op.IsDefined ? -1 : 1));
    public ExpressionParser()
    {
        Function = from name in Parse.Letter.AtLeastOnce().Text()
                   from lparen in Parse.Char('(')
                   from expr in Parse.Ref(() => Function.Or(Number).Or(QuotedString).Or(Constant)).DelimitedBy(Parse.Char(',').Token())
                   from rparen in Parse.Char(')')
                   select CallFunction(name, expr.ToArray());

        Constant = Parse.LetterOrDigit.AtLeastOnce().Text().Select(k => new ConstantEvaluator(k));
    }

    public JToken Evaluate(string name, params JToken[] arguments)
    {
        return Functions[name](arguments);

    }

    IJTokenEvaluator CallFunction(string name, IJTokenEvaluator[] parameters)
    {
        return new FunctionEvaluator(this, name, parameters);
    }

    public JToken Evaluate(string str)
    {
        var stringParser = //Apparently the name 'string' was taken...
             from first in Parse.Char('[')
             from text in this.Function
             from last in Parse.Char(']')
             select text;



        var func = stringParser.Parse(str);

        return func.Evaluate();
    }



}