Search code examples
parsingocamlarithmetic-expressionsmenhir

Parsing arithmetic expression with variable using menhir


parser.mly:

%{
  let env=Hashtbl.create 10
%}
%token <int> INT
%token SUB
%token EOL
%token EQUAL
%token NAME
%left SUB EQUAL

%start main             /* the entry point */
%type<int> main
%%
 main:
  expr EOL                { $1 }
;

expr:
   INT                     { $1} 
  | expr SUB expr           { $1 - $3}
  | var EQUAL expr {Hashtbl.add env $1 $3;0}
  | var {Hashtbl.find env $1}
;

var:NAME {$1}

(the 12's line is: %type<int> main)

using:

wangkai@wangkai-ThinkPad-X1:~/src/ocaml/lexonlysub$ ./calc
a=1
0
b=2
0
b
2
a
2
^C

I don't know why after define b=2,then a is 2 too,it should be 1.Why got 2?

and this:

wangkai@wangkai-ThinkPad-X1:~/src/ocaml/lexonlysub$ ./calc
a=1
0
b=2
0
a-b
0

lexer.mll:

{
open Parser
exception Eof
}
rule token = parse
  [' ' '\t']     { token lexbuf }     (* skip blanks *)
| ['\n' ]        { EOL }
 | ['0'-'9']+ as lxm { INT(int_of_string lxm) } 
| '-'            { SUB }
|['a'-'z']+ {NAME}
|'=' {EQUAL}
| eof            { raise Eof }

Thanks!

calc.ml

open Syntax

let _ =
  try
    let lexbuf = Lexing.from_channel stdin in
    while true do
      let result = Parser.main Lexer.token lexbuf in
      print_int result;
      print_newline ();
      flush stdout
    done
  with Lexer.Eof -> exit 0

makefile:

calc:lexer.cmo parser.cmo syntax.cmo calc.cmo 
    ocamlc -o calc lexer.cmo parser.cmo syntax.cmo calc.cmo

lexer.cmo:lexer.mll
    ocamllex lexer.mll
    
parser.cmo:parser.mly
    menhir parser.mly

syntax.cmo:syntax.ml
    ocamlc -c syntax.ml

calc.cmo:calc.ml
    ocamlc -c lexer.ml
    ocamlc -c parser.mli
    ocamlc -c parser.ml
    ocamlc -c calc.ml

Solution

  • You need to add a payload to NAME. Otherwise, it won't register the string that was lexed; instead, the value it gives you is just (), so every time you access your hash table you access the same element.

    You need to add the string type to NAME:

    %token<string> NAME
    

    to have a string payload. You also need to actually remember that payload in the lexer:

    | ['a'-'z']+ as name { NAME(name) }
    

    (these two snippets should replace their equivalent in your code)

    You can ensure that you have the appropriate type by adding some annotations:

    %{
      let env : (string, int) Hashtbl.t = Hashtbl.create 10
    %}
    

    (if you add this annotation to your current version, it should fail; but it won't with the appropriate payload added to NAME).