protected Maybe doCall()

in core/src/main/java/org/apache/brooklyn/core/workflow/ShorthandProcessorExprParser.java [123:340]


        protected Maybe<Boolean> doCall(List<ParseNodeOrValue> templateTokens, List<ParseNodeOrValue> inputTokens, int depth) {
//            boolean isEndOfOptional = false;
            outer: while (true) {
//                if (isEndOfOptional) {
//                    if (optionalDepth <= 0) {
//                        throw new IllegalStateException("Unexpected optional block closure");
//                    }
//                    optionalDepth--;
//                    if (optionalSkippingInput>0) {
//                        // we were in a block where we skipped something optional because it couldn't be matched; outer parser is now canonical,
//                        // and should stop skipping
//                        return Maybe.of(true);
//                    }
//                    isEndOfOptional = false;
//                }

                if (templateTokens.isEmpty()) {
////                    if (Strings.isNonBlank(inputRemaining) && valueUpdater==null) {
////                        return Maybe.absent("Input has trailing characters after template is matched: '" + inputRemaining + "'");
////                    }
//                    if (optionalDepth>0)
//                        return Maybe.absent("Mismatched optional marker in template");
                    return Maybe.of(true);
                }
                ParseNodeOrValue tnv = templateTokens.remove(0);

                if (tnv.isParseNodeMode(ExpressionParser.SQUARE_BRACKET)) {
                    List<ParseNodeOrValue> tt = ((ParseNode) tnv).getContents();
                    String optionalPresentVar = null;
                    chompWhitespace(tt);
                    if (!tt.isEmpty() && tt.get(0).getParseNodeMode().equals(ParseValue.MODE) &&
                            ((String)tt.get(0).getContents()).startsWith("?")) {
                        ParseNodeOrValue vt = tt.remove(0);
                        ParseNodeOrValue vt2 = tt.stream().findFirst().orElse(null);
                        if (vt2!=null && vt2.isParseNodeMode(ExpressionParser.INTERPOLATED)) {
                            ParseValue vt2c = ((ParseNode) vt2).getOnlyContent()
                                    .mapMaybe(x -> x instanceof ParseValue ? Maybe.of((ParseValue)x) : Maybe.absent())
                                    .orThrow(() -> new IllegalStateException("? after [ should be followed by optional presence variable using syntax '?${var}', not '" + vt2.getSource() + "'"));
                            optionalPresentVar = vt2c.getContents();
                            tt.remove(0);
                        } else {
                            throw new IllegalStateException("? after [ should indicate optional presence variable using syntax '?${var}', not '"+vt.getSource()+"'");
                        }
                    }
                    Maybe<Boolean> cr;
                    if (optionalSkippingInput<=0) {
                        // make a deep copy so that valueUpdater writes get replayed
                        Map<String, Object> backupResult = (Map) CollectionMerger.builder().deep(true).build().merge(MutableMap.of(), result);
                        Consumer<String> backupValueUpdater = valueUpdater;
                        List<ParseNodeOrValue> backupInputRemaining = MutableList.copyOf(inputTokens);
                        List<ParseNodeOrValue> backupTT = tt;
                        int oldDepth = optionalDepth;
                        int oldSkippingDepth = optionalSkippingInput;

                        optionalDepth++;
                        tt = MutableList.copyOf(tt);
                        tt.addAll(templateTokens);
                        // this nested call handles the bracketed nodes, and everything after, to make sure the inclusion of the optional works to the end
                        cr = doCall(tt, inputTokens, depth+1);
                        if (cr.isPresent()) {
                            if (cr.get()) {
                                // succeeded
                                templateTokens.clear();  // because the subcall handled them
                                if (optionalPresentVar != null) result.put(optionalPresentVar, true);
                                continue;
                            } else {
                                // go into next block, we'll re-run, but with optional skipping turned on
                            }
                        }
                        // restore
                        result = backupResult;
                        valueUpdater = backupValueUpdater;
                        if (optionalPresentVar!=null) result.put(optionalPresentVar, false);
                        inputTokens.clear();
                        inputTokens.addAll(backupInputRemaining);
                        tt = backupTT;
                        optionalDepth = oldDepth;
                        optionalSkippingInput = oldSkippingDepth;

                        optionalSkippingInput++;
                        optionalDepth++;
//                        tt.add(0, tnv); // put our bracket back on the head so we come back in to this block
                        cr = doCall(tt, inputTokens, depth+1);
                        if (cr.isPresent()) {
                            optionalSkippingInput--;
                            continue;
                        }
                    } else {
                        if (optionalPresentVar!=null) {
                            result.put(optionalPresentVar, false);
                            valueUpdater = null;
                        }
                        optionalDepth++;
                        cr = doCall(tt, inputTokens, depth+1);
                        if (cr.isPresent()) {
                            continue;
                        }
                    }
                    // failed
                    return cr;
                }

                if (ExpressionParser.isQuotedExpressionNode(tnv)) {
                    if (optionalSkippingInput>0) continue;

                    Maybe<String> literalM = getSingleStringContents(tnv, "inside shorthand template quote");
                    if (literalM.isAbsent()) return Maybe.castAbsent(literalM);
                    String literal = Strings.trimStart(literalM.get());
//                    boolean foundSomething = false;
                    valueUpdater: do {
                        chompWhitespace(inputTokens);
                        literal: while (!inputTokens.isEmpty()) {
                            if (!literal.isEmpty()) {
                                Maybe<String> nextInputM = getSingleStringContents(inputTokens.stream().findFirst().orElse(null), "matching literal '" + literal + "'");
                                String nextInput = Strings.trimStart(nextInputM.orNull());
                                if (nextInput == null) {
                                    break; // shouldn't happen, but if so, fall through to error below
                                }
                                if (literal.startsWith(nextInput)) {
                                    literal = Strings.removeFromStart(literal, nextInput.trim());
                                    inputTokens.remove(0);
                                    chompWhitespace(inputTokens);
                                    literal = Strings.trimStart(literal);
                                    continue literal;
                                }
                                if (nextInput.startsWith(literal)) {
                                    String putBackOnInput = nextInput.substring(literal.length());
                                    literal = "";
                                    inputTokens.remove(0);
                                    if (!putBackOnInput.isEmpty()) inputTokens.add(0, new ParseValue(putBackOnInput));
                                    else chompWhitespace(inputTokens);
                                    // go into next block
                                }
                            }
                            if (literal.isEmpty()) {
                                // literal found
                                continue outer;
                            }
                            break;
                        }
                        if (inputTokens.isEmpty()) {
                            return Maybe.absent("Literal '" + literalM.get() + "' expected, when end of input reached");
                        }
                        if (valueUpdater!=null) {
                            if (inputTokens.isEmpty()) return Maybe.absent("Literal '"+literal+"' expected, when end of input tokens reached");
                            Pair<String,Boolean> value = getNextInputTokenUpToPossibleExpectedLiteral(inputTokens, templateTokens, literal, false);
                            if (value.getRight()) valueUpdater.accept(value.getLeft());
                            // always continue, until we see the literal
//                            if (!value.getRight()) {
//                                return Maybe.of(foundSomething);
//                            }
//                            foundSomething = true;
                            continue valueUpdater;
                        }
                        return Maybe.absent("Literal '"+literal+"' expected, when encountered '"+inputTokens+"'");
                    } while (true);
                }

                if (tnv.isParseNodeMode(ExpressionParser.INTERPOLATED)) {
                    if (optionalSkippingInput>0) continue;

                    Maybe<String> varNameM = getSingleStringContents(tnv, "in template interpolated variable definition");
                    if (varNameM.isAbsent()) return Maybe.castAbsent(varNameM);
                    String varName = varNameM.get();

                    if (inputTokens.isEmpty()) return Maybe.absent("End of input when looking for variable "+varName);

                    chompWhitespace(inputTokens);
                    String value;
                    if (templateTokens.isEmpty()) {
                        // last word (whether optional or not) takes everything
                        value = getRemainderPossiblyRaw(inputTokens);
                        inputTokens.clear();

                    } else {
                        Pair<String,Boolean> valueM = getNextInputTokenUpToPossibleExpectedLiteral(inputTokens, templateTokens, null, false);
                        if (!valueM.getRight()) {
                            // if we didn't find an expression, bail out
                            return Maybe.absent("Did not find expression prior to expected literal");
                        }
                        value = valueM.getLeft();
                    }
                    boolean dotsMultipleWordMatch = varName.endsWith("...");
                    if (dotsMultipleWordMatch) varName = Strings.removeFromEnd(varName, "...");
                    String keys[] = varName.split("\\.");
                    final String tt = varName;
                    valueUpdater = v2 -> {
                        Map target = result;
                        for (int i=0; i<keys.length; i++) {
                            if (!Pattern.compile("[A-Za-z0-9_-]+").matcher(keys[i]).matches()) {
                                throw new IllegalArgumentException("Invalid variable '"+tt+"'");
                            }
                            if (i == keys.length - 1) {
                                target.compute(keys[i], (k, v) -> v == null ? v2 : v + " " + v2);
                            } else {
                                // need to make sure we have a map or null
                                target = (Map) target.compute(keys[i], (k, v) -> {
                                    if (v == null) return MutableMap.of();
                                    if (v instanceof Map) return v;
                                    return Maybe.absent("Cannot process shorthand for " + Arrays.asList(keys) + " because entry '" + k + "' is not a map (" + v + ")");
                                });
                            }
                        }
                    };
                    valueUpdater.accept(value);
                    if (!dotsMultipleWordMatch && !templateTokens.isEmpty()) valueUpdater = null;
                    continue;
                }

                if (tnv.isParseNodeMode(ExpressionParser.WHITESPACE)) {
                    chompWhitespace(templateTokens);
                    continue;
                }

                // unexpected token
                return Maybe.absent("Unexpected token in shorthand pattern '"+template+"' at "+tnv+", followed by "+templateTokens);
            }
        }