private void setSpec(String rawSortSpec, IndexFacts.Session indexFacts) {
        var tokenizer = new Tokenizer(rawSortSpec);
        while (tokenizer.skipSpaces()) {

            char orderMarker = tokenizer.peek();
            if (orderMarker == '+' || orderMarker == '-') {
                tokenizer.step();
            }
            var functionName = tokenizer.token();
            var inMissing = false;
            if (tokenizer.peek() == '(' && MISSING.equalsIgnoreCase(functionName)) {
                inMissing = true;
                tokenizer.step();
                functionName = tokenizer.token();
            }
            AttributeSorter sorter;
            if (tokenizer.peek() == '(') {
                tokenizer.step();
                if (LOWERCASE.equalsIgnoreCase(functionName)) {
                    sorter = new LowerCaseSorter(canonic(tokenizer.token(), indexFacts));
                    tokenizer.expectChar(')');
                } else if (RAW.equalsIgnoreCase(functionName)) {
                    sorter = new RawSorter(canonic(tokenizer.token(), indexFacts));
                    tokenizer.expectChar(')');
                } else if (UCA.equalsIgnoreCase(functionName)) {
                    var attrName = tokenizer.token();
                    if (tokenizer.expectChars(',', ')') == ',') {
                        UcaSorter.Strength strength = UcaSorter.Strength.UNDEFINED;
                        var locale = tokenizer.token();
                        if (tokenizer.expectChars(',', ')') == ',') {
                            var s = tokenizer.token();
                            tokenizer.expectChar(')');
                            if (STRENGTH_PRIMARY.equalsIgnoreCase(s)) {
                                strength = UcaSorter.Strength.PRIMARY;
                            } else if (STRENGTH_SECONDARY.equalsIgnoreCase(s)) {
                                strength = UcaSorter.Strength.SECONDARY;
                            } else if (STRENGTH_TERTIARY.equalsIgnoreCase(s)) {
                                strength = UcaSorter.Strength.TERTIARY;
                            } else if (STRENGTH_QUATERNARY.equalsIgnoreCase(s)) {
                                strength = UcaSorter.Strength.QUATERNARY;
                            } else if (STRENGTH_IDENTICAL.equalsIgnoreCase(s)) {
                                strength = UcaSorter.Strength.IDENTICAL;
                            } else {
                                throw new IllegalInputException("Unknown collation strength: '" + s + "'");
                            }
                        }
                        sorter = new UcaSorter(canonic(attrName, indexFacts), locale, strength);
                    } else {
                        sorter = new UcaSorter(canonic(attrName, indexFacts));
                    }
                } else {
                    if (functionName.isEmpty()) {
                        throw new IllegalInputException("No sort function specified at " + tokenizer.spec());
                    } else {
                        throw new IllegalInputException("Unknown sort function '" + functionName + "' at " + tokenizer.spec());
                    }
                }
            } else {
                sorter = new AttributeSorter(canonic(functionName, indexFacts));
            }
            Order order = Order.UNDEFINED;
            if (orderMarker == '+' || orderMarker == '-') {
                // Override in sortspec
                order = (orderMarker == '+') ? Order.ASCENDING : Order.DESCENDING;
            }
            MissingPolicy missingPolicy = MissingPolicy.DEFAULT;
            String missingValue = null;
            if (inMissing) {
                tokenizer.expectChar(',');
                missingPolicy = decodeMissingPolicy(tokenizer);
                if (missingPolicy == MissingPolicy.AS) {
                    tokenizer.expectChar(',');
                    missingValue = decodeMissingValue(tokenizer);
                }
                tokenizer.expectChar(')');
            }
            fieldOrders.add(new FieldOrder(sorter, order, missingPolicy, missingValue));
            if (tokenizer.valid()) {
                tokenizer.expectChar(' ');
            }
        }
    }
Creates a new sorting from the given string and returns it, or returns null if the argument does not contain
any sorting criteria (e.g it is null or the empty string)
container-search/src/main/java/com/yahoo/search/query/Sorting.java