Skip to content

Commit

Permalink
Make sure escape chars in a edge labels in the DOT generator are all …
Browse files Browse the repository at this point in the history
…replaced correctly

Fixes #33.
  • Loading branch information
mike-lischke committed Jan 4, 2025
1 parent 313d194 commit 18cc77d
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 13 deletions.
21 changes: 10 additions & 11 deletions src/tool/DOTGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ import {
RangeTransition, RuleStartState, RuleStopState, RuleTransition, SetTransition, StarBlockStartState,
StarLoopEntryState, StarLoopbackState,
} from "antlr4ng";
import { STGroupFile } from "stringtemplate4ts";
import { STGroupFile, IST } from "stringtemplate4ts";

import type { IST } from "stringtemplate4ts/dist/compiler/common.js";
import { Utils } from "../misc/Utils.js";
import { Grammar } from "./Grammar.js";

Expand Down Expand Up @@ -113,14 +112,14 @@ export class DOTGenerator {
throw new Error("no such template: edge");
}

edgeST.add("label", this.getEdgeLabel(String(edge)));
edgeST.add("label", this.getEdgeLabel(edge.toString()));
} else if (edge.isEpsilon) {
edgeST = DOTGenerator.stLib.getInstanceOf("epsilon-edge");
if (!edgeST) {
throw new Error("no such template: epsilon-edge");
}

edgeST.add("label", this.getEdgeLabel(String(edge)));
edgeST.add("label", this.getEdgeLabel(edge.toString()));
let loopback = false;
if (edge.target instanceof PlusBlockStartState) {
loopback = s.equals((edge.target).loopBackState);
Expand Down Expand Up @@ -183,7 +182,7 @@ export class DOTGenerator {
throw new Error("no such template: edge");
}

edgeST.add("label", this.getEdgeLabel(String(edge)));
edgeST.add("label", this.getEdgeLabel(edge.toString()));
}

edgeST.add("src", `s${s.stateNumber}`);
Expand Down Expand Up @@ -316,7 +315,7 @@ export class DOTGenerator {
return Utils.sortLinesInString(output);
}

protected getStateLabel(s: DFAState | ATNState): string {
private getStateLabel(s: DFAState | ATNState): string {
if (s instanceof DFAState) {
let buf = `s${s.stateNumber}`;

Expand Down Expand Up @@ -401,11 +400,11 @@ export class DOTGenerator {
/**
* Fix edge strings so they print out in DOT properly.
*/
protected getEdgeLabel(label: string): string {
label = label.replace("\\", "\\\\");
label = label.replace("\"", "\\\"");
label = label.replace("\n", "\\\\n");
label = label.replace("\r", "");
private getEdgeLabel(label: string): string {
label = label.replaceAll("\\", "\\\\");
label = label.replaceAll("\"", "\\\"");
label = label.replaceAll("\n", "\\\\n");
label = label.replaceAll("\r", "");

return label;
}
Expand Down
2 changes: 0 additions & 2 deletions tests/TestATNInterpreter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ describe("TestATNInterpreter", () => {

g.importVocab(lg);

//const f = new ParserATNFactory(g);
//const atn = f.createATN();
const atn = g.atn!;

const input = new MockIntTokenStream(types);
Expand Down
38 changes: 38 additions & 0 deletions tests/bugs/DotGenerator.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright (c) Mike Lischke. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*/

import { readFile } from "node:fs/promises";
import { dirname, join } from "node:path";

import { beforeAll, beforeEach, describe, expect, it } from "vitest";

import { DOTGenerator } from "../../src/tool/DOTGenerator.js";
import { Grammar, LexerGrammar } from "../../src/tool/index.js";

describe("DOTGenerator", () => {
let lexerGrammar: Grammar;
let dotGenerator: DOTGenerator;

beforeAll(async () => {
const sourcePath = join(dirname(import.meta.url), "data/abbLexer.g4").substring("file:".length);
const lexerGrammarText = await readFile(sourcePath, "utf8");
lexerGrammar = new LexerGrammar(lexerGrammarText);
lexerGrammar.tool.process(lexerGrammar, false);

});

beforeEach(() => {
dotGenerator = new DOTGenerator(lexerGrammar);
});

it("Bug #33", () => {
const rule = lexerGrammar.getRule("EscapeSequence")!;
const startState = lexerGrammar.atn!.ruleToStartState[rule.index]!;
const result = dotGenerator.getDOTFromState(startState, true);
expect(result.indexOf(`s327 -> s335 [fontsize=11, fontname="Courier", arrowsize=.7, ` +
String.raw`label = "'\\\\'", arrowhead = normal];`)).toBeGreaterThan(-1);
});

});
107 changes: 107 additions & 0 deletions tests/bugs/data/abbLexer.g4
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// $antlr-format alignTrailingComments true, columnLimit 150, maxEmptyLinesToKeep 1, reflowComments false, useTab false
// $antlr-format allowShortRulesOnASingleLine true, allowShortBlocksOnASingleLine true, minEmptyLines 0, alignSemicolons ownLine
// $antlr-format alignColons trailing, singleLineOverrulesHangingColon true, alignLexerCommands true, alignLabels true, alignTrailers true

lexer grammar abbLexer;

options {
caseInsensitive = true;
}

MODULE : 'module';
ENDMODULE : 'endmodule';
PROC : 'PROC';
ENDPROC : 'ENDPROC';
LOCAL : 'LOCAL';
CONST : 'CONST';
PERS : 'PERS';
VAR : 'VAR';
TOOLDATA : 'TOOLDATA';
WOBJDATA : 'WOBJDATA';
SPEEDDATA : 'SPEEDDATA';
ZONEDATA : 'ZONEDATA';
CLOCK : 'CLOCK';
BOOL : 'BOOL';
ON_CALL : '\\ON';
OFF_CALL : '\\OFF';

SLASH : '/';
EQUALS : ':=';
COMMA : ',';
CURLY_OPEN : '{';
CURLY_CLOSE : '}';
COLON : ':';
SEMICOLON : ';';
BRACKET_OPEN : '(';
BRACKET_CLOSE : ')';
SQUARE_OPEN : '[';
SQUARE_CLOSE : ']';
DOT : '.';
DOUBLEDOT : '..';
REL_BIGGER : '>';
REL_BIGGER_OR_EQUAL : '>=';
REL_SMALLER : '<';
REL_SMALLER_OR_EQUAL : '<=';
REL_EQUAL : '==';
REL_NOTEQUAL : '<>';
PLUS : '+';
MINUS : '-';
MULTIPLY : '*';
PERCENT : '%';
HASH : '#';

WS: (' ' | '\t' | '\u000C') -> skip;

NEWLINE: '\r'? '\n';

LINE_COMMENT: '!' ~ ('\n' | '\r')* -> skip;

BOOLLITERAL: 'FALSE' | 'TRUE';

CHARLITERAL: '\'' (EscapeSequence | ~ ('\'' | '\\' | '\r' | '\n')) '\'';

STRINGLITERAL: '"' (EscapeSequence | ~ ('\\' | '"' | '\r' | '\n'))* '"';

fragment EscapeSequence:
'\\' (
'b'
| 't'
| 'n'
| 'f'
| 'r'
| '"'
| '\''
| '\\'
| '0' .. '3' '0' .. '7' '0' .. '7'
| '0' .. '7' '0' .. '7'
| '0' .. '7'
)
;

FLOATLITERAL:
('0' .. '9')+ '.' ('0' .. '9')* Exponent?
| '.' ('0' .. '9')+ Exponent?
| ('0' .. '9')+ Exponent
;

fragment Exponent: 'E' ('+' | '-')? ('0' .. '9')+;

INTLITERAL: ('0' .. '9')+ | HexPrefix HexDigit+ HexSuffix | BinPrefix BinDigit+ BinSuffix;

fragment HexPrefix: '\'' 'H';

fragment HexDigit: '0' .. '9' | 'A' .. 'F';

fragment HexSuffix: '\'';

fragment BinPrefix: '\'' 'B';

fragment BinDigit: '0' | '1';

fragment BinSuffix: '\'';

IDENTIFIER: IdentifierStart IdentifierPart*;

fragment IdentifierStart: 'A' .. 'Z' | '_';

fragment IdentifierPart: IdentifierStart | '0' .. '9';

0 comments on commit 18cc77d

Please sign in to comment.