在nim中匹配

2021-03-11 14:32:51

Fusion包含inim模块,该模块是指在stdlib的延伸视为extent.clow中,以下模块存在:

使用NIM宏的融合/匹配 - 模式匹配实现 - 本文的主要重点

要安装融合,只需运行敏捷安装融合。在没有安装的情况下尝试熄灭,请使用Nim Playground。

导入融合/匹配{。实验:" Casestmtmacros" 。}案例[(1,3),(3,4)]:[(1,@ a),_]:echo e ex:echo"匹配失败"

新的模式匹配库介绍了两个非常有用的概念的支持:模式匹配和对象破坏性。

模式匹配是一种机制,允许您检查特定对象反对的模式 - 您可以将其思考主要作为一种方法来减少样板代码,当时,检查一个值是否在范围内,检查Apartular键是否存在表等。

导入STD / JSON,融合/匹配{。实验:" Casestmtmacros" 。}#json是非常简单的数据格式,但是说明了很多有用的功能模式匹配案例parsejson(""" {" key":"价值"}"""):#不再需要检查键是否存在 - 它是自动完成的{" key" :Jint()}:丢弃#从嵌套数据结构中提取值也变得更加容易。 {" key" :(getstr:@ val)}:echo val assert val是字符串

对象破坏性允许您从对象中的特定字段中提取值.it在Python等动态编程语言中非常常见.NIM - Tuple Unpacking已经支持最简单的破坏性形式:

[(@ first,@秒),所有@ trail]:= [(12,3),(33,4),(12,33)]回声首先,&#34 ;," ,第二,&#34 ;," , 踪迹

模式匹配的主要目的是简化条件和Checks。与对象变体配对时尤其有用,但也可以用于执行大量其他内容,例如键值对匹配和序列匹配的广泛支持。为非常常见的用例提供了一种特殊的句法糖,例如选项[t]检查(类似于rust中的话):

和匹配的案例对象(如AST)的树结构。符合Nep1样式指南约定的枚举,您可以完全省略前缀,导致代码看起来像这样:

案例<一些AST节点> :#节点种类是“nnkident`”,但可以省略“NNK”(Strval:@名称):Echo"找到标识:" ,名称#从infix表达式中提取子节点。 InfixExpr [Ident(strval:" +"),@ lhs,@ rhs]:...#匹配与*完全*两个分支的语句。 #无需担心索引例外 - 在模式匹配期间相应地检查了“LEN”#。 IFSTMT [ELIFBRANCH [@ COND1,_],ELIFBRANCH [@ COND2,_]]:...

NIM宏是语言中最强大的部位之一,但它们可能会对新人感到有点恐吓,特别是当涉及到实施手头的特定问题的宏时。

本文介绍了一个示例,就如何轻松地创建一个相对复杂的蜂映射新的模式匹配库。

我们将为DataFlow编程创建一个宏,并支持某些操作,从STD / SEQUTILS模块(地图/过滤器/每)。宏将不会涵盖所有可能的组合和用例,因为它将实现更加复杂。

编写宏时,只需编写DSL代码(仿佛已经宏)和您希望生成的内容非常有用。在我们的情况下,您的情况,输入可以看起来很粗糙:

流线(" / etc / passwd"):地图[_,seq [string]]:它.split(":")Keepif:它.Len> 1和它.matches [_.startswith(" systemd"),._]每个:回声

var res = seq [restype]在线(" / etc / passwd"):让它= IT0。分裂(":")如果它1。 len> 1和IT1。匹配[_.startswith(" systemd"),._]:回声IT1

现在问题是 - 如何将第一个转换为第二个?我们将首先通过首先查看流宏上的DumpTree的输出:

1 stmtlist 2命令3 ident" flow" 4拨打5 ident"线条" 6 strlit" / etc / passwd" 7 stmtlist 8致电9 bracketexpr 10 ident"地图" 11 ident" _" 12 Bracketexpr 13 Idend" SEQ" 14 ident" string" 15 stmtlist 16致电17 dotexpr 18 ident"它" 19 ident" split" 20 strlit":"

这一文本的负荷可能似乎一点令人困惑,但最终它可以很容易地分开(这正是我们将要做的)。首先,在第3行,我们看到流量标识符(Ident&#34 ; Flow") - 这是我们的宏的开始。然后,在下一行是一条线(" / etc / passwd")的论点。第7-20行的StmtList是流程宏的实际正文 - 地图部分等。我们将稍后进入其内部结构。

在我们有一个粗略的输入AST概述之后,是时候决定如何实现这个分子宏。

我经常试图为DSL介绍某种中间表示,以使事情从代码生成中更加组织和解析解析阶段。这可能会更长,但更可扩展和强大。您可以毫无疑问,毫无疑问只需直接到代码生成,但对于更复杂的公司,我仍然会建议使用某种IR。

在该特定情况下,可以描述流宏的DSL结构:

键入flowstagekind = enum fskmap#阶段for元素转换fskfilter#筛选元素fskeach#执行操作而不返回值flowstage =对象outputtype:选项[nimnode] #ssuert结果类型类型:舞台主体的流动性#键

它直接映射到输入DSL上。映射应该创建一个FSKMAP阶段,过滤器创建FSKFilter等。因此,您可以指定这样的输出类型:地图[预期的输出]。宏将在两个阶段工作:首先,首先将输入表示形式转换为中间表示,然后它会产生结果的AST。

现在,在我们对我们想要做的良好了解之后 - 这个问题是'怎么样?'。那就是融合/匹配特别方便的地方 - 我们已经识别了所有模式,现在它只是一个在代码中写下这个问题。

没有模式匹配,您将留下长一系列重复[0] [0] [0],如果种类== nnkbracketexpr,以便从DSL检索部分并验证输入。

在我们继续为整个DSL编写模式之前,重要的是要考虑写入阶段的三种可能的情况。第一个一次非常简单 - 没有指定类型,只有舞台标识符:

但是可以使用两种不同的方式编写一个具有类型参数的单个阶段 - 缺两种句法正确,但有不同的解析树:

差异是由于方法调用语法 - 映射[a]被视为括号表达式(如阵列下标),但是将映射[a]被解析为过程映射调用,具有参数[a](将数组传递到上函数)。

让我们制作一个小型题名,以便更好地了解新的图案匹配库如何帮助我们这里。

我们将专注于与我们的任务相关的零件 - 有关更多详细信息,请阅读文档。

写入NIM宏时,您主要处理Nimnode对象 - 首先ToproCess输入AST,然后生成新代码。AST由案例对象组成。通常,宏的第一部分涉及正确的节点类型的大量检查,然后在子节点上进行迭代以提取输入数据.Pattern匹配简化了这一点,允许直接写入AST的预期模式,并与Dumptree的语法密切相关。

例如 - 如果我们有像Map [String]这样的代码,它有以下树表示:

请注意AST和匹配模式之间的相似性 - 每个节点都有各种字段,它描述了这是什么样的节点。在这种情况下,我们对BrackEtexProde - 流级类型和类型参数的第一和第二子节点感兴趣。

正如我们之前已经见过的那样,PAP [String]和Map [String]被解析使用 - 第一个被处理为传递给映射为functionargument的一个元素数组,第二个是括号表达式。方法调用语法使编程DSL一点更难 - 您需要检查渗透蛋白,请记住每个捕获的索引是否应等。

使用模式匹配虽然它变得相当容易 - 添加了替代替代。

也可以完全从DSL省略类型参数 - 它们是很好的,并且可以允许更好的类型检查,但可能会变得非常烦人。所以,我们还应该指望某人只是写入地图 - 没有任何类型的资格。要处理这种情况,我们为模式添加第三个替代方案:

这带来了一个重要的变化:typeparam捕获不再是nimnode - typehas更改为选项[nimnode],因为并非所有替代品都具有此变量。头仍然是一个亮度,就像以前一样 - 所有可能的替代方案,所以如果输入匹配,则会设置它。

此示例显示了模式匹配如何帮助处理差异替换术语。此其他功能非常强大的功能是序列匹配 - 可悲的是在此验证中,我们无需它,但我决定仍然展示它.Consider一个程序声明ast - 假设我们需要要匹配名称,参数和返回type.usually,案例语句的一部分将类似于此图片:

nnkprocdef:让名称= arg [0]让returntype = arg [3] [0]让参数= arg [3] [1 .. ^ 1]

我们的第一阶段将处理输入到流程中。我们已经有一种方法来从输入AST中提取数据 - 使用模式匹配。

宏观流量(arg,body:untyped):untyped = var阶段:身体elem的seq [flowstage]:如果是elem。匹配(调用[bracketexpr [@ ident,opt @ outtype],@ body] | #`map [string]:`command [@ ident是ident(),bracket [outtype],@ body] | #`map [string ]:“调用[@ ident是ident(),@ body]#只是`地图:`,没有类型参数):阶段。添加FlowStage(类型:identTokind(ident),OutputType:outtype,Body:Body)

之后,我们拥有生成结果代码的所有必要信息。如果上阶段不是每个阶段,则即,每次迭代后都有返回值,我们需要确定结果序列的类型,然后追加迭代。

如果阶段[^ 1]。依赖{fskeach}:#如果上阶段有返回类型(不是`每个`)那么我们需要#累积临时变量的结果。结果=引用do:var` reven`:seq [## [表达式]#]为IT0 {。注射。}在`arg`:`stream`。添加#[表达式来评估] #` revent` else:#否则只需迭代每个元素结果=引用执行:对于IT0 {。注入。在`arg`:#[表达式]}

DataFlow的每个阶段都有一种类型,并且可能定义变量。除此之外 - 每个阶段使用它的特殊变量 - 必须为每个阶段单独注入,但同时它在阶段之间使用暂停值。

var res:seq [#[the表达式]#]在[1,2,3]中:让它=它* 2让它= $ IT RES。添加它

正如您可以清楚地看到的,此类代码甚至不会因重新定义而编译。解决此问题的两种可能方法 - 那种明显的方式,而不是全部显而易见的.Let从第一个 - 自每个变量开始可以在Newscope中重新定义我们可以做到:

因为它在[1,2,3]中:阻止:让它=它* 2块:让它= $ IT echo"添加结果 - " , 它

它会非常精细地编译和工作。但是现在我们有一个问题获取表达式的类型 - 只要你只使用地图 - 毕竟块:是表达式,我们可以拥有这样的东西:

echo typeof((块:让它= 1块:让它=它* 2块:$ it))

所有方法都不是世界上最漂亮的代码 - 但是当我们必须处理过滤器,每个注入的变量和迭代器时,它将变得更糟糕。

第二种替代方法是使用Auto Return类型和Assissige表达式的结果声明。在这种情况下,编译器将弄清楚我们的返回类型。

proc hello [t](a:t):auto = f for c" ee" :结果=(12," SOM"," ee" a)echo typeof hello [int]

现在,我们只需要编写#[表达式以评估]#和asbstitutitute结果=必要时的代码生成。

流中的每个阶段都会注入它变量 - 从前一个阶段的评估结果。避免从多个允许重新定义错误=它=<表达式> oneach阶段,我们将用它替换它的每次出现它<阶段索引>。对于第一阶段,它将是它 - > IT1,第二个是它 - > IT2等等。

重写占用输入nimnode,要么返回它 - 是(如果不需要重写),或者,如果是标识符IT(strval:"它")),将其转换为一个纽音相应的索引。

proc重写(node:nimnode,idx:int):nimnode =案例节点:ident(strval:"它"):结果= ident("它"& $ idx) (种类:在nnktokenkinds):#`nktokenkinds`是一组Nod#39; t有子节点的节点#种。 #这些在没有任何#修改的情况下返回。结果=节点其他:#对于带子节点的节点种类,必须完成重写#递归结果=节点中子N的NewTree(node。种类型):结果。添加subn。重写(IDX)

对于每个阶段,我们重写身体,然后将生成的CodeTo的新块追加。

func evalexpromstages(阶段:seq [flowstage]):nimnode =结果= idx的newstmtlist(),阶段阶段:#rewrite身体让body =阶段。身体 。重写(IDX)案例阶段。同类:#如果阶段是一个过滤器,它被转换为`如果注入了`和新的新变量。 FSKFilter:结果。添加报价DO:让stexok =((`body`))如果不是stexok:继续fskeach:#`每个`没有变量或特殊格式 - 只是#重写身体并粘贴回到结果代码结果。添加FSKMAP的主体:#为注入节点创建新标识符,并为其分配“身体”的#结果。让ITID = ident("它" $(IDX + 1))结果。添加报价DO:让`ITID` =`正文`#如果需要明确检查舞台的输出类型#创建类型断言。如果有些(@ exptype)?=阶段。 OutputType:结果。添加maketypeassert(Exptype,阶段。身体,ITID)

Func typeexprofstages(阶段:seq [flowstage],arg:nimnode):nimnode = let evalexpr = evalexpromstages(阶段)var restuple = nnkpar。 Newtree(Ident" IT0")对于IDX,阶段阶段:如果是ST。善意{fskfilter}:重新删除。添加ident("它" $(idx + 1))letid = newlit(阶段。len-1)结果= quote do:block :( proc():auto = #` auto` annotation允许从#proc Body中的任何分配中派生Proc的类型#,我们利用此方法,#并避免手动构建类型表达式#。对于IT0 {。注入。在`arg`:`semarexpr`结果=` Resuple`#^^^^^^^^^^^^^^^ ^^^^^^^ |#|返回的#类型将来自此分配。#即使它被放置在循环体内,它仍然是#派生必需的返回类型)()[lastid`]#^^^^^^^^^^^^^^^ | | #|从proc返回类型#|获取最后一个元素#| #dive proc宣布我们立即称呼它

宏观流量(arg,body:untyped):untyped = var阶段:身体elem的seq [flowstage]:如果是elem。匹配(调用[bracketexpr [@ ident,opt @ outtype],@ body] | #`map [string]:`command [@ ident是ident(),bracket [outtype],@ body] | #`map [string ]:“调用[@ ident是ident(),@ body]#只是`地图:`,没有类型参数):阶段。添加FlowStage(种类:identTokind(ident),OutputType:Outtype,Body:Body)Let evalexpr = AsgexProMstages(阶段)如果阶段[^ 1]。依赖{fskeach}:#如果上阶段有返回类型(不是`每个`)那么我们需要#累积临时变量的结果。让Resexpr = typeexpromstages(阶段,arg)Letid = Ident("它" $ amp; $阶段。Len)让Resid = ident(" res")结果= quote do:var` resid`:seq [typeof(`resexpr`)]对于IT0 {。注射。}在`arg`:`评价中,'respexpr`` resid`。添加`lastid`` star` else:结果= quote do:for it0 {。注入。}在`arg`:`evalexpr`结果= newblockstmt(结果)

让Res =流线(" / etc / passwd"):地图[seq [string]]:它。分裂(":")过滤器:让shell =它[^ 1]它。 len> 1和壳。 endswith(" bash")地图:shell

让res = block:var res:seq [typeof(block:#gentley重复的代码,用于获取表达式的类型。proc():auto = for IT0,在线(" / etc / passwd"):让IT1 = Split(IT0,":" - 1)让stageok`Gensym10271 = Let Shell = IT1 [BackwardIndex(1)] 1< len(IT1)和endswith(shell," Bash")如果不是steionok` gensym10271:继续让它het3 = shell结果=(it0,IT1,IT3)()[2])]#IT0中的实际实现(" / etc / passwd&#34 ;):让它=拆分(IT0,":" - 1)让stageok`gensym10267 = Let Shell = IT1 [BackwardIndex(1)] 1< Len(IT1)和endswith(shell," bash")如果不是stexok` gensym10267:继续让它3 = shell add(res,it3)res

这里可以看到全流程宏实现 - 是图书馆测试套件的一部分,但仍然存在从文章中的大量评论。

我试图用一个方式编写测试套件,这也可以更容易地用作榜样,而在大多数情况下它没有这样的实现评论,它仍然可以将其视为如何使用的示例图书馆。

该库仍在开发出来 - 可以预期一些小错误和不一致性,也可以作为符合人体工程学的改进。将来,一些内部实施细节(例如捕获的变量的可变性)可以在未来改变。当查看类型的实施时,可以改变实现的功能,捕获将使用不可变视图完成。

我个人将此库视为一个踏脚石,用于在NIM核心中添加模式匹配支持 - 谢谢,以无与伦比的成分计算能力,甚至可以在包括在语言本身的情况下在externallibraries中测试(而不是制作几乎不可逆转的附加物和处理回退/糟糕的设计选择以后)。这意味着,首先,欢迎DSL可用性和人体工程学的反馈,以及关于似乎不粉刺的部分的assdussions

......