如何在不向每个规则添加 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');
});
我正在重写我的翻译器,这次我对测试更加严格,因为这个版本可能会存在几周以上。
因为您可以 运行 访问者从任何节点开始,所以您可以 几乎 编写像这样的漂亮的小测试...
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');
});