Adds Parse Tree Verification Tests

Ported from: 326b947b55
This commit is contained in:
Bruce Markham
2020-04-04 15:24:48 -04:00
parent e10a1d3fde
commit 83395142bf
10 changed files with 250 additions and 0 deletions

View File

@@ -0,0 +1,7 @@
import { File } from './support/infrastructure/File';
import scanAndValidateParseTrees from './support/scanAndValidateParseTrees';
const INPUT_DIRECTORY: File = new File(__dirname, "../proleap-vb6/src/test/resources");
const DIRECTORIES_EXCLUDED: File[] = [ new File(INPUT_DIRECTORY.getAbsolutePath(), "io/proleap/vb6/asg") ];
scanAndValidateParseTrees(INPUT_DIRECTORY, (directory) => Boolean(DIRECTORIES_EXCLUDED.find(d => d.equals(directory))));

View File

@@ -0,0 +1,8 @@
import { VbParserError } from './VbParserError';
import { ANTLRErrorListener, Recognizer, RecognitionException } from "antlr4ts";
export class ThrowingErrorListener<TSymbol> implements ANTLRErrorListener<TSymbol> {
syntaxError<T extends TSymbol>(recognizer: Recognizer<T, any>, offendingSymbol: T | undefined, line: number, charPositionInLine: number, msg: string, e: RecognitionException | undefined): void {
throw new VbParserError("syntax error in line " + line + ":" + charPositionInLine + " " + msg);
}
}

View File

@@ -0,0 +1,82 @@
/**
* VB6 parse runner for Jest tests.
*/
import { createLogger, ILogger } from './infrastructure/Logger';
import { File } from './infrastructure/File';
import { readFileSync } from 'fs';
const readFileToString = (path: string): string => readFileSync(path, {encoding: 'utf8'});
import { StartRuleContext, VisualBasic6Parser, VisualBasic6Lexer } from '../../index';
import { ANTLRInputStream, CommonTokenStream } from "antlr4ts";
import { cleanFileTree } from './cleanFileTree';
import { IVbParserParams, VbParserParams } from './VbParserParams';
import { ThrowingErrorListener } from './ThrowingErrorListener';
import { isClazzModule, isStandardModule, isForm } from './vbFileTypes'
export interface IVbParseTestRunner {
parseFile(inputFile: File): void;
}
const LOG: ILogger = createLogger(__filename);
const TREE_SUFFIX: string = ".tree";
export class VbParseTestRunner
implements IVbParseTestRunner {
protected createDefaultParams(): IVbParserParams {
return new VbParserParams();
}
protected doCompareParseTree(treeFile: File, startRule: StartRuleContext, parser: VisualBasic6Parser): void {
const treeFileData: string = readFileToString(treeFile.getAbsolutePath());
if (treeFileData && treeFileData.length) {
LOG.info(`Comparing parse tree with file ${treeFile.getName()}.`);
it(treeFile.getName(), () => {
const inputFileTree: string = startRule.toStringTree(parser);
const cleanedInputFileTree: string = cleanFileTree(inputFileTree);
const cleanedTreeFileData: string = cleanFileTree(treeFileData);
expect(cleanedInputFileTree).toBe(cleanedTreeFileData);
});
} else {
LOG.info(`Ignoring empty parse tree file ${treeFile.getName()}.`);
}
}
protected doParse(inputFile: File, params: IVbParserParams): void {
LOG.info(`Parsing file ${inputFile.getName()}.`);
const lexer: VisualBasic6Lexer = new VisualBasic6Lexer(new ANTLRInputStream(readFileToString(inputFile.getAbsolutePath())));
if (!params.getIgnoreSyntaxErrors()) {
lexer.removeErrorListeners();
lexer.addErrorListener(new ThrowingErrorListener());
}
const tokens: CommonTokenStream = new CommonTokenStream(lexer);
const parser: VisualBasic6Parser = new VisualBasic6Parser(tokens);
if (!params.getIgnoreSyntaxErrors()) {
parser.removeErrorListeners();
parser.addErrorListener(new ThrowingErrorListener());
}
const startRule: StartRuleContext = parser.startRule();
const treeFile: File = new File(inputFile.getAbsolutePath() + TREE_SUFFIX);
if (treeFile.exists()) {
this.doCompareParseTree(treeFile, startRule, parser);
}
}
parseFile(inputFile: File): void {
if (!isClazzModule(inputFile) && !isStandardModule(inputFile) && !isForm(inputFile)) {
LOG.info(`Ignoring file ${inputFile.getName()}.`);
} else {
this.doParse(inputFile, this.createDefaultParams());
}
}
}

View File

@@ -0,0 +1,5 @@
export class VbParserError extends Error {
constructor(message: string) {
super(message)
}
}

View File

@@ -0,0 +1,9 @@
export interface IVbParserParams {
getIgnoreSyntaxErrors(): boolean;
}
export class VbParserParams implements IVbParserParams {
getIgnoreSyntaxErrors(): boolean {
return false;
}
}

View File

@@ -0,0 +1,10 @@
/**
* To be removed, as soon as the grammar does not require NEWLINEs and WS
* anymore
*/
export function cleanFileTree(input: string): string {
const inputNoEscapedNewline: string = input.replace(/\\r/g, "").replace(/\\n/g, "").replace(/\\t/g, "");;
const inputNoNewline: string = inputNoEscapedNewline.replace(/\r?\n|\r/g, "");
const inputReducedWhitespace: string = inputNoNewline.replace(/[\s]+/g, " ").replace(/[\s]+\)/, ")");
return inputReducedWhitespace;
}

View File

@@ -0,0 +1,50 @@
import * as path from 'path';
import * as fs from 'fs';
export class File {
private _path: string;
constructor(value: string, ...additionalPathPieces: string[]) {
if (additionalPathPieces && additionalPathPieces.length) {
this._path = path.resolve(process.cwd(), value, ...additionalPathPieces);
} else {
this._path = path.resolve(process.cwd(), value);
}
}
getName(): string {
return path.basename(this._path);
}
getAbsolutePath(): string {
return path.resolve(this._path);
}
exists(): boolean {
return fs.existsSync(this._path);
}
removeExtension(): string {
return path.basename(this._path, path.extname(this._path));
}
equals(other: File): boolean {
return this.getAbsolutePath() === other.getAbsolutePath();
}
isDirectory(): boolean {
return fs.statSync(this._path).isDirectory();
}
isFile(): boolean {
return fs.statSync(this._path).isFile();
}
listFiles(): File[] {
return fs.readdirSync(this._path).map(e => new File(this._path, e))
}
getParent(): string {
return path.dirname(this._path);
}
}

View File

@@ -0,0 +1,27 @@
export interface ILogger {
info(message: string): void;
}
export class ConsoleLogger implements ILogger {
private _name: string;
constructor(name: string) {
this._name = name;
}
info(message: string): void {
console.log(`${this._name}: ${message}`);
}
}
export class NullLogger implements ILogger {
constructor(name: string) {
}
info(message: string): void {
}
}
export function createLogger(name: string): ILogger {
return new NullLogger(name);
}

View File

@@ -0,0 +1,35 @@
import { File } from './infrastructure/File';
import { isClazzModule, isStandardModule, isForm } from './vbFileTypes';
import { IVbParseTestRunner, VbParseTestRunner } from './VbParseTestRunner';
function validateParseTrees(vb6InputFile: File): void {
const runner: IVbParseTestRunner = new VbParseTestRunner();
runner.parseFile(vb6InputFile);
}
export default function scanAndValidateParseTrees(inputDirectory: File, exclusionFilter: (file: File) => boolean) {
if (inputDirectory.isDirectory() && !exclusionFilter(inputDirectory)) {
describe(inputDirectory.getName(), () => {
// for each of the files in the directory
inputDirectory.listFiles().forEach(inputDirectoryFile => {
// if the file is a VB6 relevant file
if (
inputDirectoryFile.isFile()
&& (isClazzModule(inputDirectoryFile)
|| isStandardModule(inputDirectoryFile)
|| isForm(inputDirectoryFile))) {
validateParseTrees(inputDirectoryFile);
}
// else, if the file is a relevant directory
else if (inputDirectoryFile.isDirectory()) {
const subInputDirectory: File = inputDirectoryFile;
const subInputDirectoryName: string = subInputDirectory.getName();
if ("."!==subInputDirectoryName && ".."!==subInputDirectoryName) {
scanAndValidateParseTrees(subInputDirectory, exclusionFilter);
}
}
});
});
}
}

View File

@@ -0,0 +1,17 @@
import { File } from './infrastructure/File';
import { extname as getFileExtension } from 'path';
export function isClazzModule(inputFile: File): boolean {
const extension: string = getFileExtension(inputFile.getName()).toLowerCase();
return ".cls" === extension;
}
export function isStandardModule(inputFile: File): boolean {
const extension: string = getFileExtension(inputFile.getName()).toLowerCase();
return ".bas" === extension;
}
export function isForm(inputFile: File): boolean {
const extension: string = getFileExtension(inputFile.getName()).toLowerCase();
return ".frm" === extension;
}