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

时间:2018-01-24 22:24:30

标签: c# dsl

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

我提出了一个小格式,允许我在json文件中写一些小步骤。人们都知道天蓝色的资源模板,它的灵感来自于此。

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

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

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

因此,在上面的特定实例中,宿主程序将运行该命令并在stdout上生成一些输出。我现在需要我的代码来解析输出字符串 [split(regex(stdout,\"\\('AABB: ', (.*?)\\)\",$1))]并将其转换为将在变量stdout上运行Regex匹配的代码,并将第一个捕获组作为参数返回到split函数,该函数将创建项的数组对象并在结尾处替换字符串JToken对象。

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

我的naiv approch只会编写一些代码,可以进行一些搜索和替换并解决这个小例子,但是如果我想用更多的函数来改进我的小框架等等。

我从哪里开始?

1 个答案:

答案 0 :(得分:1)

在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(.*)\",\"$1\"))]");

    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: ', (.*?)\\)\",\"$1\")))]");

    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();
    }



}