h = object.new

op_escape = [ "!" : "_bang",
          "*" : "_star",
          "-" : "_minus",
          "+" : "_plus",
          "|" : "_or",
          "&" : "_and",
          "@" : "_at",
          "~" : "_tilde",
          "^" : "_up",
          "/" : "_forward",
          "\\" : "_back",
          "?" : "_question",
          "<" : "_less",
          ">" : "_greater",
          "=" : "_equal",
          "%" : "_percent",
          "_" : "_under",
          "$" : "_dollar" ]

escape_op = { input |
  input.sub "[!?*+^@/\\><$_%%|&=~-]", { o |
    op_escape[o]
  }
}

kw_escape = /\A(and|break|do|else|elseif|end|false|for|function|if|in|local|nil|not|or|repeat|return|then|true|until|while)\Z/i

escape_keyword = { input |
  input.sub kw_escape, { m | "_" + m }
}

h.escape_identifier = { identifier |
  escape_keyword(escape_op(identifier))
}

unescape_ops = ["bang" : "!",
      "star" : "*",
      "minus" : "-",
      "plus" : "+",
      "or" : "|" ,
      "and" : "&",
      "at" : "@",
      "tilde" : "~",
      "up" : "^",
      "forward" : "/",
      "back" : "\\",
      "question" : "?",
      "less" : "<",
      "greater" : ">",
      "notequal" : "!=",
      "equal" : "=",
      "percent" : "%",
      "under" : "_",
      "dollar" : "$" ]

op_unescape = /_(bang|star|minus|plus|or|and|at|tilde|up|forward|back|question|less|greater|equal|percent|under|dollar)/

unescape_op = { input |
  input.sub op_unescape, { m | unescape_ops[m] }
}

kw_unescape = /__(and|break|do|else|elseif|end|false|for|function|if|in|local|nil|not|or|repeat|return|then|true|until|while)/

unescape_keyword = { input |
  input.sub kw_unescape, { m | m }
}

h.unescape_identifier = { identifier |
  unescape_op(unescape_keyword(identifier))
}

h.escape_string = { str |
  str.sub("\n", '\n')
}

h.lift_string = { str, var, type = :simple |
  lifted_id = my.lifted_string_index[str]

  null? lifted_id
    {
      true? type == :simple
       { my.liftable_strings << "symbol:new('#{str}')" }
       { my.liftable_strings << "symbol:new(\"#{str}\")" }

      lifted_id = my.liftable_strings.length
      my.lifted_string_index[str] = lifted_id
    }

  set_result var, "_lifted_strings[#{lifted_id}]", type: :string
}

h.lifted_strings = {
  list = [r(null, "local _lifted_strings = {")]
  o = my

  my.liftable_strings.each { str |
    list << o.r(null, "#{str}," )
  }

  list << r(null, "}")

  list
}

export h, :string_helper