我如何在 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();
}
}
抱歉,如果我的标题偏离了,但我不知道从哪里开始。
我想出了一个小格式,允许我在 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();
}
}