Skip to content

Commit 842811f

Browse files
committed
Throw ANTLR errors (#421)
This guarantees (more) exceptions to be thrown. Note that it isn't sufficient to just add a default (or: minimal) catch handler to all expressions, e.g. adding that to the 'atom' expression would result in an not detailed enough message. The overriding of 'recoverFromMismatchedToken' guarantees the throwing of an exception when attempting a recover (before that would be reported but not thrown (see metafacture/metafacture-playground#84 )). - use 'FluxParseException' instead of 'RuntimeException' - add some more tests
1 parent 7b480c9 commit 842811f

File tree

3 files changed

+119
-5
lines changed

3 files changed

+119
-5
lines changed

metafacture-flux/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ dependencies {
2323
api project(':metafacture-framework')
2424
implementation project(':metafacture-commons')
2525
implementation project(':metafacture-io')
26+
implementation project(':metafacture-plumbing')
2627
antlr 'org.antlr:antlr:3.5.2'
2728
testImplementation 'junit:junit:4.12'
2829
}

metafacture-flux/src/main/antlr/org/metafacture/flux/parser/Flux.g

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,34 @@ import org.metafacture.flux.FluxParseException;
4141
package org.metafacture.flux.parser;
4242
}
4343

44+
@parser::members {
45+
// ensure throwing an exception
46+
@Override
47+
protected Object recoverFromMismatchedToken(IntStream input, int ttype, BitSet follow) throws RecognitionException {
48+
{
49+
RecognitionException e = new MismatchedTokenException(ttype, input);
50+
// if next token is what we are looking for then "delete" this token
51+
if ( mismatchIsUnwantedToken(input, ttype) ) {
52+
e = new UnwantedTokenException(ttype, input);
53+
beginResync();
54+
input.consume(); // simply delete extra token
55+
endResync();
56+
reportError(e); // report after consuming so AW sees the token in the exception
57+
// we want to return the token we're actually matching
58+
Object matchedSymbol = getCurrentInputSymbol(input);
59+
input.consume(); // move past ttype token as if all were ok
60+
}
61+
// can't recover with single token deletion, try insertion
62+
if ( mismatchIsMissingToken(input, follow) ) {
63+
Object inserted = getMissingSymbol(input, e, ttype, follow);
64+
e = new MissingTokenException(ttype, input, inserted);
65+
reportError(e); // report after inserting so AW sees the token in the exception
66+
}
67+
throw e;
68+
}
69+
}
70+
}
71+
4472
flux
4573
:
4674
varDef* flow*
@@ -88,6 +116,9 @@ tee
88116
^(SUBFLOW flowtail)+
89117
)
90118
;
119+
catch [RecognitionException re] {
120+
throw re;
121+
}
91122

92123
flowtail
93124
:
@@ -103,6 +134,9 @@ flowtail
103134
)
104135
)*
105136
;
137+
catch [RecognitionException re] {
138+
throw re;
139+
}
106140

107141
StdIn
108142
:
@@ -120,6 +154,9 @@ exp
120154
:
121155
atom ('+'^ atom)*
122156
;
157+
catch [RecognitionException re] {
158+
throw re;
159+
}
123160

124161
atom
125162
:
@@ -137,6 +174,9 @@ pipeArgs
137174
)
138175
(','! namedArg)*
139176
;
177+
catch [RecognitionException re] {
178+
throw re;
179+
}
140180

141181
namedArg
142182
:

metafacture-flux/src/test/java/org/metafacture/flux/FluxGrammarTest.java

Lines changed: 78 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@
2828
import org.antlr.runtime.RecognitionException;
2929
import org.junit.Before;
3030
import org.junit.Test;
31+
import org.metafacture.commons.reflection.ReflectionException;
3132
import org.metafacture.flux.parser.FluxProgramm;
32-
import org.metafacture.framework.MetafactureException;
3333

3434
/**
3535
* Tests for the Flux grammar.
@@ -91,7 +91,7 @@ public void shouldReplaceJavaEscapeSequences()
9191
}
9292

9393
@Test(expected = FluxParseException.class)
94-
public void issue421_shouldThrowRuntimeExceptionWhenSemicolonInFlowIsMissing()
94+
public void issue421_shouldThrowFluxParseExceptionWhenSemicolonInFlowIsMissing()
9595
throws RecognitionException, IOException {
9696
final String script = "\"test\"|print";
9797
try {
@@ -102,18 +102,91 @@ public void issue421_shouldThrowRuntimeExceptionWhenSemicolonInFlowIsMissing()
102102
}
103103
}
104104

105-
@Test(expected = RuntimeException.class)
106-
public void issue421_shouldThrowRuntimeExceptionWhenSemicolonInVarDefIsMissing()
105+
@Test(expected = FluxParseException.class)
106+
public void issue421_shouldThrowFluxParseExceptionWhenSemicolonInVarDefIsMissing()
107107
throws RecognitionException, IOException {
108108
final String script = "foo=42";
109109
try {
110110
FluxCompiler.compile(createInputStream(script), emptyMap());
111-
} catch (RuntimeException re) {
111+
} catch (FluxParseException re) {
112112
assertEquals("mismatched input '<EOF>' expecting ';' in Flux", re.getMessage());
113113
throw re;
114114
}
115115
}
116116

117+
@Test(expected = ReflectionException.class)
118+
public void issue421_shouldThrowReflectionExceptionWhenCommandIsNotFound()
119+
throws RecognitionException, IOException {
120+
final String script = "\"test\"|prin;";
121+
try {
122+
FluxCompiler.compile(createInputStream(script), emptyMap());
123+
} catch (ReflectionException re) {
124+
assertEquals("Class not found: prin", re.getMessage());
125+
throw re;
126+
}
127+
}
128+
129+
@Test(expected = FluxParseException.class)
130+
public void issue421_shouldThrowFluxParseExceptionWhenInputIsMissingAfterPipe1()
131+
throws RecognitionException, IOException {
132+
final String script = "\"test\"|";
133+
try {
134+
FluxCompiler.compile(createInputStream(script), emptyMap());
135+
} catch (FluxParseException re) {
136+
assertEquals("no viable alternative at input '<EOF>' in Flux", re.getMessage());
137+
throw re;
138+
}
139+
}
140+
141+
@Test(expected = FluxParseException.class)
142+
public void issue421_shouldThrowFluxParseExceptionWhenInputIsMissingAfterPipe2()
143+
throws RecognitionException, IOException {
144+
final String script = "\"test\"|;";
145+
try {
146+
FluxCompiler.compile(createInputStream(script), emptyMap());
147+
} catch (FluxParseException re) {
148+
assertEquals("no viable alternative at input ';' in Flux", re.getMessage());
149+
throw re;
150+
}
151+
}
152+
153+
@Test(expected = FluxParseException.class)
154+
public void issue421_shouldThrowFluxParseExceptionWhenTeeStructureOccursWithouATeeCommand()
155+
throws RecognitionException, IOException {
156+
final String script = "\"test\"|{print}{print} ;";
157+
try {
158+
FluxCompiler.compile(createInputStream(script), emptyMap());
159+
} catch (FluxParseException re) {
160+
assertEquals("Flow cannot be split without a tee-element.", re.getMessage());
161+
throw re;
162+
}
163+
}
164+
165+
@Test(expected = FluxParseException.class)
166+
public void issue421_shouldThrowFluxParseExceptionWhenTeeIsNotASender()
167+
throws RecognitionException, IOException {
168+
final String script = "\"test\"|print|object-tee|{print}{print} ;";
169+
try {
170+
FluxCompiler.compile(createInputStream(script), emptyMap());
171+
} catch (FluxParseException re) {
172+
assertEquals("org.metafacture.io.ObjectStdoutWriter is not a sender", re.getMessage());
173+
throw re;
174+
}
175+
}
176+
177+
@Test(expected = FluxParseException.class)
178+
public void issue421_shouldInsertMissingSymbolsWhenTeeIsStructurallyInvalid()
179+
throws RecognitionException, IOException {
180+
final String script = "\"test\"|object-tee|{object-tee{print{print} ;";
181+
try {
182+
FluxCompiler.compile(createInputStream(script), emptyMap());
183+
String tmp=stdoutBuffer.toString();
184+
} catch (FluxParseException re) {
185+
assertEquals("missing '}' at '{' in Flux", re.getMessage());
186+
throw re;
187+
}
188+
}
189+
117190
private ByteArrayInputStream createInputStream(String script) {
118191
return new ByteArrayInputStream(script.getBytes(StandardCharsets.UTF_8));
119192
}

0 commit comments

Comments
 (0)