Skip to content
许兴逸 edited this page Oct 12, 2021 · 9 revisions

编译阶段

CodeGen最终可以获取一个中间表示,它的类型是YukimiScript.Parser.Intermediate

加载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有向图文件,或者使用YukimiScript.Parser.Diagram.exportMermaid: Diagram -> string生成Mermaid Flowchart图文件。

关于DGML有向图文件,可以参考微软的文档:https://docs.microsoft.com/zh-cn/visualstudio/modeling/directed-graph-markup-language-dgml-reference?view=vs-2019
生成的Mermaid Flowchart图,可以嵌入到Markdown文档中,关于Mermaid,请查看:https://github.com/mermaid-js/mermaid

展开系统宏

此操作由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阶段不需要的信息,只保留场景定义信息和对外部定义的调用,最终产生一个YukimiScript.Parser.Intermediate

生成目标代码

经过以上处理后,Intermediate便可提交给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模块中产生一个可读的字符串说明。