OAL 解释器实现

OAL 解释器是基于 Antlr4 实现的,咱们先来理解下 Antlr4 

Antlr4 根本介绍

Antlr4 应用案例

参考Antlr4的应用简介这篇文章,咱们实现了一个简略的案例:antlr案例:简略的计算器,上面来讲讲这个案例。

首先,装好ANTLR v4(IDEA插件)插件,这个之后验证语法树的时候会用到。

pom.xml 中配置 antlr4 的依赖和插件

<dependency>  <groupId>org.antlr</groupId>  <artifactId>antlr4-runtime</artifactId>  <version>4.7.1</version></dependency>
<plugin>  <groupId>org.antlr</groupId>  <artifactId>antlr4-maven-plugin</artifactId>  <version>${antlr.version}</version>  <executions>    <execution>      <id>antlr</id>      <goals>        <goal>antlr4</goal>      </goals>    </execution>  </executions></plugin>

src/main/antlr4/com/switchvov/antlr/demo/calc 目录下增加一个 Calc.g4 文件

grammar Calc;   //名称须要和文件名统一root : expr EOF;   //解决问题: no viable alternative at input '<EOF>'expr    : expr '+' expr     #add   //标签会生成对应拜访办法不便咱们实现调用逻辑编写    | expr '-' expr     #sub    | INT               #int    ;INT : [0-9]+                   //定义整数    ;WS : [ \r\n\t]+ -> skip      //跳过空白类字符   ;

执行一下: mvn compile -Dmaven.test.skip=true ,在 target/generated-sources/antlr4 会生成相应的 Java 代码。

应用形式默认是监听器模式,也能够配置成访问者模式。

监听器模式:次要借助了 ParseTreeWalker 这样一个类,相当于是一个 hook ,每通过一个树的节点,便会触发对应节点的办法。益处就算是比拟不便,然而灵活性不够,不可能自主性的调用任意节点进行应用。

访问者模式:将每个数据的节点类型高度形象进去够,依据你传入的上下文类型来判断你想要拜访的是哪个节点,触发对应的办法

<font color="red">PS:论断,简略语法监听器模式就能够了,如果语法比拟灵便能够思考应用访问者模式。</font>

antlr4├── Calc.tokens├── CalcLexer.tokens└── com    └── switchvov        └── antlr            └── demo                └── calc                    ├── Calc.interp                    ├── CalcBaseListener.java # 监听模式下生成的监听器基类,实现类监听器接口,通过继承该类能够实现相应的性能                    ├── CalcLexer.interp                    ├── CalcLexer.java  # 词法解析器                    ├── CalcListener.java # 监听模式下生成的监听器接口                    └── CalcParser.java # 语法解析器

继承 com.switchvov.antlr.demo.calc.CalcBaseListener ,实现计算器相应性能

package com.switchvov.antlr.demo.calc;import java.util.ArrayDeque;import java.util.Deque;/** * @author switch * @since 2021/6/30 */public class CalcExecuteListener extends CalcBaseListener {        Deque<Integer> queue = new ArrayDeque<>(16);    @Override    public void exitInt(CalcParser.IntContext ctx) {        queue.add(Integer.parseInt(ctx.INT().getText()));    }    @Override    public void exitAdd(CalcParser.AddContext ctx) {        int r = queue.pop();        int l = queue.pop();        queue.add(l + r);    }    @Override    public void exitSub(CalcParser.SubContext ctx) {        int r = queue.pop();        int l = queue.pop();        queue.add(l - r);    }    public int result() {        return queue.pop();    }}

测试一下

package com.switchvov.antlr.demo.calc;import org.antlr.v4.runtime.CharStreams;import org.antlr.v4.runtime.CodePointCharStream;import org.antlr.v4.runtime.CommonTokenStream;import org.antlr.v4.runtime.tree.ParseTree;import org.antlr.v4.runtime.tree.ParseTreeWalker;import org.junit.Test;/** * @author switch * @since 2021/6/30 */public class CalcTest {    public static int exec(String input) {        // 读入字符串        CodePointCharStream cs = CharStreams.fromString(input);        // 词法解析        CalcLexer lexer = new CalcLexer(cs);        CommonTokenStream tokens = new CommonTokenStream(lexer);        // 语法解析        CalcParser parser = new CalcParser(tokens);        // 监听器触发获取执行后果        ParseTree tree = parser.expr();        ParseTreeWalker walker = new ParseTreeWalker();        CalcExecuteListener listener = new CalcExecuteListener();        walker.walk(listener, tree);        return listener.result();    }    @Test    public void testCalc() {        String input = "1+2";        // 输入后果:3        System.out.println(exec(input));    }}

Antlr4 IDEA 插件应用

Calc.g4 语法定义文件中,鼠标右击能够抉择 Test Rule root ,而后在 ANTLR Preview 的输入框中填入 1 + 2 就能够校验语法文件是否 OK ,并且也能够看到相应的语法树

Antlr4 在 Skywalking 的利用

通过“ Antlr4 根本介绍”一节,基本上对 Antlr4 应用有了个大略的意识。上面来看看 Skywalking 中 Antlr4 是如何应用的。

词法定义

oap-server/oal-grammar/src/main/antlr4/org/apache/skywalking/oal/rt/grammar/OALLexer.g4 文件中,咱们能看到 OAL 的词法定义

// Observability Analysis Language lexerlexer grammar OALLexer;@Header {package org.apache.skywalking.oal.rt.grammar;}// KeywordsFROM: 'from';FILTER: 'filter';DISABLE: 'disable';SRC_ALL: 'All';SRC_SERVICE: 'Service';SRC_SERVICE_INSTANCE: 'ServiceInstance';SRC_ENDPOINT: 'Endpoint';SRC_SERVICE_RELATION: 'ServiceRelation';SRC_SERVICE_INSTANCE_RELATION: 'ServiceInstanceRelation';SRC_ENDPOINT_RELATION: 'EndpointRelation';SRC_SERVICE_INSTANCE_JVM_CPU: 'ServiceInstanceJVMCPU';SRC_SERVICE_INSTANCE_JVM_MEMORY: 'ServiceInstanceJVMMemory';SRC_SERVICE_INSTANCE_JVM_MEMORY_POOL: 'ServiceInstanceJVMMemoryPool';SRC_SERVICE_INSTANCE_JVM_GC: 'ServiceInstanceJVMGC';SRC_SERVICE_INSTANCE_JVM_THREAD: 'ServiceInstanceJVMThread';SRC_SERVICE_INSTANCE_JVM_CLASS:'ServiceInstanceJVMClass';SRC_DATABASE_ACCESS: 'DatabaseAccess';SRC_SERVICE_INSTANCE_CLR_CPU: 'ServiceInstanceCLRCPU';SRC_SERVICE_INSTANCE_CLR_GC: 'ServiceInstanceCLRGC';SRC_SERVICE_INSTANCE_CLR_THREAD: 'ServiceInstanceCLRThread';SRC_ENVOY_INSTANCE_METRIC: 'EnvoyInstanceMetric';// Browser keywordsSRC_BROWSER_APP_PERF: 'BrowserAppPerf';SRC_BROWSER_APP_PAGE_PERF: 'BrowserAppPagePerf';SRC_BROWSER_APP_SINGLE_VERSION_PERF: 'BrowserAppSingleVersionPerf';SRC_BROWSER_APP_TRAFFIC: 'BrowserAppTraffic';SRC_BROWSER_APP_PAGE_TRAFFIC: 'BrowserAppPageTraffic';SRC_BROWSER_APP_SINGLE_VERSION_TRAFFIC: 'BrowserAppSingleVersionTraffic';// Constructors symbolsDOT:                                 '.';LR_BRACKET:                          '(';RR_BRACKET:                          ')';LS_BRACKET:                          '[';RS_BRACKET:                          ']';COMMA:                               ',';SEMI:                                ';';EQUAL:                               '=';DUALEQUALS:                          '==';ALL:                                 '*';GREATER:                             '>';LESS:                                '<';GREATER_EQUAL:                       '>=';LESS_EQUAL:                          '<=';NOT_EQUAL:                           '!=';LIKE:                                'like';IN:                                  'in';CONTAIN:                            'contain';NOT_CONTAIN:                        'not contain';// LiteralsBOOL_LITERAL:       'true'            |       'false'            ;NUMBER_LITERAL :   Digits+;CHAR_LITERAL:       '\'' (~['\\\r\n] | EscapeSequence) '\'';STRING_LITERAL:     '"' (~["\\\r\n] | EscapeSequence)* '"';DelimitedComment    : '/*' ( DelimitedComment | . )*? '*/'      -> channel(HIDDEN)    ;LineComment    : '//' ~[\u000A\u000D]*      -> channel(HIDDEN)    ;SPACE:                               [ \t\r\n]+    -> channel(HIDDEN);// IdentifiersIDENTIFIER:         Letter LetterOrDigit*;// Fragment rulesfragment EscapeSequence    : '\\' [btnfr"'\\]    | '\\' ([0-3]? [0-7])? [0-7]    | '\\' 'u'+ HexDigit HexDigit HexDigit HexDigit    ;fragment HexDigits    : HexDigit ((HexDigit | '_')* HexDigit)?    ;fragment HexDigit    : [0-9a-fA-F]    ;fragment Digits    : [0-9] ([0-9_]* [0-9])?    ;fragment LetterOrDigit    : Letter    | [0-9]    ;fragment Letter    : [a-zA-Z$_] // these are the "java letters" below 0x7F    | ~[\u0000-\u007F\uD800-\uDBFF] // covers all characters above 0x7F which are not a surrogate    | [\uD800-\uDBFF] [\uDC00-\uDFFF] // covers UTF-16 surrogate pairs encodings for U+10000 to U+10FFFF    ;

语法定义

oap-server/oal-grammar/src/main/antlr4/org/apache/skywalking/oal/rt/grammar/OALParser.g4 文件中,咱们能看到 OAL 的语法定义

parser grammar OALParser;@Header {package org.apache.skywalking.oal.rt.grammar;}options { tokenVocab=OALLexer; }// Top Level Descriptionroot    : (aggregationStatement | disableStatement)*    ;aggregationStatement    : variable (SPACE)? EQUAL (SPACE)? metricStatement DelimitedComment? LineComment? (SEMI|EOF)    ;disableStatement    : DISABLE LR_BRACKET disableSource RR_BRACKET DelimitedComment? LineComment? (SEMI|EOF)    ;metricStatement    : FROM LR_BRACKET source (sourceAttributeStmt+) RR_BRACKET (filterStatement+)? DOT aggregateFunction    ;filterStatement    : DOT FILTER LR_BRACKET filterExpression RR_BRACKET    ;filterExpression    : expression    ;source    : SRC_ALL | SRC_SERVICE | SRC_DATABASE_ACCESS | SRC_SERVICE_INSTANCE | SRC_ENDPOINT |      SRC_SERVICE_RELATION | SRC_SERVICE_INSTANCE_RELATION | SRC_ENDPOINT_RELATION |      SRC_SERVICE_INSTANCE_JVM_CPU | SRC_SERVICE_INSTANCE_JVM_MEMORY | SRC_SERVICE_INSTANCE_JVM_MEMORY_POOL | SRC_SERVICE_INSTANCE_JVM_GC | SRC_SERVICE_INSTANCE_JVM_THREAD | SRC_SERVICE_INSTANCE_JVM_CLASS |// JVM source of service instance      SRC_SERVICE_INSTANCE_CLR_CPU | SRC_SERVICE_INSTANCE_CLR_GC | SRC_SERVICE_INSTANCE_CLR_THREAD |      SRC_ENVOY_INSTANCE_METRIC |      SRC_BROWSER_APP_PERF | SRC_BROWSER_APP_PAGE_PERF | SRC_BROWSER_APP_SINGLE_VERSION_PERF |      SRC_BROWSER_APP_TRAFFIC | SRC_BROWSER_APP_PAGE_TRAFFIC | SRC_BROWSER_APP_SINGLE_VERSION_TRAFFIC    ;disableSource    : IDENTIFIER    ;sourceAttributeStmt    : DOT sourceAttribute    ;sourceAttribute    : IDENTIFIER | ALL    ;variable    : IDENTIFIER    ;aggregateFunction    : functionName LR_BRACKET ((funcParamExpression (COMMA funcParamExpression)?) | (literalExpression (COMMA literalExpression)?))? RR_BRACKET    ;functionName    : IDENTIFIER    ;funcParamExpression    : expression    ;literalExpression    : BOOL_LITERAL | NUMBER_LITERAL | IDENTIFIER    ;expression    : booleanMatch | stringMatch | greaterMatch | lessMatch | greaterEqualMatch | lessEqualMatch | notEqualMatch | booleanNotEqualMatch | likeMatch | inMatch | containMatch | notContainMatch    ;containMatch    : conditionAttributeStmt CONTAIN stringConditionValue    ;notContainMatch    : conditionAttributeStmt NOT_CONTAIN stringConditionValue    ;booleanMatch    : conditionAttributeStmt DUALEQUALS booleanConditionValue    ;stringMatch    :  conditionAttributeStmt DUALEQUALS (stringConditionValue | enumConditionValue)    ;greaterMatch    :  conditionAttributeStmt GREATER numberConditionValue    ;lessMatch    :  conditionAttributeStmt LESS numberConditionValue    ;greaterEqualMatch    :  conditionAttributeStmt GREATER_EQUAL numberConditionValue    ;lessEqualMatch    :  conditionAttributeStmt LESS_EQUAL numberConditionValue    ;booleanNotEqualMatch    :  conditionAttributeStmt NOT_EQUAL booleanConditionValue    ;notEqualMatch    :  conditionAttributeStmt NOT_EQUAL (numberConditionValue | stringConditionValue | enumConditionValue)    ;likeMatch    :  conditionAttributeStmt LIKE stringConditionValue    ;inMatch    :  conditionAttributeStmt IN multiConditionValue    ;multiConditionValue    : LS_BRACKET (numberConditionValue ((COMMA numberConditionValue)*) | stringConditionValue ((COMMA stringConditionValue)*) | enumConditionValue ((COMMA enumConditionValue)*)) RS_BRACKET    ;conditionAttributeStmt    : conditionAttribute ((DOT conditionAttribute)*)    ;conditionAttribute    : IDENTIFIER    ;booleanConditionValue    : BOOL_LITERAL    ;stringConditionValue    : STRING_LITERAL    ;enumConditionValue    : IDENTIFIER DOT IDENTIFIER    ;numberConditionValue    : NUMBER_LITERAL    ;

Antlr4 生成 Java 代码

oap-server/oal-grammar 下执行 mvn compile -Dmaven.test.skip=true 会在 oap-server/oal-grammar/target/generated-sources/antlr4 目录下生成相应的 Java 代码

.├── OALLexer.tokens├── OALParser.tokens└── org    └── apache        └── skywalking            └── oal                └── rt                    └── grammar                        ├── OALLexer.interp                        ├── OALLexer.java # 词法解析器                        ├── OALParser.interp                        ├── OALParser.java # 语法解析器                        ├── OALParserBaseListener.java # 监听器                        └── OALParserListener.java

Skywalking 的应用

通过“ Antlr4 应用案例”一节,能够晓得 Antlr4 有两种性能实现形式:监听器或者拜访器。

通过“ Antlr4 生成 Java 代码”一节,晓得 Skywalking 应用的是监听器模式。

Skywalking 对于 OAL 的相应的代码都在 oap-server/oal-rt 模块中。

org.apache.skywalking.oal.rt.grammar.OALParserBaseListener 的继承类坐标是 org.apache.skywalking.oal.rt.parser.OALListener 

package org.apache.skywalking.oal.rt.parser;import java.util.Arrays;import java.util.List;import org.antlr.v4.runtime.misc.NotNull;import org.apache.skywalking.oal.rt.grammar.OALParser;import org.apache.skywalking.oal.rt.grammar.OALParserBaseListener;import org.apache.skywalking.oap.server.core.source.DefaultScopeDefine;public class OALListener extends OALParserBaseListener {    private List<AnalysisResult> results;    private AnalysisResult current;    private DisableCollection collection;    private ConditionExpression conditionExpression;    private final String sourcePackage;    public OALListener(OALScripts scripts, String sourcePackage) {        this.results = scripts.getMetricsStmts();        this.collection = scripts.getDisableCollection();        this.sourcePackage = sourcePackage;    }    @Override    public void enterAggregationStatement(@NotNull OALParser.AggregationStatementContext ctx) {        current = new AnalysisResult();    }    @Override    public void exitAggregationStatement(@NotNull OALParser.AggregationStatementContext ctx) {        DeepAnalysis deepAnalysis = new DeepAnalysis();        results.add(deepAnalysis.analysis(current));        current = null;    }    @Override    public void enterSource(OALParser.SourceContext ctx) {        current.setSourceName(ctx.getText());        current.setSourceScopeId(DefaultScopeDefine.valueOf(metricsNameFormat(ctx.getText())));    }    @Override    public void enterSourceAttribute(OALParser.SourceAttributeContext ctx) {        current.getSourceAttribute().add(ctx.getText());    }    @Override    public void enterVariable(OALParser.VariableContext ctx) {    }    @Override    public void exitVariable(OALParser.VariableContext ctx) {        current.setVarName(ctx.getText());        current.setMetricsName(metricsNameFormat(ctx.getText()));        current.setTableName(ctx.getText().toLowerCase());    }    @Override    public void enterFunctionName(OALParser.FunctionNameContext ctx) {        current.setAggregationFunctionName(ctx.getText());    }    @Override    public void enterFilterStatement(OALParser.FilterStatementContext ctx) {        conditionExpression = new ConditionExpression();    }    @Override    public void exitFilterStatement(OALParser.FilterStatementContext ctx) {        current.addFilterExpressionsParserResult(conditionExpression);        conditionExpression = null;    }    @Override    public void enterFuncParamExpression(OALParser.FuncParamExpressionContext ctx) {        conditionExpression = new ConditionExpression();    }    @Override    public void exitFuncParamExpression(OALParser.FuncParamExpressionContext ctx) {        current.addFuncConditionExpression(conditionExpression);        conditionExpression = null;    }    /////////////    // Expression    ////////////    @Override    public void enterConditionAttribute(OALParser.ConditionAttributeContext ctx) {        conditionExpression.getAttributes().add(ctx.getText());    }    @Override    public void enterBooleanMatch(OALParser.BooleanMatchContext ctx) {        conditionExpression.setExpressionType("booleanMatch");    }    @Override    public void enterStringMatch(OALParser.StringMatchContext ctx) {        conditionExpression.setExpressionType("stringMatch");    }    @Override    public void enterGreaterMatch(OALParser.GreaterMatchContext ctx) {        conditionExpression.setExpressionType("greaterMatch");    }    @Override    public void enterGreaterEqualMatch(OALParser.GreaterEqualMatchContext ctx) {        conditionExpression.setExpressionType("greaterEqualMatch");    }    @Override    public void enterLessMatch(OALParser.LessMatchContext ctx) {        conditionExpression.setExpressionType("lessMatch");    }    @Override    public void enterLessEqualMatch(OALParser.LessEqualMatchContext ctx) {        conditionExpression.setExpressionType("lessEqualMatch");    }    @Override    public void enterNotEqualMatch(final OALParser.NotEqualMatchContext ctx) {        conditionExpression.setExpressionType("notEqualMatch");    }    @Override    public void enterBooleanNotEqualMatch(final OALParser.BooleanNotEqualMatchContext ctx) {        conditionExpression.setExpressionType("booleanNotEqualMatch");    }    @Override    public void enterLikeMatch(final OALParser.LikeMatchContext ctx) {        conditionExpression.setExpressionType("likeMatch");    }    @Override    public void enterContainMatch(final OALParser.ContainMatchContext ctx) {        conditionExpression.setExpressionType("containMatch");    }    @Override    public void enterNotContainMatch(final OALParser.NotContainMatchContext ctx) {        conditionExpression.setExpressionType("notContainMatch");    }    @Override    public void enterInMatch(final OALParser.InMatchContext ctx) {        conditionExpression.setExpressionType("inMatch");    }    @Override    public void enterMultiConditionValue(final OALParser.MultiConditionValueContext ctx) {        conditionExpression.enterMultiConditionValue();    }    @Override    public void exitMultiConditionValue(final OALParser.MultiConditionValueContext ctx) {        conditionExpression.exitMultiConditionValue();    }    @Override    public void enterBooleanConditionValue(OALParser.BooleanConditionValueContext ctx) {        enterConditionValue(ctx.getText());    }    @Override    public void enterStringConditionValue(OALParser.StringConditionValueContext ctx) {        enterConditionValue(ctx.getText());    }    @Override    public void enterEnumConditionValue(OALParser.EnumConditionValueContext ctx) {        enterConditionValue(ctx.getText());    }    @Override    public void enterNumberConditionValue(OALParser.NumberConditionValueContext ctx) {        conditionExpression.isNumber();        enterConditionValue(ctx.getText());    }    private void enterConditionValue(String value) {        if (value.split("\\.").length == 2 && !value.startsWith("\"")) {            // Value is an enum.            value = sourcePackage + value;        }        conditionExpression.addValue(value);    }    /////////////    // Expression end.    ////////////    @Override    public void enterLiteralExpression(OALParser.LiteralExpressionContext ctx) {        if (ctx.IDENTIFIER() == null) {            current.addFuncArg(new Argument(EntryMethod.LITERAL_TYPE, Arrays.asList(ctx.getText())));            return;        }        current.addFuncArg(new Argument(EntryMethod.IDENTIFIER_TYPE, Arrays.asList(ctx.getText().split("\\."))));    }    private String metricsNameFormat(String source) {        source = firstLetterUpper(source);        int idx;        while ((idx = source.indexOf("_")) > -1) {            source = source.substring(0, idx) + firstLetterUpper(source.substring(idx + 1));        }        return source;    }    /**     * Disable source     */    @Override    public void enterDisableSource(OALParser.DisableSourceContext ctx) {        collection.add(ctx.getText());    }    private String firstLetterUpper(String source) {        return source.substring(0, 1).toUpperCase() + source.substring(1);    }}

简略来说,就是通过监听器封装了个 org.apache.skywalking.oal.rt.parser.OALScripts 对象

package org.apache.skywalking.oal.rt.parser;import java.util.LinkedList;import java.util.List;import lombok.Getter;@Getterpublic class OALScripts {    // 解析进去的剖析后果汇合    private List<AnalysisResult> metricsStmts;    // 禁用表达式汇合    private DisableCollection disableCollection;    public OALScripts() {        metricsStmts = new LinkedList<>();        disableCollection = new DisableCollection();    }}

org.apache.skywalking.oal.rt.parser.ScriptParser 类读取 oal 文件,应用 Antlr 生成的 Java 类进行解析

package org.apache.skywalking.oal.rt.parser;import java.io.IOException;import java.io.Reader;import org.antlr.v4.runtime.CharStreams;import org.antlr.v4.runtime.CommonTokenStream;import org.antlr.v4.runtime.tree.ParseTree;import org.antlr.v4.runtime.tree.ParseTreeWalker;import org.apache.skywalking.oal.rt.grammar.OALLexer;import org.apache.skywalking.oal.rt.grammar.OALParser;/** * Script reader and parser. */public class ScriptParser {    private OALLexer lexer;    private String sourcePackage;    private ScriptParser() {    }    public static ScriptParser createFromFile(Reader scriptReader, String sourcePackage) throws IOException {        ScriptParser parser = new ScriptParser();        parser.lexer = new OALLexer(CharStreams.fromReader(scriptReader));        parser.sourcePackage = sourcePackage;        return parser;    }    public static ScriptParser createFromScriptText(String script, String sourcePackage) throws IOException {        ScriptParser parser = new ScriptParser();        parser.lexer = new OALLexer(CharStreams.fromString(script));        parser.sourcePackage = sourcePackage;        return parser;    }    public OALScripts parse() throws IOException {        OALScripts scripts = new OALScripts();        CommonTokenStream tokens = new CommonTokenStream(lexer);        OALParser parser = new OALParser(tokens);        ParseTree tree = parser.root();        ParseTreeWalker walker = new ParseTreeWalker();        walker.walk(new OALListener(scripts, sourcePackage), tree);        return scripts;    }    public void close() {    }}

参考文档

  • ANTLR官网
  • antlr4 GitHub
  • antlr4 语法案例
  • Antlr4的应用简介
  • antlr案例:简略的计算器
  • 某小伙的Antlr4学习笔记
  • ANTLR v4(IDEA插件)
分享并记录所学所见