Parsing 在用Ocaml编写的编译器中,在何处/如何声明变量的唯一键?
我正在用Ocaml编写mini-pascal编译器。例如,我希望我的编译器接受以下代码: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
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 *)