忠实地处理漂亮打印机中的空白间距
Faithfully handle white-spacing in a pretty-printer
我正在为一种语言编写前端(通过 ocamllex
和 ocamlyacc
)。
因此前端可以从程序构建 Abstract Syntax Tree (AST)
。然后我们经常写一个漂亮的打印机,它接受一个 AST 并打印一个程序。如果以后我们只是想编译或分析 AST,大多数时候,我们并不需要打印出的程序与原始程序完全相同,就白-间距。然而,这一次,我想写一个漂亮的打印机,打印 完全 与原始程序相同的程序,就空白而言。
因此,我的问题是在尽量不修改太多 AST 类型的同时处理空白的最佳做法是什么。我真的不想为 AST 中的每种类型添加一个数字(空格)。
例如,这就是我目前在 lexer.mll
中处理(即跳过)空白的方式:
rule token = parse
...
| [' ' '\t'] { token lexbuf } (* skip blanks *)
| eof { EOF }
有谁知道如何更改它以及前端的其他部分以正确考虑空白间距以便稍后打印?
为每个标记保留源文件位置信息是很常见的。例如,此信息允许更准确的错误。
最通用的做法是保留每个token的起止行号和列位置,一共是四个数字。如果很容易从令牌的值和开始位置计算出令牌的结束位置,则可以将其减少为两个数字,但代价是额外的代码复杂性。
Bison 有一些功能可以简化记住位置对象的簿记工作; ocamlyacc 可能包含类似的功能,但我没有在文档中看到任何内容。在任何情况下,维护与每个输入标记关联的位置对象都是直截了当的。
有了这些信息,就可以很容易地重新创建两个相邻标记之间的空格,只要分隔标记的是空格即可。评论是另一个问题。
这是一个判断调用,它是否比在词法分析时将前面的空格(甚至注释)附加到每个标记更简单。
您可以使用匹配语句打印不同数量的 space,具体取决于您处理的令牌。如果令牌是:id,num,define statement, assign(=)
,我通常会在之前打印 1 space
如果标记是一个算术表达式,我会在它之前打印一个 space,在它之后打印一个 space。
如果您正在处理 if 或 while 语句,我会将正文缩进四 spaces。
我认为最好的办法是编写一个 pretty_print 函数,例如:
let rec pretty_print pos ast =
match ast with
|Some_token -> String.make pos ' '; (* adds 'pos' number of spaces; pos will start off as zero. *)
print_string "Some_token";
|Other_token...
总而言之,我将通过在递归函数中单独匹配每个标记来处理白色 spaces,并打印出适当数量的 spaces。
我正在为一种语言编写前端(通过 ocamllex
和 ocamlyacc
)。
因此前端可以从程序构建 Abstract Syntax Tree (AST)
。然后我们经常写一个漂亮的打印机,它接受一个 AST 并打印一个程序。如果以后我们只是想编译或分析 AST,大多数时候,我们并不需要打印出的程序与原始程序完全相同,就白-间距。然而,这一次,我想写一个漂亮的打印机,打印 完全 与原始程序相同的程序,就空白而言。
因此,我的问题是在尽量不修改太多 AST 类型的同时处理空白的最佳做法是什么。我真的不想为 AST 中的每种类型添加一个数字(空格)。
例如,这就是我目前在 lexer.mll
中处理(即跳过)空白的方式:
rule token = parse
...
| [' ' '\t'] { token lexbuf } (* skip blanks *)
| eof { EOF }
有谁知道如何更改它以及前端的其他部分以正确考虑空白间距以便稍后打印?
为每个标记保留源文件位置信息是很常见的。例如,此信息允许更准确的错误。
最通用的做法是保留每个token的起止行号和列位置,一共是四个数字。如果很容易从令牌的值和开始位置计算出令牌的结束位置,则可以将其减少为两个数字,但代价是额外的代码复杂性。
Bison 有一些功能可以简化记住位置对象的簿记工作; ocamlyacc 可能包含类似的功能,但我没有在文档中看到任何内容。在任何情况下,维护与每个输入标记关联的位置对象都是直截了当的。
有了这些信息,就可以很容易地重新创建两个相邻标记之间的空格,只要分隔标记的是空格即可。评论是另一个问题。
这是一个判断调用,它是否比在词法分析时将前面的空格(甚至注释)附加到每个标记更简单。
您可以使用匹配语句打印不同数量的 space,具体取决于您处理的令牌。如果令牌是:id,num,define statement, assign(=)
,我通常会在之前打印 1 space如果标记是一个算术表达式,我会在它之前打印一个 space,在它之后打印一个 space。
如果您正在处理 if 或 while 语句,我会将正文缩进四 spaces。
我认为最好的办法是编写一个 pretty_print 函数,例如:
let rec pretty_print pos ast =
match ast with
|Some_token -> String.make pos ' '; (* adds 'pos' number of spaces; pos will start off as zero. *)
print_string "Some_token";
|Other_token...
总而言之,我将通过在递归函数中单独匹配每个标记来处理白色 spaces,并打印出适当数量的 spaces。