private Token expect(String item, String alternative) {
      if (iToken >= typeInfoTokens.size()) {
        throw new IllegalArgumentException("Error: " + item
            + " expected at the end of '" + typeInfoString + "'");
      }
      Token t = typeInfoTokens.get(iToken);
      if (item.equals("type")) {
        if (!serdeConstants.LIST_TYPE_NAME.equals(t.text)
            && !serdeConstants.MAP_TYPE_NAME.equals(t.text)
            && !serdeConstants.STRUCT_TYPE_NAME.equals(t.text)
            && !serdeConstants.UNION_TYPE_NAME.equals(t.text)
            && null == PrimitiveObjectInspectorUtils
            .getTypeEntryFromTypeName(t.text)
            && !t.text.equals(alternative)) {
          throw new IllegalArgumentException("Error: " + item
              + " expected at the position " + t.position + " of '"
              + typeInfoString + "' but '" + t.text + "' is found.");
        }
      } else if (item.equals("name")) {
        if (!t.isType && !t.text.equals(alternative)) {
          throw new IllegalArgumentException("Error: " + item
              + " expected at the position " + t.position + " of '"
              + typeInfoString + "' but '" + t.text + "' is found.");
        }
      } else {
        if (!item.equals(t.text) && !t.text.equals(alternative)) {
          throw new IllegalArgumentException("Error: " + item
              + " expected at the position " + t.position + " of '"
              + typeInfoString + "' but '" + t.text + "' is found.");
        }
      }
      iToken++;
      return t;
    }
Tokenize the typeInfoString. The rule is simple: all consecutive
alphadigits and '_', '.' are in one token, and all other characters are
one character per token.

tokenize("map<int,string>") should return
["map","<","int",",","string",">"]

Note that we add '$' in new Calcite return path. As '$' will not appear
in any type in Hive, it is safe to do so.
serde/src/java/org/apache/hadoop/hive/serde2/typeinfo/TypeInfoUtils.java