代码之家  ›  专栏  ›  技术社区  ›  Poul K. Sørensen

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

  •  1
  • Poul K. Sørensen  · 技术社区  · 6 年前

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

    我提出了一种小格式,允许我在json文件中编写一些小步骤。了解azure资源模板的人,其灵感来自于此。

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

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

    目标是让我可以轻松地添加可用于编写表达式的新函数/变量。我想用运行在dotnet核心netstandard2.0上的c代码来定义所有这些。

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

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

    我的naiv方法只是编写一些studid代码来进行一些搜索、替换和解决这个小示例,但是如果我想用更多的函数等来改进我的小框架,该怎么办呢。

    我从哪里开始?

    1 回复  |  直到 6 年前
        1
  •  1
  •   Poul K. Sørensen    6 年前

    在推特朋友的帮助下,我用Sprache解决了这个问题。

    标准输出。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();
        }
    
    
    
    }