【ANTLR】【词法分析】【谓词断言】改变token的优先级

场景介绍:

定义词法文件:

lexer grammar TestLexer;

options {
    caseInsensitive = true;
}

// Lexer Rules
AGE : 'AGE' ;
PAREN_LEFT : '(';
PAREN_RIGHT : ')';
REGULAR_ID: SIMPLE_LETTER (SIMPLE_LETTER | '$' | '_' | '#' | [0-9])* ;
fragment SIMPLE_LETTER  : [\p{Letter}\p{Emoji}];

SPACES: [ \n\t\r]+ -> channel(HIDDEN);

定义语法文件:

parser grammar TestParser;

options {
    tokenVocab = TestLexer;
}

// Parser Rules
prog: expression EOF;


expression
    : age_func
    | regular_id
    ;

age_func
    : AGE PAREN_LEFT PAREN_RIGHT
    ;


regular_id
    : REGULAR_ID  // 匹配普通标识符
    ;

antlr词法解析器定义了两个token

  • AGE
  • REGULAR_ID

按照antlr词法分析的规则,当一个字符串匹配的token有两个的时候,优先匹配第一个定义的token,所以当age出现的时候永远是匹配AGE,不会匹配REGULAR_ID

针对语法文件:

  • 当用户输入age()的时候匹配:age_func语法(所以必须匹配AGE字符)
  • 当用户输入age的时候匹配:regular_id语法(必须匹配REGULAR_ID字符)

所以需要再进行词法分析的时候动态的改变优先级,这是时候就需要用到谓词断言。

谓词断言

在语法解析或者词法分析过程中插入一部分用户自定义代码,这段代码的返回值值bool

格式如下:


// 词法分析谓词断言,词法分析针对的是字符,所以是从满足token的最后一个下标开始分析
// 所以词法分析的谓词断言在后
AGE: 'AGE' {user_code()}?

// 语法分析谓词断言,语法分析针对的是上下文,只必须从第一个token开始分析断言
// 所以语法分析的谓词断言在前
expression
    : {user_code()}? age_func
    | regular_id
    ;

详细文档见:antlr中对于谓词断言的文档

所以我们需要当词法分析达到AGE的末尾的时候根据后序字符的来判断是匹配AGE这个token,可以增加nextTokenIsLeftParen函数来进行后续字符的判断(可根据不同的target语言进行修改代码)。

修改后的词法分析的代码如下

lexer grammar TestLexer;

options {
    caseInsensitive = true;
}

@lexer::members{
boolean nextTokenIsLeftParen() {
    // 记录当前扫描到的字符下标
    int index = _input.index()-1;
    boolean res = false;
    for (int i = 1;true;i++) {
        char c = (char) _input.LA(i);
        // 如果碰到我们定义的空白字符则继续扫描下一个
        if (c == '\t' || c == '\n' || c == '\t' || c == ' ') {
            continue;
        }
        // 如果碰到左括号,表示应该匹配AGE这个TOKEN
        if (c == '(') {
            res = true;
            break;
        }
        // 如果下一个合法字符不是'('则跳出循环,这个时候结果是false
        // 即不满足谓词断言,不匹配AGE
        break;
    }
    // 因为LA消费了后序字符,所以我们要把指针调回AGE的末尾
    _input.seek(index);
    return res;
}
}

// Lexer Rules
AGE : 'AGE' {nextTokenIsLeftParen()}?;
PAREN_LEFT : '(';
PAREN_RIGHT : ')';
REGULAR_ID: SIMPLE_LETTER (SIMPLE_LETTER | '$' | '_' | '#' | [0-9])* ;
fragment SIMPLE_LETTER  : [\p{Letter}\p{Emoji}];

SPACES: [ \n\t\r]+ -> channel(HIDDEN);

测试代码如下:

import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CodePointCharStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.Token;

import java.util.List;

public class Main {
    public static void main(String[] args) {
        CodePointCharStream age = CharStreams.fromString("age()");

        TestLexer testLexer = new TestLexer(age);

        CommonTokenStream commonTokenStream = new CommonTokenStream(testLexer);

        commonTokenStream.fill();

        // 打印token
        List<Token> tokens = commonTokenStream.getTokens();
        for (Token token : tokens) {
            System.out.println(token);
        }

        TestParser testParser = new TestParser(commonTokenStream);

        TestParser.ProgContext prog = testParser.prog();

        // 打印语法树
        String stringTree = prog.toStringTree(testParser);

        System.out.println(stringTree);


    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值