如何在不向每个规则添加 EOF 的情况下测试 ANTLR 翻译

How to test ANTLR translation without adding EOF to every rule

我正在重写我的翻译器,这次我对测试更加严格,因为这个版本可能会存在几周以上。

因为您可以 运行 访问者从任何节点开始,所以您可以 几乎 编写像这样的漂亮的小测试...

expect(parse("some test code", "startGrammarRule")).toEqual(new ASTForGrammarRule())

然后为每个访问者功能写一个(或几个)

除了您调用的规则是一个子规则,因此其中没有“EOF”,所以如果我的语法中有某个地方

numberList: NUMBER ( ',' NUMBER )* ;

... 然后 parse("1,2,3", "numberList") 只解析“1”(因为它只是一个“EOF”,这会使解析器饿到足以消耗所有字符串)。

编辑规则以添加 EOF 是不可能的。对于我为其编写测试的每条规则,我可以添加该规则的测试版本...

numberList: NUMBER ( ',' NUMBER )* ;
numberList_TEST: numberList EOF ;

...但这会使语法混乱并引起担心必须始终严格维护 _TEST 规则...

当我创建一个解析器时,我想要一个标志,该解析器动态构造该虚假 TEST 规则,然后从那里解析,或类似的东西...

有没有更好的方法来为我的解析器编写我还没有想出的测试?

在一个 Java 项目中,我正在使用自定义匹配器来检查解析的令牌是否是令牌流的 100%,如果不是,将会失败。

您似乎使用了 TypeScript 目标,因此在 TypeScript 中可能如下所示:

T.g4

grammar T;

parse      : numberList EOF;
numberList : NUMBER ( ',' NUMBER )*;

NUMBER : [0-9]+;
ID     : [a-zA-Z]+;
WS     : [ \t\r\n]+ -> channel(HIDDEN);

parserMatchers.ts

import { TLexer } from '../src/parser/TLexer';
import { BailErrorStrategy, CharStreams, CommonTokenStream } from 'antlr4ts';
import { TParser } from '../src/parser/TParser';
import { Lexer } from 'antlr4ts/Lexer';

expect.extend({
  toBeCompletelyParsedBy: (source: string, ruleName: string) => {
    const lexer = new TLexer(CharStreams.fromString(source));
    lexer.removeErrorListeners();
    const tokenStream = new CommonTokenStream(lexer);
    const parser = new TParser(tokenStream);
    parser.removeErrorListeners();
    parser.errorHandler = new BailErrorStrategy();
    const context = parser[ruleName]();

    // Collect the real tokens: non-HIDDEN and non-EOF tokens
    const realTokens = tokenStream.getTokens().filter((t) =>
      t.channel === Lexer.DEFAULT_TOKEN_CHANNEL && t.type !== Lexer.EOF);

    let indexOfStop = realTokens.indexOf(context.stop);
    let pass = realTokens.length === (indexOfStop + 1);

    let message = () => {

      if (pass) {
        return `Expected '${source}' not to be completely parsed by rule '${ruleName}', but it did.`;
      }

      let offending = realTokens[indexOfStop + 1];

      return `Expected '${source}' to be completely parsed by rule '${ruleName}', but '${offending.text}' ` +
        `(${offending.line}:${offending.charPositionInLine}) was not included!`;
    };

    return { pass, message };
  }
});

declare global {
  namespace jest {
    interface Matchers<R> {
      toBeCompletelyParsedBy(ruleName: string): R
    }
  }
}

export {};

在你的单元测试中,你现在可以这样做:

import './parserMatchers';

test('the numberList parser rule', () => {
  expect('3, 4, 5').toBeCompletelyParsedBy('numberList');
  expect('3, 4, 5 FOO').not.toBeCompletelyParsedBy('numberList');
});