eval (BinaryOp op (l, r)) =
  case op of
    And ->
      eval l >>= \l' ->
        if toBool l' then
          eval r >>= \r' -> return r'
        else
          return l'
    Or ->
      eval l >>= \l' -> 
        if not (toBool l') then
          eval r >>= \r' -> return r'
        else
          return l'
    _ ->
      eval l >>= \l' -> eval r >>= \r' -> return (evalOp op l' r')

-- takes a list of names and a list of values
-- evaluates all the values first (in case of 'variable swaping')
-- then zip that into a list of pairs and add to a symbol table
eval (BinaryOp op (l, r)) =
  case op of
    And ->
      eval l >>= \l' ->
        if toBool l' then
          eval r >>= \r' -> return r'
        else
          return l'
    Or ->
      eval l >>= \l' -> 
        if not (toBool l') then
          eval r >>= \r' -> return r'
        else
          return l'
    _ ->
      eval l >>= \l' -> eval r >>= \r' -> return (evalOp op l' r')

-- takes a list of names and a list of values
-- evaluates all the values first (in case of 'variable swaping')
-- then zip that into a list of pairs and add to a symbol table
eval (BinaryOp op (l, r)) =
  case op of
    And ->
      eval l >>= \l' ->
        if toBool l' then
          eval r >>= \r' -> return r'
        else
          return l'
    Or ->
      eval l >>= \l' -> 
        if not (toBool l') then
          eval r >>= \r' -> return r'
        else
          return l'
    _ ->
      eval l >>= \l' -> eval r >>= \r' -> return (evalOp op l' r')

-- takes a list of names and a list of values
-- evaluates all the values first (in case of 'variable swaping')
-- then zip that into a list of pairs and add to a symbol table