private TypeInfo parseType() {

      Token t = expect("type");

      // Is this a primitive type?
      PrimitiveTypeEntry typeEntry =
          PrimitiveObjectInspectorUtils.getTypeEntryFromTypeName(t.text);
      if (typeEntry != null && typeEntry.primitiveCategory != PrimitiveCategory.UNKNOWN ) {
        String[] params = parseParams();
        switch (typeEntry.primitiveCategory) {
        case CHAR:
        case VARCHAR:
          if (params == null || params.length == 0) {
            throw new IllegalArgumentException(typeEntry.typeName
                + " type is specified without length: " + typeInfoString);
          }

          int length = 1;
          if (params.length == 1) {
            length = Integer.parseInt(params[0]);
            if (typeEntry.primitiveCategory == PrimitiveCategory.VARCHAR) {
              BaseCharUtils.validateVarcharParameter(length);
              return TypeInfoFactory.getVarcharTypeInfo(length);
            } else {
              BaseCharUtils.validateCharParameter(length);
              return TypeInfoFactory.getCharTypeInfo(length);
            }
          } else if (params.length > 1) {
            throw new IllegalArgumentException(
                "Type " + typeEntry.typeName+ " only takes one parameter, but " +
                params.length + " is seen");
          }

        case DECIMAL:
          int precision = HiveDecimal.USER_DEFAULT_PRECISION;
          int scale = HiveDecimal.USER_DEFAULT_SCALE;
          if (params == null || params.length == 0) {
            // It's possible that old metadata still refers to "decimal" as a column type w/o
            // precision/scale. In this case, the default (10,0) is assumed. Thus, do nothing here.
          } else if (params.length == 1) {
            // only precision is specified
            precision = Integer.valueOf(params[0]);
            HiveDecimalUtils.validateParameter(precision, scale);
          } else if (params.length == 2) {
            // New metadata always have two parameters.
            precision = Integer.parseInt(params[0]);
            scale = Integer.parseInt(params[1]);
            HiveDecimalUtils.validateParameter(precision, scale);
          } else if (params.length > 2) {
            throw new IllegalArgumentException("Type decimal only takes two parameter, but " +
                params.length + " is seen");
          }
          return TypeInfoFactory.getDecimalTypeInfo(precision, scale);

        default:
          return TypeInfoFactory.getPrimitiveTypeInfo(typeEntry.typeName);
        }
      }

      // Is this a list type?
      if (serdeConstants.LIST_TYPE_NAME.equals(t.text)) {
        expect("<");
        TypeInfo listElementType = parseType();
        expect(">");
        return TypeInfoFactory.getListTypeInfo(listElementType);
      }

      // Is this a map type?
      if (serdeConstants.MAP_TYPE_NAME.equals(t.text)) {
        expect("<");
        TypeInfo mapKeyType = parseType();
        expect(",");
        TypeInfo mapValueType = parseType();
        expect(">");
        return TypeInfoFactory.getMapTypeInfo(mapKeyType, mapValueType);
      }

      // Is this a struct type?
      if (serdeConstants.STRUCT_TYPE_NAME.equals(t.text)) {
        ArrayList<String> fieldNames = new ArrayList<String>();
        ArrayList<TypeInfo> fieldTypeInfos = new ArrayList<TypeInfo>();
        boolean first = true;
        do {
          if (first) {
            expect("<");
            first = false;
          } else {
            Token separator = expect(">", ",");
            if (separator.text.equals(">")) {
              // end of struct
              break;
            }
          }
          Token name = expect("name",">");
          if (name.text.equals(">")) {
            break;
          }
          fieldNames.add(name.text);
          expect(":");
          fieldTypeInfos.add(parseType());
        } while (true);

        return TypeInfoFactory.getStructTypeInfo(fieldNames, fieldTypeInfos);
      }
      // Is this a union type?
      if (serdeConstants.UNION_TYPE_NAME.equals(t.text)) {
        List<TypeInfo> objectTypeInfos = new ArrayList<TypeInfo>();
        boolean first = true;
        do {
          if (first) {
            expect("<");
            first = false;
          } else {
            Token separator = expect(">", ",");
            if (separator.text.equals(">")) {
              // end of union
              break;
            }
          }
          objectTypeInfos.add(parseType());
        } while (true);

        return TypeInfoFactory.getUnionTypeInfo(objectTypeInfos);
      }

      throw new RuntimeException("Internal error parsing position "
          + t.position + " of '" + typeInfoString + "'");
    }
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