【问题标题】:Certain AST nodes not possible in Parsetree.signature (mli files)Parsetree.signature(mli 文件)中无法使用某些 AST 节点
【发布时间】:2018-01-17 02:04:40
【问题描述】:

我正在编写一个从mli 文件生成ml 存根的工具。我已经完成了有意义的映射(var f : int -> floatlet f _ = 0.),但是我在推理类和模块的 AST 节点时遇到了一些麻烦,特别是 Pmty_*Pcty_* 节点。这两种类型的节点(给定类型层次结构)似乎与mli 文件最相关。一些琐碎的成员——出现在mli 文件中的-dparsetrees(如Pmty_identPcty_constr)与与ml 文件最相关的节点有明显的映射关系(同样通过@987654337 的层次结构) @ 和 structure 类型,在上述示例中为 Pmod_identPcl_constr)。然而,一些节点没有明显的平行。具体来说,我无法推理:

  • Pmty_withPmty_typeof - 似乎永远无法从有效的 mli 文件中解析这两个;它们只出现在structure 上下文中,其中module_type 作为其成员之一(我已经检查了我对all of OCaml's parser test files 的怀疑)
  • Pcty_signature - 似乎唯一可能发生的情况是在 Psig_class_type 内(我已经直接映射到 Pstr_class_type);我针对 OCaml 测试文件发现了一个 Pcty_signature 仅在此位置,但在我缺少的 mli 中是否还有其他有效位置?
  • Pcty_arrow - 没有包含这个的测试文件,所以我不能确定它在哪里有效,但我的直觉说这也只在structure 内的class_type 上下文中出现(或在其他一个中) Pcty_*,在这种情况下,它的转换将由该节点从Pcty_*Pcl_* 的映射作为子节点处理);这是不正确的吗?

我对此相当深入,并不完全了解所有这些高级语言功能和代表它们的 AST 节点发生了什么,所以这里尝试对我的问题进行更简单的解释:

Parsetree中提取的相关类型

type module_type_desc =
  (* .. snip .. *)
  | Pmty_with of module_type * with_constraint list
        (* MT with ... *)
  | Pmty_typeof of module_expr
        (* module type of ME *)

type class_type_desc =
  (* .. snip .. *)
  | Pcty_signature of class_signature
        (* object ... end *)
  | Pcty_arrow of arg_label * core_type * class_type
        (* T -> CT       Simple
           ~l:T -> CT    Labelled l
           ?l:T -> CT    Optional l
         *)
  • 我相信Pmty_withPmty_typeof 只能出现在ml 文件中(而不是mli 文件)。这个假设正确吗?
  • Pcty_signature 是否可以作为不是Psig_class_type 的子节点的节点出现?
  • Pcty_arrow 可以出现在有效的mli 文件中吗?在哪里?作为什么的孩子?

正如我之前提到的,除了这两个(模块和类)之外,我对自己的处理非常有信心。如果以上内容不清楚,这里有一个带注释的 sn-p 代码转换 Parsetree.signature -> Parsetree.structure,为简洁起见删除了所有非模块/类的东西:

(* Parsetree.signature -> Parsetree.structure *)
let rec stub signature_items =
  (* Handles the module_type_desc *)
  let rec stub_module_type module_type =
    match module_type with
    | { pmty_desc = type_; pmty_attributes = attrs; _ } ->
      let expr =
        match type_ with
        | Pmty_ident ident -> Pmod_ident ident
        | Pmty_signature signatures -> Pmod_structure (stub signatures)
        | Pmty_functor (name, a, b) -> Pmod_functor (name, a, (stub_module_type b))
        (* XXX: unclear if these two can occur in an mli *)
        (* | Pmty_with (type_, constraints) -> _ TODO *)
        (* | Pmty_typeof type_ -> _ TODO *)
        | Pmty_extension ext -> Pmod_extension ext
        | Pmty_alias name -> Pmod_ident name
      in
        make_module_expr expr attrs
  in
  (* The next three functions handles the module_type for single and multiple (rec) modules *)
  let stub_module_decl module_decl =
    match module_decl with
    | { pmd_name = name; pmd_type = type_; pmd_attributes = attrs; _ } ->
      make_module_binding name (stub_module_type type_) attrs
  in
  let stub_module module_ = Pstr_module (stub_module_decl module_)
  and stub_modules modules = Pstr_recmodule (List.map stub_module_decl modules)
  and stub_include include_ =
    match include_ with
    | { pincl_mod = module_type; pincl_attributes = attrs; _ } ->
        Pstr_include (make_include_decl (stub_module_type module_type) attrs)
  in
  (* Handles classes (class_type) *)
  let stub_classes classes =
    (* Handles class_type_desc *)
    let stub_class_descr descr =
      let rec stub_class class_ =
        let stub_class_type type_ =
          match type_ with
          | Pcty_constr (ident, types) -> Pcl_constr (ident, types)
          | Pcty_signature class_ -> (* XXX: Is my below assumption true? *)
              failwith "should be covered by Psig_class_type -> Pstr_class_type"
          (* XXX: do we ever need to handle Pcty_arrow for mli files? *)
          (* | Pcty_arrow (label, a, b) -> _ *)
          | Pcty_extension ext -> Pcl_extension ext
          | Pcty_open (override, ident, class_) ->
              Pcl_open (override, ident, (stub_class class_))
        in
          match class_ with
          | { pcty_desc = type_; pcty_attributes = attrs; _ } ->
            make_class_expr (stub_class_type type_) attrs
      in
        match descr with
        | { pci_virt = virt; pci_params = params; pci_name = name;
            pci_expr = class_; pci_attributes = attrs } ->
          make_class_decl virt params name (stub_class class_) attrs
    in
      Pstr_class (List.map stub_class_descr classes)
  in
  let transform_signature signature_item =
    match signature_item with
    | { psig_desc = signature; _ } ->
      let desc =
        match signature with
        (* ... clip non-module/class stuff ... *)
        | Psig_module module_         -> stub_module module_
        | Psig_recmodule modules      -> stub_modules modules
        | Psig_include include_       -> stub_include include_
        | Psig_class classes          -> stub_classes classes
        | Psig_class_type classes     -> Pstr_class_type classes
      in
        make_str desc
  in
    List.map transform_signature signature_items

不幸的是模块/类的东西是相当复杂的逻辑,所以修剪下来还有很多。创建 *_desc 包装器有很多帮助,这些包装器封装了文件中的位置、属性等,但这些不应该是理解我如何处理模块和类的关键。但为了清楚起见,这里是所有助手的类型:

val make_str : Parsetree.structure_item_desc -> Parsetree.structure_item

val make_module_expr :
  Parsetree.module_expr_desc -> Parsetree.attributes -> Parsetree.module_expr

val make_module_binding :
  string Asttypes.loc ->
  Parsetree.module_expr -> Parsetree.attributes -> Parsetree.module_binding

val make_include_decl :
  'a -> Parsetree.attributes -> 'a Parsetree.include_infos

val make_class_decl :
  Asttypes.virtual_flag ->
  (Parsetree.core_type * Asttypes.variance) list ->
  string Asttypes.loc ->
  'a -> Parsetree.attributes -> 'a Parsetree.class_infos

val make_class_expr :
  Parsetree.class_expr_desc -> Parsetree.attributes -> Parsetree.class_expr

相关文档:


编辑:顺便说一句,除了阅读有关这些功能的文档(没有产生任何我不知道的 AST 模式)之外,我记得编译后的接口可以从实现ocamlc -i。我在编译器中追踪了与该标志相关联的变量(称为print_types)并找到了它的所有用途,但我并没有立即看出它在哪里调用了派生mli的任何使用代码文件(也许它是通过解析逐步完成的,因为编译会产生一个cmi?)。如果有更多 OCaml 知识或更多编译器经验的人可以指出mli 文件的派生位置,那么对这些模块和类 AST 节点进行逆向工程可能更容易。

编辑 2: 我也知道How to auto-generate stubs from mli file?,但是答案是“手动执行”,这肯定与我正在尝试的冲突! (回答者还声称这样的工具是微不足道的,但在这些 AST 节点上倾注了一段时间后,我不敢苟同!)

【问题讨论】:

    标签: ocaml abstract-syntax-tree


    【解决方案1】:

    (我的答案中没有包含 -dparsetree,因为它很重而且没那么有趣)。

    我相信 Pmty_with 和 Pmty_typeof 只能出现在 ml 文件(而不是 mli 文件)中。这个假设正确吗?

    module M : module type of struct type t end with type t = int
    

    从这个有效的 mli 文件中可以看出,这个假设是不正确的。 .mli 文件需要与 .ml 文件相同的解析器。

    Pcty_signature 可以作为不是 Psig_class_type 子节点的节点出现吗?

    class type c = object inherit object method x : int end end
    

    是的,它可以。 Pcty_signature 可以出现在类类型可以出现的任何地方。 (注意这里有两个Pcty_signature,一个是Pctf_inherit的孩子)。

    Pcty_arrow 出现在有效的 mli 文件中?在哪里?作为什么的孩子?

    class c' : int -> object method x : int end
    

    是的,它可以!它可以出现在任何你可以指明类类型的地方。

    基本上,您可以认为,如果构造函数可以发生在某个地方,那么所有相同类型的构造函数也可以发生在那里。任何与类型相关的构造函数都可以在 .mli 文件中(非类型相关的构造函数也可以通过狡猾的 module type of 发生)。

    如果您对它们的构造位置有疑问,请查看parser.mly。请注意,这两种文件类型使用相同的解析器。

    【讨论】:

    • 谢谢!这是一个很棒的答案! “如果构造函数可以发生在某个地方,那么该类型的所有构造函数也可以发生在那里”非常有趣。虽然作为一个不幸的副作用,这意味着我还有更多的工作要做!我希望您不介意我将您包含的精美示例用作测试用例。 mli 文件无法实现,对吗?因此,您似乎无法在 mli 的任何地方拥有 class_expr(因为它可能包含带有实现的 Pcl_structure)。我认为我仍然无法确定需要涵盖的案例子集。
    • 另外,鉴于您的简介,我想知道您是否对我迄今为止的设计方式有任何想法。我担心实际处理类和模块所需的复杂性会变得更加笨拙(我的stub_* 的某些类型可能不起作用,因为类型并不总是被合成到实现中)。很抱歉有点像水蛭,因为这与我的问题无关(并且可能违反了一些 SO 规则)。但鉴于你的职位描述,听起来我现在真的可以借用你的大脑!
    • 此外,我的测试计划是(除了手写案例)收集 ocaml 存储库中的所有 mli 文件,在它们上运行我的脚本,然后查看它们是否成功编译。我还可以在所有 ml 文件上运行 ocamlc -i 并在它们上尝试相同的操作。大概编译器和标准库应该利用大部分语言特性,所以我希望这涵盖了所有内容。这听起来合理吗? (再次抱歉打扰了!)
    • @BaileyParker 因为module type of,任何事情都可能发生在一个 mli 中。
    • @BaileyParker 这真是太讨人喜欢了。我不确定您要完成什么(以及为什么),但您可能应该首先关注该语言的一个子集。就像,暂时忘记类和对象,并在它们不出现时拥有一些有用的东西。此外,您可能想查看 ast_invariants.ml 以了解 AST 中实际发生的不变量。也许您也想使用ast_iterator.mliast_helper.mli 来编写您的代码。
    【解决方案2】:

    这是一款很棒的游戏。给我一个 AST 节点列表,我会写一个文件来使用它们。 :D

    module K : module type of String
    module M : Map.S with type key = K.t
    
    class fakeref : K.t -> object
        method get : K.t
        method set : K.t -> unit
      end
    

    所以,总结一下:类可以接受参数,因此Pcty_arrowPcty_signature 也可以是Psig_class 的孩子,如上图所示。另外两个是标准模块结构,绝对可以出现在.mli 文件中。

    至于ocamlc -i 的工作原理……好吧,它返回由类型检查器推断出的签名。没有单一的访问点。如果您愿意,可以阅读typing/HACKING.md,但请注意,兔子洞非常深。话虽如此,我认为这对实现您的目标没有太大帮助。

    我的建议如下:上面的所有节点都相当容易处理,with_type 除外。这个是非常困难的,因为它基本上允许计算签名。暂时放弃那个吧。

    另外,请注意值、模块、模块类型、类和类类型都有不同的命名空间。 Pmty_ident x -> Pmod_ident x 不正确。

    【讨论】:

    • 非常感谢;这真的很棒! “我会给你写一个使用它们的文件”——如果我把它用作我的测试用例之一,我希望它没问题!我会听取您的建议,然后再尝试除with_type 之外的所有内容(尽管我不完全理解为什么它很复杂,所以我可能需要做更多的阅读!)。我想我看到了你用Pmty_ident -> Pmod_ident 指出的问题。但要明确一点:模块类型标识符在任何情况下都不应该成为模块标识符?
    • 我希望在类型检查器中有一些不错的中心位置可以执行structure -> signature(假设双射,很容易反转)。但是根据你的建议,我会坚持更多地理解语法,而不是盲目地反转类型检查器的作用。
    • 另外,为了清楚起见,是否有一个明确的存根ml 对应于您提供的mli?对于班级来说,class fakeref _ = object method get = failwith "not implemented" method set _ = failwith "not implemented" end 似乎可以工作。但是模块呢?
    • 再深入一点,我又对Pmty_ident -> Pmod_ident 感到困惑。我相信以下转换有效:Psig_include (Pmty_ident) -> Pstr_include(Pmod_ident)。这相当于mli 中的include X 映射到ml 中的include X 对吧?基本上,我得到的是这个映射很好(在stub_module_type 内),只要stub_module_type 只在需要合成模块的上下文中调用(而不是你想要@987654344 中的实际类型) @ 喜欢在module M : Map.S)。这听起来对吗?
    • 在签名中include X指的是模块类型X。在一个结构体中,它指的是module X。模块和模块类型是不一样的。 include List 在签名中不起作用,include Map.S 在结构中不起作用。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-24
    • 1970-01-01
    • 2017-03-05
    相关资源
    最近更新 更多