Parsing 在用Ocaml编写的编译器中,在何处/如何声明变量的唯一键?

Parsing 在用Ocaml编写的编译器中,在何处/如何声明变量的唯一键?,parsing,compiler-construction,syntax,ocaml,Parsing,Compiler Construction,Syntax,Ocaml,我正在用Ocaml编写mini-pascal编译器。例如,我希望我的编译器接受以下代码: program test; var a,b : boolean; n : integer; begin ... end. int_expression: (* Constant : ignore the context *) | c = INT { fun _ -> Se_const (Sc_int c) } (* Variable : look for the varia

我正在用Ocaml编写mini-pascal编译器。例如,我希望我的编译器接受以下代码:

program test;
var
   a,b : boolean;
   n : integer;
begin
   ...
end.
int_expression:
  (* Constant : ignore the context *)
| c = INT { fun _ -> Se_const (Sc_int c) }
  (* Variable : look for the variable inside the contex *)
| id = IDENT { fun ctx -> Se_var (find id ctx) }
  (* Subexpressions : pass the context to both *)
| e1 = int_expression o = operator e2 = int_expression 
  { fun ctx -> Se_binary (o, e1 ctx, e2 ctx) }
;
我在处理变量声明时遇到困难(以下部分
var
)。目前,变量类型的定义如下所示:

其中,
s_var_uniqueId
(而不是
s_var_name
)是变量的唯一键。我的第一个问题是,每次我有一个新变量时,在哪里以及如何实现生成新id的机制(实际上是通过将最大id增加1)。我想知道我是否应该在中实现它,这可能涉及一个静态变量
cur\u id
和对
绑定部分的修改,同样不知道如何在
.mly
中实现它们。或者我应该在下一个阶段实现该机制-解释器.ml
?但在这种情况下,问题是如何使
.mly
与类型
s_var
一致,在
绑定部分中我应该提供什么
s_var\u uniqueId

另一个问题是关于
.mly
语句的这一部分:

id = IDENT COLONEQ e = expression
  { Sc_assign (Sle_var {s_var_name = id; s_var_type = St_void}, e) }
在这里,我还需要提供下一个级别(解释器.ml
)一个变量,我只知道它的
s\u var\u名称
,那么对于它的
s\u var\u类型
s\u var\u uniqueId
,我能做些什么呢


有人能帮忙吗?多谢各位

如何创建全局id生成器:

let unique =
  let counter = ref (-1) in
  fun () -> incr counter; !counter
测试:

关于更一般的设计问题:您的数据表示似乎不能忠实地表示编译器阶段。如果在解析阶段之后必须返回类型识别数据类型(使用此字段
s\u var\u type
),则可能出现问题。你有两个选择:

  • 为解析后AST设计更精确的数据表示,这将不同于后期键入AST,并且没有那些
    s\u var\u type
    字段。然后,键入将是从未键入到已键入AST的转换。这是一个干净的解决方案,我会推荐

  • 承认您必须打破数据表示语义,因为在这个阶段您没有足够的信息,并尝试在解析阶段后返回垃圾(如
    St_void
    )以稍后重建正确的信息。这种类型较少(因为您对数据有一个在类型中不明显的隐式假设),更加实用、丑陋,但有时是必要的。在这种情况下,我认为这不是一个正确的决定,但你会遇到这样的情况:最好少打字


我认为独特id处理设计的具体选择取决于您对这个更一般问题的立场,以及您对类型的具体决定。如果您选择解析后AST的更精细的类型化表示,那么您可以选择是否包含唯一ID(我会,因为生成唯一ID非常简单,不需要单独的过程,而且我宁愿稍微复杂化语法生成,而不是类型化阶段)。如果您选择使用一个伪值来破解type字段,那么如果您愿意,也可以对变量id这样做,将
0
作为伪值并在以后定义它;但我个人还是会在解析阶段这样做。

要问自己的第一个问题是,你是否真的需要一个唯一的id。根据我的经验,它们几乎从来都不是必需的,甚至是有用的。如果您试图通过使变量唯一,那么这应该在解析完成后发生,并且可能涉及某种形式的而不是唯一标识符

无论哪种方式,每次调用时返回新整数标识符的函数都是:

let unique = 
  let last = ref 0 in 
  fun () -> incr last ; !last

let one = unique ()  (* 1 *)
let two = unique ()  (* 2 *)
因此,您可以在Menhir规则中简单地分配
{…;s_var_uniqueId=unique()}

这里要解决的更重要的问题是变量绑定问题。变量
x
在一个位置定义,在另一个位置使用,您需要确定它在两个位置恰好是同一个变量。有很多方法可以做到这一点,其中之一是将绑定延迟到解释器。我将向您展示如何在解析过程中处理这个问题

首先,我将定义一个上下文:它是一组变量,允许您根据其名称轻松检索变量。您可能希望使用哈希表或映射创建它,但为了保持简单,我将在这里使用
List.assoc

type s_context = {
  s_ctx_parent : s_context option ;
  s_ctx_bindings : (string * (int * s_type)) list ;
  s_ctx_size : int ;
}

let empty_context parent = {
  s_ctx_parent = parent ;
  s_ctx_bindings = [] ;
  s_ctx_size = 0
}

let bind v_name v_type ctx = 
  try let _ = List.assoc ctx.s_ctx_bindings v_name in
      failwith "Variable is already defined"
  with Not_found -> 
    { ctx with 
      s_ctx_bindings = (v_name, (ctx.s_ctx_size, v_type)) 
        :: ctx.s_ctx_bindings ;
      s_ctx_size = ctx.s_ctx_size + 1 }

let rec find v_name ctx =       
  try 0, List.assoc ctx.s_ctx_bindings v_name
  with Not_found -> 
    match ctx.s_ctx_parent with 
      | Some parent -> let depth, found = find v_name parent in
                       depth + 1, found
      | None -> failwith "Variable is not defined"
因此,
bind
将一个新变量添加到当前上下文中,
find
在当前上下文中查找变量及其父级,并返回绑定数据和找到该变量的深度。因此,您可以在一个上下文中包含所有全局变量,然后在另一个上下文中包含函数的所有参数,该上下文将全局上下文作为其父上下文,然后在第三个上下文中包含函数中的所有局部变量(如果有),该上下文将函数的主上下文作为其父上下文,依此类推

因此,例如,
find'x'ctx
将返回类似
0,(3,St_int)
的内容,其中
0
是变量的DeBruijn索引,
3
是变量在由DeBruijn索引标识的上下文中的位置,
St_int
是类型

type s_var = {
  s_var_deBruijn: int;
  s_var_type: s_type;
  s_var_pos: int 
}

let find v_name ctx = 
   let deBruijn, (pos, typ) = find v_name ctx in 
   { s_var_deBruijn = deBruijn ;
     s_var_type = typ ;
     s_var_pos = pos }
当然,您需要函数存储其上下文,并确保第一个参数是上下文中位置0处的变量:

type s_fun =
{ s_fun_name: string;
  s_fun_type: s_type;
  s_fun_params: context; 
  s_fun_body: s_block; }

let context_of_paramlist parent paramlist = 
  List.fold_left 
    (fun ctx (v_name,v_type) -> bind v_name v_type ctx) 
    (empty_context parent)
    paramlist
然后,您可以更改解析器以考虑上下文。诀窍在于,大多数规则不会返回表示AST一部分的对象,而是返回一个函数,该函数将上下文作为参数并返回AST节点

例如:

program test;
var
   a,b : boolean;
   n : integer;
begin
   ...
end.
int_expression:
  (* Constant : ignore the context *)
| c = INT { fun _ -> Se_const (Sc_int c) }
  (* Variable : look for the variable inside the contex *)
| id = IDENT { fun ctx -> Se_var (find id ctx) }
  (* Subexpressions : pass the context to both *)
| e1 = int_expression o = operator e2 = int_expression 
  { fun ctx -> Se_binary (o, e1 ctx, e2 ctx) }
;
因此,您只需传播
| function_definition_expression (args, body) 
  { fun ctx -> let ctx = context_of_paramlist (Some ctx) args in
               { s_fun_params = ctx ; 
                 s_fun_body = body ctx } }
prog:
  PROGRAM IDENT SEMICOLON
  globals = variables
  main = block
  DOT
    { let ctx = context_of_paramlist None globals in 
      { globals = ctx;
        main = main ctx } }
type stack = value array list 
let read stack x = 
  (List.nth stack x.s_var_deBruijn).(x.s_var_pos)

let write stack x value = 
  (List.nth stack x.s_var_deBruijn).(x.s_var_pos) <- value
let inner_stack = args :: stack in
(* Evaluate f.s_fun_body with inner_stack here *)