记录令牌及其在前端之外使用它们的位置

Record tokens and their position to use them outside the front-end

我想写一个特定语言的小美化器。在美化器中,我们将能够缩进一行或几行(即,在每行的左侧添加空格);我们还将能够格式化整个代码(即,在适当的位置更改空格和换行符)。

给定一个程序,我的 ocamllexocamlyacc 的前端可以构建一个 Abstract Syntax Tree (AST):

(* in main.ml *)
let f = open_in file in
let buf = Lexing.from_channel f in
let ast = Parser.main Lexer.token buf in
analyse ast
...

我更熟悉使用 AST 来分析、编译和打印(不完全相同)程序。不过看来还是得直接在tokens上下功夫才能写出好的美化器。但是我不知道如何在前端之外操作令牌。

比如在解析的时候在某处记录token及其位置,这样我们在前端之外仍然可以使用它们是不是很常见?例如,我们可能会逐一遍历这条记录中的标记,并打印完全相同的程序(包括完全相同的空格)?

有人有任何代码片段吗?

编辑 1: 以下是一些在 lexbuf 运行时 上使用 Lexing.lexeme_start_p 的示例。但是,我想知道的是人们是否以及如何在外部(或之后)解析这些信息?例如, 之外(或之后)解析,我们如何从某个位置获取令牌?

  (* in main.ml *)
  let ast = try Parser.main Lexer.token buf with
    | Lexer.Lexing_error e ->
      let pos = Lexing.lexeme_start_p buf in
      let l = pos.pos_lnum in
      let c = pos.pos_cnum - pos.pos_bol + 1 in
      pffo "File \"%s\", line %d, characters %d-%d:\n" file l (c-1) c
      pffo "Unexpected exception, parser top : lexical analysis > %s@." e;
      exit 1
    ...

 (* in lexer.mll *)
 rule token = parse 
   ...
   | "'" '\' (_ as c)
     { let msg = Printf.sprintf "illegal escape sequence \%c" c in
       let p = Lexing.lexeme_start_p lexbuf in
       raise (Lexical_error (msg, p.Lexing.pos_fname, p.Lexing.pos_lnum, 
              p.Lexing.pos_cnum - p.Lexing.pos_bol + 1)) }

用令牌保持令牌位置在实际编程语言实现中很常见。

按原样打印出部分输入代码的最简单方法是将输入文本保留在某处,然后使用标记位置提取您想要的部分。从标记流中重建文本及其适当插入白色 spaces 的位置很难实现,而且恐怕很容易出错,而且当你的词法分析器忽略非白色 space 之类的评论时,这是不可能的。

可以在 OCaml 编译器实现中找到这样一个按原样打印输入代码的示例。例如 Location.highlight_dumb 尝试使用带有输入文本的词法分析器的 lex_buffer 字段打印围绕错误的代码,尽管有时这是不可能的,因为 lex_buffer 不会保留整个输入。