代码之家  ›  专栏  ›  技术社区  ›  Addem

OCaml映射在序列的下一步之前未更新

  •  0
  • Addem  · 技术社区  · 4 年前

    我正在尝试在OCaml中实现一个基本的解析器、扫描程序和一种最低限度的语言。我 相信 问题是,我试图在这个玩具语言中维护变量及其值之间的映射,并且该语言应该能够处理这样的表达式 a=2;a 以及返回2。名称似乎成功地存储了数字2,但当程序继续计算第二个表达式时,它在映射中找不到名称。我不明白为什么。

    下面是抽象语法树。

    Ast.ml

    type operator = Add (* for now just one operator *)
    
    type expr =
        Binop of expr * operator * expr
      | Lit of int                      (* a number *)
      | Seq of expr * expr              (* a sequence, to behave like ";" *)
      | Asn of string * expr            (* assignment, to behave like "=" in an imperative language *)
      | Var of string                   (* a variable *)
    

    这是解析器和扫描仪。

    parser.mly

    %{ 
    open Ast 
    %}
    
    %token SEQ PLUS ASSIGN EOF
    %token <int> LITERAL
    %token <string> VARIABLE
    
    %left SEQ PLUS 
    
    %start expr
    %type <Ast.expr> expr
    
    %%
    
    expr:
    | expr SEQ expr        { Seq($1, $3) }
    | expr PLUS   expr { Binop($1, Add, $3) }
    | LITERAL          { Lit($1) }
    | VARIABLE         { Var($1) }
    | VARIABLE ASSIGN expr { Asn($1, $3) }
    

    scanner.mll

    { 
    open Parser 
    }
    
    rule tokenize = parse
      [' ' '\t' '\r' '\n'] { tokenize lexbuf }
    | '+' { PLUS }
    | ['0'-'9']+ as lit { LITERAL(int_of_string lit) }
    | ['a'-'z']+ as id { VARIABLE(id) }
    | '=' { ASSIGN }
    | ';' { SEQ }
    | eof { EOF }
    

    在这里,我试图在一个基本的计算器中实现一种名称空间。

    计算.ml

    open Ast
    
    module StringMap = Map.Make(String)
    let namespace = ref StringMap.empty
    
    let rec eval exp = match exp with  
      | Lit(n) -> n        
      | Binop(e1, op, e2) ->
          let v1 = eval e1 in
          let v2 = eval e2 in
          v1+v2
      | Asn (name, e) -> 
          let v = eval e in 
          (namespace := StringMap.add name v !namespace; v)
      | Var(name) -> StringMap.find name !namespace
      | Seq(e1, e2) -> 
          (let _ = eval e1 in 
          eval e2)
    
    let _ =
      let lexbuf = Lexing.from_channel stdin in
      let expr = Parser.expr Scanner.tokenize lexbuf in
      let result = eval expr in
      print_endline (string_of_int result)
    

    为了测试它,我编译了它,它成功地编译了,然后运行 $ ./calc 在终端中,然后输入 a=2;一 然后按 Ctrl+D 。它应该打印2,但它给出了 Not found 例外据推测,这是来自 StringMap.find 行,并且在名称空间中找不到名称。我试过四处打印行,我想我可以确认序列在终端中得到了正确的处理,并且第一次评估成功进行,名称和值被输入到字符串映射中。但由于某种原因,当程序继续处理序列中的第二个表达式时,它似乎不存在。

    如有任何澄清,我将不胜感激。

    0 回复  |  直到 4 年前
        1
  •  1
  •   octachron    4 年前

    我无法重现你的错误。 将AST直接喂入 eval

    let () =
      let ast = Seq(Asn ("a", Lit 2),Var "a") in
      let result = eval ast in
      print_endline (string_of_int result)
    

    打印 2 正如预期的那样。

    在修复解析器以识别流的末尾之后:

    entry:
    | expr EOF { $1 }
    

    在中使用

    
    let () =
      let s = Lexing.from_string "a=2;a\n" in
      let ast = Parser.entry Scanner.tokenize s in
      let result = eval ast in
      print_endline (string_of_int result)
    

    打印 2. 正如预期的那样。如果没有此修复程序,您的代码将失败并出现语法错误。

    编辑: 与其使用makefile,我建议使用dune dune 文件:

    (menhir (modules parser))
    (ocamllex scanner)
    (executable (name calc))
    

    它至少可以解决您的编译问题。

        2
  •  0
  •   hyphenFG    3 年前

    嘿,所以我不知道你是否还在寻找解决方案,但我能够重现这个问题,以及我是如何解决的,只需在parser.mly中向%left语句添加assign即可

    %left SEQ ASSIGN PLUS