def normalize_funql(funql):
  """Recursively parse FunQL string to re-formatted string."""
  # Special constant used for "sea level".
  if funql == "0":
    return "0"

  name, arguments = _get_name_and_arguments(funql)
  if name == "answer":
    if len(arguments) != 1:
      raise ValueError
    argument_0 = arguments[0]
    return "%s(%s)" % (name, normalize_funql(argument_0))
  elif name in ENTITY_FUNCTIONS:
    return funql
  elif name in RELATION_CONSTANTS:
    if len(arguments) != 1:
      raise ValueError
    argument_0 = arguments[0]
    reformatted_argument_0 = normalize_funql(argument_0)
    if not reformatted_argument_0:
      raise ValueError("Failed to reformat: %s" % argument_0)
    return "%s(%s)" % (name, reformatted_argument_0)
  elif name in UNARY_CONSTANTS:
    if len(arguments) != 1:
      raise ValueError
    argument_0 = arguments[0]
    if argument_0 == "all":
      return name
    else:
      recursive_term = normalize_funql(argument_0)
      return "intersection(%s,%s)" % (name, recursive_term)
  elif name == "intersection" or name == "exclude":
    if len(arguments) != 2:
      raise ValueError
    argument_0 = arguments[0]
    argument_1 = arguments[1]
    term_a = normalize_funql(argument_0)
    term_b = normalize_funql(argument_1)
    return "%s(%s,%s)" % (name, term_a, term_b)
  elif name in ARITY_1:
    if len(arguments) != 1:
      raise ValueError
    argument_0 = arguments[0]
    return _convert_function(name, argument_0, 1)
  elif name in ARITY_2:
    if len(arguments) != 1:
      raise ValueError
    argument_0 = arguments[0]
    return _convert_function(name, argument_0, 2)
  elif name in ARITY_3:
    if len(arguments) != 1:
      raise ValueError
    argument_0 = arguments[0]
    return _convert_function(name, argument_0, 3)
  else:
    raise ValueError("No match for name: %s" % name)
Recursively parse FunQL string to re-formatted string.
language/compgen/nqg/tasks/geoquery/funql_normalization.py