All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.alipay.sofa.lookout.server.prom.ql.parse.Parser Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alipay.sofa.lookout.server.prom.ql.parse;

import com.alipay.sofa.lookout.server.prom.labels.Label;
import com.alipay.sofa.lookout.server.prom.labels.Labels;
import com.alipay.sofa.lookout.server.prom.labels.MatchType;
import com.alipay.sofa.lookout.server.prom.labels.Matcher;
import com.alipay.sofa.lookout.server.prom.ql.ast.*;
import com.alipay.sofa.lookout.server.prom.ql.func.Function;
import com.alipay.sofa.lookout.server.prom.ql.lex.Item;
import com.alipay.sofa.lookout.server.prom.ql.lex.ItemType;
import com.alipay.sofa.lookout.server.prom.ql.lex.Lexer;
import com.alipay.sofa.lookout.server.prom.ql.value.ValueType;
import com.alipay.sofa.lookout.server.prom.util.NoopUtils;
import org.apache.commons.lang3.StringUtils;

import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

import static com.alipay.sofa.lookout.server.prom.labels.Labels.MetricName;
import static com.alipay.sofa.lookout.server.prom.labels.MatchType.*;
import static com.alipay.sofa.lookout.server.prom.ql.ast.Card.*;
import static com.alipay.sofa.lookout.server.prom.ql.lex.ItemType.*;
import static com.alipay.sofa.lookout.server.prom.ql.lex.Lexer.isLabel;

/**
 * Parse an expression from a text input
 * Created by [email protected] on 2018/2/7.
 */
public class Parser {

    Lexer  lex;
    //为了缓存peek出来的值;通过peekcount方便next()的真实消费
    Item[] token = new Item[3];
    int    peekCount;

    private Parser(Lexer lexer) {
        lex = lexer;
    }

    private static Parser newParser(String input) {
        return new Parser(Lexer.lex(input));
    }

    public static Expr parseExpr(String input) {
        Parser p = newParser(input);
        Expr expr = p.parseExpr();
        p.typecheck(expr);
        return expr;
    }

    //parses the input into a metric
    public static Labels parseMetric(String input) {
        Parser p = newParser(input);
        Labels m = p.metric();
        if (p.peek().getTyp() != itemEOF) {
            p.errorf("could not parse remaining input %s...",
                p.lex.getInput().substring(p.lex.getLastPos()));
        }
        return m;
    }

    private static List parseMetricSelector(String input) {
        Parser p = newParser(input);
        String name = "";
        ItemType t = p.peek().getTyp();
        if (t == itemMetricIdentifier || t == itemIdentifier) {
            name = p.next().getVal();
        }
        VectorSelector vs = vectorSelector(p, name);
        if (p.peek().getTyp() != itemEOF) {
            p.errorf("could not parse remaining input %s...",
                p.lex.getInput().substring(p.lex.getLastPos()));
        }
        return vs.getMatchers();
    }

    // parseSeriesDesc parses the description of a time series.
    //    public static Labels parseSeriesDesc(String input) {
    //        Parser p = newParser(input);
    //        p.lex.setSeriesDesc(true);
    //        return p.parseSeriesDesc();
    //    }

    // parseStmts parses a sequence of statements from the input.
    //    private Statements parseStmts() {
    //        Statements stmts = new Statements();
    //        for (; peek().getTyp() != itemEOF; ) {
    //            if (peek().getTyp() == itemComment) {
    //                continue;
    //            }
    //            stmts.add(stmt());
    //        }
    //        return stmts;
    //    }

    private Expr parseExpr() {
        Expr expr = null;
        for (; peek().getTyp() != itemEOF;) {
            if (peek().getTyp() == itemComment) {
                continue;
            }
            if (expr != null) {
                errorf("could not parse remaining input %s...",
                    lex.getInput().substring(lex.getLastPos()));
            }
            expr = expr();
        }

        if (expr == null) {
            errorf("no expression found in input");
        }
        return expr;
    }

    // typecheck checks correct typing of the parsed statements or expression.
    void typecheck(Node node) {
        checkType(node);
    }

    // next returns the next token.
    Item next() {
        if (peekCount > 0) {
            peekCount--;
        } else {
            Item t = lex.nextItem();
            // Skip comments.
            for (; t.getTyp() == itemComment;) {
                t = lex.nextItem();
            }
            token[0] = t;
        }
        if (token[peekCount].getTyp() == itemError) {
            errorf("%s", token[peekCount].getVal());
        }
        return token[peekCount];
    }

    // peek returns but does not consume the next token.
    private Item peek() {
        if (peekCount > 0) {
            return token[peekCount - 1];
        }
        peekCount = 1;

        Item t = lex.nextItem();
        // Skip comments.
        for (; t.getTyp() == itemComment;) {
            t = lex.nextItem();
        }
        token[0] = t;
        return token[0];
    }

    // backup backs the input stream up one token.
    private void backup() {
        peekCount++;
    }

    // errorf formats the error and terminates processing.
    private void errorf(String format, Object... args) {
        error(String.format(format, args));
    }

    // error terminates processing.
    private void error(String error) {
        ParseErr perr = new ParseErr(lex.lineNumber(), lex.linePosition(), error);
        String input2 = StringUtils.trim(lex.getInput());
        int number = StringUtils.countMatches(input2, "\n");
        if (number == 0) {
            perr.line = 0;
        }
        throw new IllegalStateException(perr.toString());
    }

    // expect consumes the next token and guarantees it has the required type.
    private Item expect(ItemType exp, String context) {
        Item token = next();
        if (token.getTyp() != exp) {
            errorf("unexpected %s in %s, expected %s", token.desc(), context, exp.desc());
        }
        return token;
    }

    // expectOneOf consumes the next token and guarantees it has one of the required types.
    private Item expectOneOf(ItemType exp1, ItemType exp2, String context) {
        Item token = next();
        if (token.getTyp() != exp1 && token.getTyp() != exp2) {
            errorf("unexpected %s in %s, expected %s or %s", token.desc(), context, exp1.desc(),
                exp2.desc());
        }
        return token;
    }

    private Statement stmt() {
        Item tok = peek();
        if (tok.getTyp() == itemIdentifier || tok.getTyp() == itemMetricIdentifier) {
            return recordStmt();
        } else if (tok.getTyp() == itemAlert) {
            //alertStmt();
            throw new UnsupportedOperationException("alert stmt");
        }
        errorf("no valid statement detected");
        return null;
    }

    // recordStmt parses a recording rule.
    private RecordStmt recordStmt() {
        String ctx = "record statement";

        String name = expectOneOf(itemIdentifier, itemMetricIdentifier, ctx).getVal();
        Labels lset = null;
        //解析{}
        if (peek().getTyp() == itemLeftBrace) {
            lset = labelSet();
        }

        expect(itemAssign, ctx);
        Expr expr = expr();
        return new RecordStmt(name, expr, lset);
    }

    // expr parses any expression.
    private Expr expr() {
        // Parse the starting expression.
        Expr expr = unaryExpr();
        // Loop through the operations and construct a binary operation tree based
        // on the operators' precedence.
        ItemType op = null;
        for (; true;) {
            // If the next token is not an operator the expression is done.
            op = peek().getTyp();
            if (!op.isOperator()) {
                return expr;
            }
            next(); // Consume operator.

            // Parse optional operator matching options. Its validity
            // is checked in the type-checking stage.
            VectorMatching vecMatching = new VectorMatching(CardOneToOne);
            if (op.isSetOperator()) {
                vecMatching.setCard(CardManyToMany);
            }
            boolean returnBool = false;
            // Parse bool modifier.
            if (peek().getTyp() == itemBool) {
                if (!op.isComparisonOperator()) {
                    errorf("bool modifier can only be used on comparison operators");
                }
                next();
                returnBool = true;
            }

            // Parse ON/IGNORING clause.
            if (peek().getTyp() == itemOn || peek().getTyp() == itemIgnoring) {
                if (peek().getTyp() == itemOn) {
                    vecMatching.setOn(true);
                }
                next();
                vecMatching.setMatchingLabels(labels());

                // Parse grouping.
                ItemType t = peek().getTyp();
                if (t == itemGroupLeft || t == itemGroupRight) {
                    next();
                    if (t == itemGroupLeft) {
                        vecMatching.setCard(CardManyToOne);
                    } else {
                        vecMatching.setCard(CardOneToMany);
                    }
                    if (peek().getTyp() == itemLeftParen) {
                        vecMatching.setInclude(labels());
                    }
                }
            }

            for (String ln : vecMatching.getMatchingLabels()) {
                for (String ln2 : vecMatching.getInclude()) {
                    if (StringUtils.equals(ln, ln2) && vecMatching.isOn()) {
                        errorf("label %q must not occur in ON and GROUP clause at once", ln);
                    }
                }
            }
            // Parse the next operand.
            Expr rhs = unaryExpr();
            // Assign the new root based on the precedence of the LHS and RHS operators.
            expr = balance(expr, op, rhs, vecMatching, returnBool);
        }
    }

    private BinaryExpr balance(Expr lhs, ItemType op, Expr rhs, VectorMatching vecMatching,
                               boolean returnBool) {
        if (lhs instanceof BinaryExpr) {
            BinaryExpr lhsBE = (BinaryExpr) lhs;
            int precd = lhsBE.getOp().precedence() - op.precedence();
            if ((precd < 0) || (precd == 0 && op.isRightAssociative())) {
                BinaryExpr balanced = balance(lhsBE.getRhs(), op, rhs, vecMatching, returnBool);
                if (lhsBE.getOp().isComparisonOperator() && !lhsBE.isReturnBool()
                    && balanced.type() == ValueType.scalar
                    && lhsBE.getLhs().type() == ValueType.scalar) {
                    errorf("comparisons between scalars must use BOOL modifier");
                }
                return new BinaryExpr(lhsBE.getOp(), lhsBE.getLhs(), balanced,
                    lhsBE.getVectorMatching(), lhsBE.isReturnBool());
            }
        }

        if (op.isComparisonOperator() && !returnBool && rhs.type() == ValueType.scalar
            && lhs.type() == ValueType.scalar) {
            errorf("comparisons between scalars must use BOOL modifier");
        }
        return new BinaryExpr(op, lhs, rhs, vecMatching, returnBool);

    }

    // unaryExpr parses a unary expression.
    //
    //		 |  | (+|-)  | '('  ')'
    //
    private Expr unaryExpr() {
        Item t = peek();
        switch (t.getTyp()) {
            case itemADD:
            case itemSUB: {
                next();
                Expr e = unaryExpr();

                // Simplify unary expressions for number literals.
                // 如果是 +1 or -1
                if (e instanceof NumberLiteral) {
                    NumberLiteral nl = (NumberLiteral) e;
                    if (t.getTyp() == itemSUB) {
                        nl.setVal(nl.getVal() * -1);
                    }
                    return nl;
                }
                return new UnaryExpr(t.getTyp(), e);
            }
            case itemLeftParen: {
                next();
                Expr e = expr();
                expect(itemRightParen, "paren expression");
                return new ParenExpr(e);
            }
        }
        Expr e = primaryExpr();
        // Expression might be followed by a range selector.
        if (peek().getTyp() == itemLeftBracket) {

            if (!(e instanceof VectorSelector)) {
                errorf(
                    "range specification must be preceded by a metric selector, but follows a %T instead",
                    e);
            }
            VectorSelector vs = (VectorSelector) e;
            e = rangeSelector(vs);
        }

        // Parse optional offset.
        if (peek().getTyp() == itemOffset) {
            Duration offset = offset();
            if (e instanceof VectorSelector) {
                ((VectorSelector) e).setOffset(offset);
            } else if (e instanceof MatrixSelector) {
                ((MatrixSelector) e).setOffset(offset);

            } else {
                errorf(
                    "offset modifier must be preceded by an instant or range selector, but follows a %T instead",
                    e);
            }
        }
        return e;
    }

    // rangeSelector parses a Matrix (a.k.a. range) selector based on a given
    // Vector selector.
    //
    //		 '['  ']'
    //
    private MatrixSelector rangeSelector(VectorSelector vs) {
        String ctx = "range selector";
        next();
        String erangeStr = expect(itemDuration, ctx).getVal();
        Duration erange = parseDuration(erangeStr);
        expect(itemRightBracket, ctx);
        return new MatrixSelector(vs.getName(), vs.getMatchers(), erange);
    }

    // number parses a number.
    float number(String val) {
        try {
            float f = Float.parseFloat(val);
            return f;
        } catch (Throwable e) {
            throw new IllegalStateException("error parsing number", e);
        }

    }

    // primaryExpr parses a primary expression.
    //
    //		 |  |  | 
    //
    private Expr primaryExpr() {
        Item t = next();
        float f;
        if (t.getTyp() == itemNumber) {
            f = number(t.getVal());
            return new NumberLiteral(f);
        } else if (t.getTyp() == itemString) {
            return new StringLiteral(t.getVal());
        } else if (t.getTyp() == itemLeftBrace) {
            // Metric selector without metric name.
            backup();
            return vectorSelector(this, "");
        } else if (t.getTyp() == itemIdentifier) {
            // Check for function call.
            if (peek().getTyp() == itemLeftParen) {
                return call(t.getVal());
            }
            return vectorSelector(this, t.getVal());
        } else if (t.getTyp() == itemMetricIdentifier) {
            return vectorSelector(this, t.getVal());
        } else if (t.getTyp().isAggregator()) {
            backup();
            return aggrExpr();
        } else {
            errorf("no valid expression found");
        }

        return null;
    }

    // labels parses a list of labelnames.这个是用在ignoring 或者 on场景的吧。
    //
    //		'(' , ... ')'
    //
    private List labels() {
        String ctx = "grouping opts";
        expect(itemLeftParen, ctx);
        Item id = null;
        List labels = new ArrayList();
        if (peek().getTyp() != itemRightParen) {
            for (; true;) {
                id = next();
                if (!isLabel(id.getVal())) {
                    errorf("unexpected %s in %s, expected label", id.desc(), ctx);
                }
                labels.add(id.getVal());
                if (peek().getTyp() != itemComma) {
                    break;
                }
                next();
            }
        }
        expect(itemRightParen, ctx);

        return labels;
    }

    // aggrExpr parses an aggregation expression.
    //
    //		 () [by|without ]
    //		 [by|without ] ()
    //
    private AggregateExpr aggrExpr() {
        String ctx = "aggregation";

        Item agop = next();
        if (!agop.getTyp().isAggregator()) {
            errorf("expected aggregation operator but got %s", agop);
        }
        List grouping = new ArrayList<>();
        boolean without = false;

        boolean modifiersFirst = false;
        ItemType t = peek().getTyp();
        if (t == itemBy || t == itemWithout) {
            if (t == itemWithout) {
                without = true;
            }
            next();
            grouping = labels();
            modifiersFirst = true;
        }

        expect(itemLeftParen, ctx);
        Expr param = null;
        if (agop.getTyp().isAggregatorWithParam()) {
            param = expr();
            expect(itemComma, ctx);
        }
        Expr e = expr();
        expect(itemRightParen, ctx);

        if (!modifiersFirst) {
            t = peek().getTyp();
            if (t == itemBy || t == itemWithout) {
                if (grouping.size() > 0) {
                    errorf("aggregation must only contain one grouping clause");
                }
                if (t == itemWithout) {
                    without = true;
                }
                next();
                grouping = labels();
            }
        }

        return new AggregateExpr(agop.getTyp(), e, param, grouping, without);


    }

    // call parses a function call.

    //	 '(' [ , ...] ')'

    Call call(String name) {
        String ctx = "function call";
        Function fn = Function.getFunction(name);
        expect(itemLeftParen, ctx);
        // Might be call without args.
        if (peek().getTyp() == itemRightParen) {
            next(); // Consume.
            return new Call(fn, null);
        }

        Expressions args = new Expressions();
        for (; true;) {
            args.add(expr());
            // Terminate if no more arguments.
            if (peek().getTyp() != itemComma) {
                break;
            }
            next();
        }

        // Call must be closed.
        expect(itemRightParen, ctx);

        return new Call(fn, args);
    }

    // labelSet parses a set of label matchers
    //
    //		'{' [  '=' , ... ] '}'
    //
    Labels labelSet() {
        Labels labels = new Labels();
        // List




© 2015 - 2025 Weber Informatics LLC | Privacy Policy