Skip to content
许兴逸 edited this page Sep 29, 2021 · 9 revisions

CodeGen可以获取到的中间代码

CodeGen可以获取的中间代码使用YukimiScript.Parser.Dom.Dom类型包装,其中仅有Scenes成员有实际意义,其它成员均可丢弃。
Scene成员包含了所有场景的定义,其中SceneDefination部分是关于场景定义本身的内容,Block是当前场景中已经经过编译的代码块,DebugInformation则是调试信息。
其中对于Block的部分,其Operation的部分中,只可能出现CommandCallEmptyLine两种可能,不可能出现其他情况。
而对于CommandCallCallee只可能是外部定义的命令,而UnnamedArgs部分按顺序排列好了传入的所有参数,和外部定义命令中的参数列表中的每个形参顺序严格对应,并已经预先将使用的默认值参数传入,而NamedArgs成员应当为空列表,不需要对其进行处理。

编译阶段

加载Libs和要被编译的文件

编译前将会使用System.IO.File.ReadAllLines函数按行读取所有要编译的文件和--lib参数中定义的库。

按行解析

对加载的每个文件,执行YukimiScript.Parser.Parser.parseLines: string[] -> Result<Parsed list, (int * exn) list>

如果解析成功,将会返回Result.Ok (Parsed list),其中Parsed是单行解析结果,包含了解析结果YukimiScript.Elements.Line,和这一行的注释。

如果解析失败,将会返回Result.Error ((int * exn) list),这是一个异常列表,其中int的部分为此异常发生的行号,exn则是此行发生的异常,异常请参见后文“异常处理”。

产生DOM

对于加载的每个文件,如果上一步解析成功,则应该可以得到一个Parsed list,之后需要将其组织为具有层级结构的DOM。
可以使用YukimiScript.Parser.Dom.analyze: fileName: string -> parsed: Parsed seq -> Result<Dom>来生成当前文件的DOM。
其中fileName参数用于为DOM附加调试信息,parsed则是上一步生成的按行解析的解析结果,之后返回一个Result<Dom>

合并Libs

对于从--lib加载的文件,需要对这些文件进行合并,以方便后续在其中查找外部定义和宏。
YukimiScript.Parser.Dom.merge: Dom -> Dom -> Dom可用于合并两个Dom,而YukimiScript.Parser.Dom.empty: Dom则是预定义的一个空Dom,可以使用fold操作来将一组Dom合并为一个。

当合并结束后,应当对其进行合法性检查:

当以上合并完成后,再将其合并到当前待编译的DOM中。

展开文本命令

YukimiScript.Parser.Dom.expandTextCommands: Dom -> Dom函数实现了对整个Dom中所有的Scene执行文本命令的展开。

对于每个待编译文件的DOM,需要消除其文本语法,将其变换为命令调用语法,即将所有的YukimiScript.Parser.Elements.TextBlock变换为YukimiScript.Parser.Elements.CommandCall的序列,对于文字语法和命令的对应关系,需要参考“系统外部定义参考”中“文字相关”的部分。
YukimiScript.Parser.Text.toCommands: TextBlock -> CommandCall list可将单个TextBlock变换为一组CommandCall序列,而YukimiScript.Parser.Text.expandTextBlock: TextBlock -> DebugInformation -> Block函数用于在展开的同时为其添加合适的调试信息。

展开用户定义的宏

YukimiScript.Parser.Dom.expandUserMacros: src: Dom -> Dom实现了对整个Dom进行宏展开,由于之前已经将lib合并入待编译的DOM,因此可以查找到lib中的宏。

对于DOM中Scenes中的Block部分,执行YukimiScript.Parser.Macro.expandBlock: (MacroDefination * Block) list -> Block -> Result<(Operation * DebugInformation) list>会在给定的(MacroDefination * Block) list中搜索宏并对Block进行宏展开,它会调用YukimiScript.Parser.Macro.expandSingleOperation: (MacroDefination * Block) list -> Operation -> Result<Block>对单个操作进行宏展开。

对于单个Operation,首先检查它是否为CommandCall,如果不是,则原样输出不做展开,如果是,则执行以下操作:

  1. 使用YukimiScript.Parser.Macro.matchMacro: DebugInformation -> CommandCall -> (MacroDefination * 'a) list -> Result<MacroDefination * 'a * (string * Constant) list>找到一个匹配到的宏,并返回此宏、宏的实现及以键值对构成的参数列表。其中参数匹配是由YukimiScript.Parser.Macro.matchArguments: DebugInformation -> Parameter list -> CommandCall -> Result<(string * Constant> list>函数实现的。
  2. 将宏的实现取出,对其每个Operation,将形式参数替换为实际参数,此操作通过YukimiScript.Parser.Macro.replaceParamToArgs: (string * Constant) list -> CommandCall -> CommandCall函数实现。

之后需要进行合法性检查:

  • 不应当存在重复定义的externs与macros

产生剧情图

此操作由YukimiScript.Parser.Diagram.analyze: (string * Dom) list -> Result<Diagram>)函数实现,其需要传入一组待分析的脚本及其文件名,返回一个Diagram表示的图。
之后由YukimiScript.Parser.Diagram.exportDgml: Diagram -> string函数产生DGML有向图文件。

关于DGML有向图文件,可以参考微软的文档:https://docs.microsoft.com/zh-cn/visualstudio/modeling/directed-graph-markup-language-dgml-reference?view=vs-2019

展开系统宏

此操作由YukimiScript.Parser.Dom.expandSystemMacros: Dom -> Dom函数实现,它将调用YukimiScript.Parser.Macro.expandSystemMacros: Block -> Block函数实现这个操作。

目前它仅仅用于消除__diagram_link_to宏。

链接外部定义

此操作由YukimiScript.Parser.Dom.linkToExternCommands函数实现,它首先会载入系统外部定义,系统外部定义在YukimiScript.Parser.Dom.systemCommands定义。

它将会消除所有由键值对构成的参数,并将其按顺序以匿名参数的方式传入,同时以默认值补全没有传入的参数。

生成目标代码

经过以上处理后,DOM便可提交给CodeGen生成目标代码,关于目标代码请参阅“目标代码”。
可以参考Lua的CodeGen:https://github.com/Strrationalism/YukimiScript/blob/main/YukimiScript.CodeGen.Lua/Lua.fs

异常处理

对于大多数异常,都使用exn类型进行处理,在YukimiScript.ParserTypes.fs文件中定义了YukimiScript.Parser.Result,为F#中Result<'a, exn>类型的缩写。
对于YukimiScript.Parser中产生的大多数异常,可以在YukimiScript.Parser.ErrorStringing模块中产生一个可读的字符串说明。

Clone this wiki locally