static TensorType fromSpec(String specString, List<String> dimensionOrder) {
        specString = specString.trim();
        if ( ! specString.startsWith(START_STRING) || ! specString.endsWith(END_STRING))
            throw formatException(specString);
        String specBody = specString.substring(START_STRING.length(), specString.length() - END_STRING.length());

        String dimensionsSpec;
        TensorType.Value valueType;
        if (specBody.startsWith("(")) {
            valueType = TensorType.Value.DOUBLE; // no value type spec: Use default
            dimensionsSpec = specBody.substring(1);
        }
        else {
            int parenthesisIndex = specBody.indexOf("(");
            if (parenthesisIndex < 0)
                throw formatException(specString);
            valueType = parseValueTypeSpec(specBody.substring(0, parenthesisIndex), specString);
            dimensionsSpec = specBody.substring(parenthesisIndex + 1);
        }

        if (dimensionsSpec.isEmpty()) return new TensorType.Builder(valueType, List.of()).build();

        List<TensorType.Dimension> dimensions = new ArrayList<>();
        for (String element : dimensionsSpec.split(",")) {
            String trimmedElement = element.trim();
            TensorType.Dimension dimension = tryParseIndexedDimension(trimmedElement);
            if (dimension == null)
                dimension = tryParseMappedDimension(trimmedElement);
            if (dimension == null)
                throw formatException(specString, "Dimension '" + element + "' is on the wrong format");
            dimensions.add(dimension);
            if (dimensionOrder != null)
                dimensionOrder.add(dimension.name());
        }
        return new TensorType.Builder(valueType, dimensions).build();
    }
@param dimensionOrder if not null, this will be populated with the dimension names in the order they are written
vespajlib/src/main/java/com/yahoo/tensor/TensorTypeParser.java