text: """
  Process raw qstr file and output qstr data with length, hash and data bytes.
  
  This script works with Python 2.6, 2.7, 3.3 and 3.4.
  """
  
  from __future__ import print_function
  
  import re
  import sys
  
  # Python 2/3 compatibility:
  #   - iterating through bytes is different
  #   - codepoint2name lives in a different module
  import platform
  
  if platform.python_version_tuple()[0] == "2":
      bytes_cons = lambda val, enc=None: bytearray(val)
      from htmlentitydefs import codepoint2name
  elif platform.python_version_tuple()[0] == "3":
      bytes_cons = bytes
      from html.entities import codepoint2name
  # end compatibility code
  
  codepoint2name[ord("-")] = "hyphen"
  
  # add some custom names to map characters that aren't in HTML
  codepoint2name[ord(" ")] = "space"
  codepoint2name[ord("'")] = "squot"
  codepoint2name[ord(",")] = "comma"
  codepoint2name[ord(".")] = "dot"
  codepoint2name[ord(":")] = "colon"
  codepoint2name[ord(";")] = "semicolon"
  codepoint2name[ord("/")] = "slash"
  codepoint2name[ord("%")] = "percent"
  codepoint2name[ord("#")] = "hash"
  codepoint2name[ord("(")] = "paren_open"
  codepoint2name[ord(")")] = "paren_close"
  codepoint2name[ord("[")] = "bracket_open"
  codepoint2name[ord("]")] = "bracket_close"
  codepoint2name[ord("{")] = "brace_open"
  codepoint2name[ord("}")] = "brace_close"
  codepoint2name[ord("*")] = "star"
  codepoint2name[ord("!")] = "bang"
  codepoint2name[ord("\\")] = "backslash"
  codepoint2name[ord("+")] = "plus"
  codepoint2name[ord("$")] = "dollar"
  codepoint2name[ord("=")] = "equals"
  codepoint2name[ord("?")] = "question"
  codepoint2name[ord("@")] = "at_sign"
  codepoint2name[ord("^")] = "caret"
  codepoint2name[ord("|")] = "pipe"
  codepoint2name[ord("~")] = "tilde"
  
  # static qstrs, should be sorted
  
  static_qstr_list = [
      "",
      "__dir__",  # Put __dir__ after empty qstr for builtin dir() to work
      "\n",
      " ",
      "*",
      "/",
      "<module>",
      "_",
      "__call__",
      "__class__",
      "__delitem__",
      "__enter__",
      "__exit__",
      "__getattr__",
      "__getitem__",
      "__hash__",
      "__init__",
      "__int__",
      "__iter__",
      "__len__",
      "__main__",
      "__module__",
      "__name__",
      "__new__",
      "__next__",
      "__qualname__",
      "__repr__",
      "__setitem__",
      "__str__",
      "ArithmeticError",
      "AssertionError",
      "AttributeError",
      "BaseException",
      "EOFError",
      "Ellipsis",
      "Exception",
      "GeneratorExit",
      "ImportError",
      "IndentationError",
      "IndexError",
      "KeyError",
      "KeyboardInterrupt",
      "LookupError",
      "MemoryError",
      "NameError",
      "NoneType",
      "NotImplementedError",
      "OSError",
      "OverflowError",
      "RuntimeError",
      "StopIteration",
      "SyntaxError",
      "SystemExit",
      "TypeError",
      "ValueError",
      "ZeroDivisionError",
      "abs",
      "all",
      "any",
      "append",
      "args",
      "bool",
      "builtins",
      "bytearray",
      "bytecode",
      "bytes",
      "callable",
      "chr",
      "classmethod",
      "clear",
      "close",
      "const",
      "copy",
      "count",
      "dict",
      "dir",
      "divmod",
      "end",
      "endswith",
      "eval",
      "exec",
      "extend",
      "find",
      "format",
      "from_bytes",
      "get",
      "getattr",
      "globals",
      "hasattr",
      "hash",
      "id",
      "index",
      "insert",
      "int",
      "isalpha",
      "isdigit",
      "isinstance",
      "islower",
      "isspace",
      "issubclass",
      "isupper",
      "items",
      "iter",
      "join",
      "key",
      "keys",
      "len",
      "list",
      "little",
      "locals",
      "lower",
      "lstrip",
      "main",
      "map",
      "micropython",
      "next",
      "object",
      "open",
      "ord",
      "pop",
      "popitem",
      "pow",
      "print",
      "range",
      "read",
      "readinto",
      "readline",
      "remove",
      "replace",
      "repr",
      "reverse",
      "rfind",
      "rindex",
      "round",
      "rsplit",
      "rstrip",
      "self",
      "send",
      "sep",
      "set",
      "setattr",
      "setdefault",
      "sort",
      "sorted",
      "split",
      "start",
      "startswith",
      "staticmethod",
      "step",
      "stop",
      "str",
      "strip",
      "sum",
      "super",
      "throw",
      "to_bytes",
      "tuple",
      "type",
      "update",
      "upper",
      "utf-8",
      "value",
      "values",
      "write",
      "zip",
  ]
  
  # this must match the equivalent function in qstr.c
  def compute_hash(qstr, bytes_hash):
      hash = 5381
      for b in qstr:
          hash = (hash * 33) ^ b
      # Make sure that valid hash is never zero, zero means "hash not computed"
      return (hash & ((1 << (8 * bytes_hash)) - 1)) or 1
  
  
  def qstr_escape(qst):
      def esc_char(m):
          c = ord(m.group(0))
          try:
              name = codepoint2name[c]
          except KeyError:
              name = "0x%02x" % c
          return "_" + name + "_"
  
      return re.sub(r"[^A-Za-z0-9_]", esc_char, qst)
  
  
  def parse_input_headers(infiles):
      qcfgs = {}
      qstrs = {}
  
      # add static qstrs
      for qstr in static_qstr_list:
          # work out the corresponding qstr name
          ident = qstr_escape(qstr)
  
          # don't add duplicates
          assert ident not in qstrs
  
          # add the qstr to the list, with order number to retain original order in file
          order = len(qstrs) - 300000
          qstrs[ident] = (order, ident, qstr)
  
      # read the qstrs in from the input files
      for infile in infiles:
          with open(infile, "rt") as f:
              for line in f:
                  line = line.strip()
  
                  # is this a config line?
                  match = re.match(r"^QCFG\((.+), (.+)\)", line)
                  if match:
                      value = match.group(2)
                      if value[0] == "(" and value[-1] == ")":
                          # strip parenthesis from config value
                          value = value[1:-1]
                      qcfgs[match.group(1)] = value
                      continue
  
                  # is this a QSTR line?
                  match = re.match(r"^Q\((.*)\)$", line)
                  if not match:
                      continue
  
                  # get the qstr value
                  qstr = match.group(1)
  
                  # special cases to specify control characters
                  if qstr == "\\n":
                      qstr = "\n"
                  elif qstr == "\\r\\n":
                      qstr = "\r\n"
  
                  # work out the corresponding qstr name
                  ident = qstr_escape(qstr)
  
                  # don't add duplicates
                  if ident in qstrs:
                      continue
  
                  # add the qstr to the list, with order number to retain original order in file
                  order = len(qstrs)
                  # but put special method names like __add__ at the top of list, so
                  # that their id's fit into a byte
                  if ident == "":
                      # Sort empty qstr above all still
                      order = -200000
                  elif ident == "__dir__":
                      # Put __dir__ after empty qstr for builtin dir() to work
                      order = -190000
                  elif ident.startswith("__"):
                      order -= 100000
                  qstrs[ident] = (order, ident, qstr)
  
      if not qcfgs:
          sys.stderr.write("ERROR: Empty preprocessor output - check for errors above\n")
          sys.exit(1)
  
      return qcfgs, qstrs
  
  
  def make_bytes(cfg_bytes_len, cfg_bytes_hash, qstr):
      qbytes = bytes_cons(qstr, "utf8")
      qlen = len(qbytes)
      qhash = compute_hash(qbytes, cfg_bytes_hash)
      if all(32 <= ord(c) <= 126 and c != "\\" and c != '"' for c in qstr):
          # qstr is all printable ASCII so render it as-is (for easier debugging)
          qdata = qstr
      else:
          # qstr contains non-printable codes so render entire thing as hex pairs
          qdata = "".join(("\\x%02x" % b) for b in qbytes)
      if qlen >= (1 << (8 * cfg_bytes_len)):
          print("qstr is too long:", qstr)
          assert False
      qlen_str = ("\\x%02x" * cfg_bytes_len) % tuple(
          ((qlen >> (8 * i)) & 0xFF) for i in range(cfg_bytes_len)
      )
      qhash_str = ("\\x%02x" * cfg_bytes_hash) % tuple(
          ((qhash >> (8 * i)) & 0xFF) for i in range(cfg_bytes_hash)
      )
      return '(const byte*)"%s%s" "%s"' % (qhash_str, qlen_str, qdata)
  
  
  def print_qstr_data(qcfgs, qstrs):
      # get config variables
      cfg_bytes_len = int(qcfgs["BYTES_IN_LEN"])
      cfg_bytes_hash = int(qcfgs["BYTES_IN_HASH"])
  
      # print out the starter of the generated C header file
      print("// This file was automatically generated by makeqstrdata.py")
      print("")
  
      # add NULL qstr with no hash or data
      print(
          'QDEF(MP_QSTRnull, (const byte*)"%s%s" "")'
          % ("\\x00" * cfg_bytes_hash, "\\x00" * cfg_bytes_len)
      )
  
      # go through each qstr and print it out
      for order, ident, qstr in sorted(qstrs.values(), key=lambda x: x[0]):
          qbytes = make_bytes(cfg_bytes_len, cfg_bytes_hash, qstr)
          print("QDEF(MP_QSTR_%s, %s)" % (ident, qbytes))
  
  
  def do_work(infiles):
      qcfgs, qstrs = parse_input_headers(infiles)
      print_qstr_data(qcfgs, qstrs)
  
  
  if __name__ == "__main__":
      do_work(sys.argv[1:])
  