Search code examples
ocamlocamlyacc

Ocaml stringmap calculator AST parse not adding or finding


Very close to getting this to work but having trouble with StringMap from OCaml. Essentially I'm making a calculator which takes in from ocamllex the lexical stream ... so here commas are supposed to separate out our expressions while equal signs means that we will be assigning a value to a variable.

I realized that when assigning variables I'm not able to look them up because I get (Fatal error: exception Not_found) on the lines for not finding the key's I added in the Var case of the function. I don't know where to put the StringMap.empty or how to make it visible in this function... I was wondering why it can't find what I'm adding in the equals case?

Here's my code.

open Ast

module StringMap = Map.Make(String)

let varMap = StringMap.empty

let rec parser = function

  Lit(x) -> x
| Binop(e1, op, e2) -> (
      let v1 = parser e1 and v2 = parser e2 in
      match op with
            Add -> v1 + v2
            | Sub -> v1 - v2
            | Mul -> v1 * v2
            | Div -> v1 / v2
      )
| Var(v) -> StringMap.find v varMap
| Statements(e1, e2) ->   ignore(parser e1); parser e2
| Equals(v, e1) ->  StringMap.add v e1 varMap; parser e1

let _ =
  let LexingBuffer = Lexing.from_channel stdin in
  let expression = Parser.expression Scanner.token LexingBuffer in
  let result = parser expression in
  print_endline (string_of_int result)

Solution

  • Map is an immutable data structure. Any function that changes the map will return a new modified instance, while leaving the previous one unaltered. Therefore, this expression

    StringMap.add v e1 varMap; parser e1
    

    will just throw the new map away, and return whatever the recursive call to parser returns instead. If you want to use Map you have to keep the new instance around. You can do so either by making varMap a ref cell that you update:

    let varMap = ref StringMap.empty
    
    ...
    
    varMap := StringMap.add v1 !varMap
    

    or by adding a function argument to parser and pass it along:

    let rec parser varMap = function
    
    ...
    
    parser (StringMap.add v e1 varMap) e1
    

    Another option is to use a Hashtbl instead, which is mutable and works very well with strings.