Skip to content

Commit 43b01ab

Browse files
committed
#30 improved JBBPTokenizerException to show error position as marked one
1 parent ee6d620 commit 43b01ab

File tree

4 files changed

+214
-30
lines changed

4 files changed

+214
-30
lines changed

jbbp/src/main/java/com/igormaznitsa/jbbp/compiler/tokenizer/JBBPTokenizer.java

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import com.igormaznitsa.jbbp.model.JBBPFieldFloat;
2424
import com.igormaznitsa.jbbp.model.JBBPFieldString;
2525
import com.igormaznitsa.jbbp.utils.JBBPUtils;
26-
2726
import java.util.HashSet;
2827
import java.util.Iterator;
2928
import java.util.NoSuchElementException;
@@ -44,11 +43,14 @@ public final class JBBPTokenizer implements Iterable<JBBPToken>, Iterator<JBBPTo
4443
* The Pattern to break a string to tokens.
4544
* <b>WARNING! DO NOT UNESCAPE '}' AND '{' CHARS BECAUSE IT MAKES INCOMPATIBILITY WITH ANDROID!</b>
4645
*/
47-
private static final Pattern PATTERN = Pattern.compile("\\s*//.*$|\\s*(\\})|\\s*([^\\s;\\[\\]\\}\\{]+)?\\s*(?:\\[\\s*([^\\[\\]\\{\\};]+)\\s*\\])?\\s*([^\\d\\s;\\[\\]\\}\\{/][^\\s;\\[\\]\\}\\{/]*)?\\s*([\\{;])", Pattern.MULTILINE);
46+
private static final Pattern PATTERN = Pattern.compile(
47+
"\\s*//.*$|\\s*(\\})|\\s*([^\\s;\\[\\]\\}\\{]+)?\\s*(?:\\[\\s*([^\\[\\]\\{\\};]+)\\s*\\])?\\s*([^\\d\\s;\\[\\]\\}\\{/][^\\s;\\[\\]\\}\\{/]*)?\\s*([\\{;])",
48+
Pattern.MULTILINE);
4849
/**
4950
* The Pattern to break field type to parameters.
5051
*/
51-
private static final Pattern FIELD_TYPE_BREAK_PATTERN = Pattern.compile("^([<>])?([\\w][\\w$]*)(?::((?:[-]?\\d+)|(?:\\(.+\\))))?$");
52+
private static final Pattern FIELD_TYPE_BREAK_PATTERN =
53+
Pattern.compile("^([<>])?([\\w][\\w$]*)(?::((?:[-]?\\d+)|(?:\\(.+\\))))?$");
5254
/**
5355
* Inside table to keep disabled names for fields.
5456
*/
@@ -102,7 +104,8 @@ public JBBPTokenizer(final String str) {
102104
* @param str a string to be parsed, must not be null.
103105
* @param customFieldTypeProcessor custom field type processor, it can be null
104106
*/
105-
public JBBPTokenizer(final String str, final JBBPCustomFieldTypeProcessor customFieldTypeProcessor) {
107+
public JBBPTokenizer(final String str,
108+
final JBBPCustomFieldTypeProcessor customFieldTypeProcessor) {
106109
JBBPUtils.assertNotNull(str, "String must not be null");
107110

108111
if (customFieldTypeProcessor == null) {
@@ -145,9 +148,12 @@ private void readNextItem() {
145148
final String groupName = this.matcher.group(4);
146149
final String groupEnder = this.matcher.group(5);
147150

148-
final String skipString = this.processingString.substring(Math.max(this.lastCharSubstringFound, 0), matcher.start()).trim();
151+
final String skipString =
152+
this.processingString.substring(Math.max(this.lastCharSubstringFound, 0), matcher.start())
153+
.trim();
149154
if (skipString.length() != 0 && !skipString.startsWith("//")) {
150-
this.detectedException = new JBBPTokenizerException(skipString, Math.max(this.lastCharSubstringFound, 0));
155+
this.detectedException =
156+
new JBBPTokenizerException(skipString, this.processingString, Math.max(this.lastCharSubstringFound, 0));
151157
} else {
152158
JBBPTokenType type = JBBPTokenType.ATOM;
153159

@@ -157,15 +163,21 @@ private void readNextItem() {
157163
// {
158164
type = JBBPTokenType.STRUCT_START;
159165
if (groupName != null) {
160-
final int position = matcher.start() + groupWholeFound.length() - groupWholeFoundTrimmed.length();
161-
this.detectedException = new JBBPTokenizerException("Wrong structure format, it must have only name (and may be array definition)", position);
166+
final int position =
167+
matcher.start() + groupWholeFound.length() - groupWholeFoundTrimmed.length();
168+
this.detectedException = new JBBPTokenizerException(
169+
"Wrong structure format, it must have only name (and may be array definition)",
170+
this.processingString,
171+
position);
162172
return;
163173
}
164174
} else if (groupCloseStruct != null) {
165175
type = JBBPTokenType.STRUCT_END;
166176
} else if (groupTypeOrName == null) {
167-
final int position = matcher.start() + groupWholeFound.length() - groupWholeFoundTrimmed.length();
168-
this.detectedException = new JBBPTokenizerException("Detected atomic field definition without type", position);
177+
final int position =
178+
matcher.start() + groupWholeFound.length() - groupWholeFoundTrimmed.length();
179+
this.detectedException =
180+
new JBBPTokenizerException("Detected atomic field definition without type", this.processingString, position);
169181
return;
170182
}
171183

@@ -213,17 +225,23 @@ private void readNextItem() {
213225
} else if ("<".equals(groupTypeByteOrder)) {
214226
byteOrder = JBBPByteOrder.LITTLE_ENDIAN;
215227
} else {
216-
throw new Error("Illegal byte order char, unexpected error, contact developer please [" + fieldType + ']');
228+
throw new Error(
229+
"Illegal byte order char, unexpected error, contact developer please ["
230+
+ fieldType + ']');
217231
}
218232
} else {
219233
byteOrder = JBBPByteOrder.BIG_ENDIAN;
220234
}
221235

222-
parsedType = new JBBPFieldTypeParameterContainer(byteOrder, groupTypeName, groupTypeExtraField);
236+
parsedType =
237+
new JBBPFieldTypeParameterContainer(byteOrder, groupTypeName, groupTypeExtraField);
223238
}
224239

225240
if (wrongFormat) {
226-
this.detectedException = new JBBPTokenizerException("Wrong format of type definition [" + fieldType + ']', position);
241+
this.detectedException =
242+
new JBBPTokenizerException("Wrong format of type definition [" + fieldType + ']',
243+
this.processingString,
244+
position);
227245
return;
228246
}
229247
}
@@ -233,11 +251,14 @@ private void readNextItem() {
233251
}
234252
} else {
235253
if (this.lastCharSubstringFound < 0) {
236-
this.detectedException = new JBBPTokenizerException("Wrong format of whole string", 0);
254+
this.detectedException = new JBBPTokenizerException("Wrong format of whole string", this.processingString, 0);
237255
} else {
238256
final String restOfString = this.processingString.substring(this.lastCharSubstringFound);
239257
if (restOfString.trim().length() != 0) {
240-
throw new JBBPTokenizerException("Can't recognize a part of script [" + restOfString + ']', this.lastCharSubstringFound);
258+
throw new JBBPTokenizerException(
259+
"Can't recognize a part of script [" + restOfString + ']',
260+
this.processingString,
261+
this.lastCharSubstringFound);
241262
}
242263
}
243264
this.nextItem = null;
@@ -256,7 +277,7 @@ private JBBPTokenizerException checkFieldName(final String name, final int posit
256277
if (name != null) {
257278
final String normalized = JBBPUtils.normalizeFieldNameOrPath(name);
258279
if (normalized.indexOf('.') >= 0) {
259-
return new JBBPTokenizerException("Field name must not contain '.' char", position);
280+
return new JBBPTokenizerException("Field name must not contain '.' char", this.processingString, position);
260281
}
261282

262283
if (normalized.length() > 0) {
@@ -265,13 +286,13 @@ private JBBPTokenizerException checkFieldName(final String name, final int posit
265286
|| normalized.startsWith("$")
266287
|| Character.isDigit(normalized.charAt(0))
267288
) {
268-
return new JBBPTokenizerException("'" + name + "' can't be field name", position);
289+
return new JBBPTokenizerException("'" + name + "' can't be field name", this.processingString, position);
269290
}
270291

271292
for (int i = 1; i < normalized.length(); i++) {
272293
final char chr = normalized.charAt(i);
273294
if (chr != '_' && !Character.isLetterOrDigit(chr)) {
274-
return new JBBPTokenizerException("Char '" + chr + "' not allowed in name", position);
295+
return new JBBPTokenizerException("Char '" + chr + "' not allowed in name", this.processingString, position);
275296
}
276297
}
277298
}

jbbp/src/main/java/com/igormaznitsa/jbbp/exceptions/JBBPTokenizerException.java

Lines changed: 86 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,29 +17,45 @@
1717
package com.igormaznitsa.jbbp.exceptions;
1818

1919
/**
20-
* The Exception can be thrown during parsing sources for tokens and allows to figure out the position of the problematic token.
20+
* The Exception can be thrown during parsing sources for tokens and allows to figure
21+
* out the position of the problematic token.
2122
*
2223
* @since 1.0
2324
*/
2425
public class JBBPTokenizerException extends JBBPCompilationException {
25-
private static final long serialVersionUID = -1132154077305894146L;
26+
private static final long serialVersionUID = -1132154077305893246L;
2627

2728
/**
2829
* The Token position.
2930
*/
3031
private final int position;
3132

33+
private final String errorPart;
34+
3235
/**
33-
* The Constructor.
36+
* Constructor.
3437
*
3538
* @param message the exception message.
39+
* @param script the script contains error, can be null
3640
* @param pos the position of a problematic token inside sources.
41+
* @since 2.0.3
3742
*/
38-
public JBBPTokenizerException(final String message, final int pos) {
43+
public JBBPTokenizerException(final String message, final String script, final int pos) {
3944
super(message);
45+
this.errorPart = script == null ? "" : extractErrorPartText(script, pos);
4046
this.position = pos;
4147
}
4248

49+
/**
50+
* Get error part of script where error position marked by !>..<!
51+
*
52+
* @return error part of the script in position, or empty if it was impossible to extract the part
53+
* @since 2.0.3
54+
*/
55+
public String getErrorPart() {
56+
return this.errorPart;
57+
}
58+
4359
/**
4460
* get the position in sources of the problematic token.
4561
*
@@ -48,4 +64,70 @@ public JBBPTokenizerException(final String message, final int pos) {
4864
public int getPosition() {
4965
return this.position;
5066
}
67+
68+
/**
69+
* Auxiliary internal method to extract error part from script around specific position.
70+
*
71+
* @param script the error script to be processed, must not be null
72+
* @param errorPosition the error position in the script
73+
* @return error part of the script as string, must not be null
74+
* @since 2.0.3
75+
*/
76+
private static String extractErrorPartText(final String script, final int errorPosition) {
77+
if (script.length() == 0 || errorPosition >= script.length() || errorPosition < 0) {
78+
return "";
79+
}
80+
final int maxLengthWing = 16;
81+
final StringBuilder buffer = new StringBuilder();
82+
buffer.append(script.charAt(errorPosition));
83+
int errorPositionAtBuffer = 0;
84+
int leftPosition = errorPosition - 1;
85+
int rightPosition = errorPosition + 1;
86+
int leftNonSpaceCounter = 0;
87+
int rightNonSpaceCounter = 0;
88+
for (int i = 0; i < maxLengthWing; i++) {
89+
if (leftPosition >= 0) {
90+
final char chr = script.charAt(leftPosition);
91+
if (Character.isISOControl(chr)
92+
|| (i > 2 && leftNonSpaceCounter > 0 && Character.isSpaceChar(chr))) {
93+
leftPosition = -1;
94+
} else {
95+
buffer.insert(0, chr);
96+
leftNonSpaceCounter += Character.isSpaceChar(chr) ? 1 : 0;
97+
errorPositionAtBuffer++;
98+
leftPosition--;
99+
}
100+
}
101+
if (rightPosition >= 0 && rightPosition < script.length()) {
102+
final char chr = script.charAt(rightPosition);
103+
if (Character.isISOControl(chr)
104+
|| (i > 2 && rightNonSpaceCounter > 0 && Character.isSpaceChar(chr))) {
105+
rightPosition = -1;
106+
} else {
107+
buffer.append(chr);
108+
rightNonSpaceCounter += Character.isSpaceChar(chr) ? 1 : 0;
109+
rightPosition++;
110+
}
111+
}
112+
}
113+
final String errorMarkerLeft = " ->";
114+
final String errorMarkerRight = "<- ";
115+
buffer.insert(errorPositionAtBuffer + 1, errorMarkerRight);
116+
buffer.insert(errorPositionAtBuffer, errorMarkerLeft);
117+
errorPositionAtBuffer += errorMarkerLeft.length();
118+
119+
if (Character.isISOControl(buffer.charAt(errorPositionAtBuffer))) {
120+
String hex = Integer.toHexString(buffer.charAt(errorPositionAtBuffer));
121+
hex = "\\u" + "0000".substring(hex.length()) + hex;
122+
123+
buffer.delete(errorPositionAtBuffer, errorPositionAtBuffer + 1);
124+
buffer.insert(errorPositionAtBuffer, hex);
125+
}
126+
return buffer.toString().trim();
127+
}
128+
129+
@Override
130+
public String toString() {
131+
return this.getLocalizedMessage() + " (pos=" + this.position + ", errorPart=\"" + this.errorPart + "\")";
132+
}
51133
}

0 commit comments

Comments
 (0)