Improved error message reporting

This commit is contained in:
moandji.ezana 2015-02-11 17:59:20 +02:00
parent c4027ed2d5
commit c0e78db681
16 changed files with 153 additions and 237 deletions

View file

@ -1,6 +1,5 @@
package com.moandjiezana.toml;
import static com.moandjiezana.toml.ValueConverterUtils.INVALID;
import static com.moandjiezana.toml.ValueConverters.CONVERTERS;
import java.util.ArrayList;
@ -17,34 +16,15 @@ class ArrayConverter implements ValueConverter {
}
@Override
public Object convert(String s) {
AtomicInteger sharedIndex = new AtomicInteger();
Object converted = convert(s, sharedIndex);
char[] chars = s.toCharArray();
for (int i = sharedIndex.incrementAndGet(); i < chars.length; i++) {
char c = chars[i];
if (c == '#') {
break;
}
if (!Character.isWhitespace(c)) {
return INVALID;
}
}
return converted;
}
@Override
public Object convert(String s, AtomicInteger index) {
public Object convert(String s, AtomicInteger index, Context context) {
AtomicInteger line = context.line;
int startLine = line.get();
int startIndex = index.get();
char[] chars = s.toCharArray();
List<Object> arrayItems = new ArrayList<Object>();
boolean terminated = false;
boolean inComment = false;
Results.Errors errors = new Results.Errors();
for (int i = index.incrementAndGet(); i < chars.length; i = index.incrementAndGet()) {
@ -54,31 +34,40 @@ class ArrayConverter implements ValueConverter {
inComment = true;
} else if (c == '\n') {
inComment = false;
line.incrementAndGet();
} else if (inComment || Character.isWhitespace(c) || c == ',') {
continue;
} else if (c == '[') {
arrayItems.add(convert(s, index));
Object converted = convert(s, index, context);
if (converted instanceof Results.Errors) {
errors.add((Results.Errors) converted);
} else if (!isHomogenousArray(converted, arrayItems)) {
errors.heterogenous(context.identifier.getName(), line.get());
} else {
arrayItems.add(converted);
}
continue;
} else if (c == ']') {
terminated = true;
break;
} else {
arrayItems.add(CONVERTERS.convert(s, index));
Object converted = CONVERTERS.convert(s, index, context);
if (converted instanceof Results.Errors) {
errors.add((Results.Errors) converted);
} else if (!isHomogenousArray(converted, arrayItems)) {
errors.heterogenous(context.identifier.getName(), line.get());
} else {
arrayItems.add(converted);
}
}
}
if (!terminated) {
return ValueConverterUtils.unterminated(s.substring(startIndex, s.length()));
errors.unterminated(context.identifier.getName(), s.substring(startIndex, s.length()), startLine);
}
for (Object arrayItem : arrayItems) {
if (arrayItem == INVALID) {
return INVALID;
}
if (!isHomogenousArray(arrayItem, arrayItems)) {
return INVALID;
}
if (errors.hasErrors()) {
return errors;
}
return arrayItems;

View file

@ -1,8 +1,5 @@
package com.moandjiezana.toml;
import static com.moandjiezana.toml.ValueConverterUtils.INVALID;
import static com.moandjiezana.toml.ValueConverterUtils.isComment;
import java.util.concurrent.atomic.AtomicInteger;
@ -16,19 +13,7 @@ class BooleanConverter implements ValueConverter {
}
@Override
public Object convert(String s) {
AtomicInteger index = new AtomicInteger();
Object converted = convert(s, index);
if (!isComment(s.substring(index.incrementAndGet()))) {
return INVALID;
}
return converted;
}
@Override
public Object convert(String s, AtomicInteger index) {
public Object convert(String s, AtomicInteger index, Context context) {
s = s.substring(index.get());
Boolean b = s.startsWith("true") ? Boolean.TRUE : Boolean.FALSE;

View file

@ -0,0 +1,13 @@
package com.moandjiezana.toml;
import java.util.concurrent.atomic.AtomicInteger;
class Context {
final Identifier identifier;
final AtomicInteger line;
public Context(Identifier identifier, AtomicInteger line) {
this.identifier = identifier;
this.line = line;
}
}

View file

@ -1,8 +1,5 @@
package com.moandjiezana.toml;
import static com.moandjiezana.toml.ValueConverterUtils.INVALID;
import static com.moandjiezana.toml.ValueConverterUtils.isComment;
import java.text.SimpleDateFormat;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
@ -37,40 +34,7 @@ class DateConverter implements ValueConverter {
}
@Override
public Object convert(String s) {
Matcher matcher = DATE_REGEX.matcher(s);
matcher.matches();
if (!isComment(matcher.group(4))) {
return INVALID;
}
s = matcher.group(1);
String zone = matcher.group(3);
String fractionalSeconds = matcher.group(2);
String format = "yyyy-MM-dd'T'HH:mm:ss";
if (fractionalSeconds != null && !fractionalSeconds.isEmpty()) {
format += ".SSS";
s += fractionalSeconds;
}
format += "Z";
if ("Z".equals(zone)) {
s += "+0000";
} else if (zone.contains(":")) {
s += zone.replace(":", "");
}
try {
SimpleDateFormat dateFormat = new SimpleDateFormat(format);
dateFormat.setLenient(false);
return dateFormat.parse(s);
} catch (Exception e) {
return INVALID;
}
}
@Override
public Object convert(String original, AtomicInteger index) {
public Object convert(String original, AtomicInteger index, Context context) {
StringBuilder sb = new StringBuilder();
for (int i = index.get(); i < original.length(); i = index.incrementAndGet()) {
@ -87,7 +51,9 @@ class DateConverter implements ValueConverter {
Matcher matcher = DATE_REGEX.matcher(s);
if (!matcher.matches()) {
return INVALID;
Results.Errors errors = new Results.Errors();
errors.invalidValue(context.identifier.getName(), s, context.line.get());
return errors;
}
String dateString = matcher.group(1);
@ -110,7 +76,9 @@ class DateConverter implements ValueConverter {
dateFormat.setLenient(false);
return dateFormat.parse(dateString);
} catch (Exception e) {
return INVALID;
Results.Errors errors = new Results.Errors();
errors.invalidValue(context.identifier.getName(), s, context.line.get());
return errors;
}
}

View file

@ -1,6 +1,5 @@
package com.moandjiezana.toml;
import static com.moandjiezana.toml.ValueConverterUtils.INVALID;
import static com.moandjiezana.toml.ValueConverters.CONVERTERS;
import java.util.HashMap;
@ -16,20 +15,10 @@ class InlineTableConverter implements ValueConverter {
}
@Override
public Object convert(String s) {
AtomicInteger index = new AtomicInteger();
Object converted = convert(s, index);
String substring = s.substring(index.incrementAndGet());
if (converted == INVALID || !ValueConverterUtils.isComment(substring)) {
return INVALID;
}
return converted;
}
@Override
public Object convert(String s, AtomicInteger sharedIndex) {
public Object convert(String s, AtomicInteger sharedIndex, Context context) {
AtomicInteger line = context.line;
int startLine = line.get();
int startIndex = sharedIndex.get();
char[] chars = s.toCharArray();
boolean inKey = true;
boolean inValue = false;
@ -37,6 +26,7 @@ class InlineTableConverter implements ValueConverter {
boolean terminated = false;
StringBuilder currentKey = new StringBuilder();
HashMap<String, Object> results = new HashMap<String, Object>();
Results.Errors errors = new Results.Errors();
for (int i = sharedIndex.incrementAndGet(); sharedIndex.get() < chars.length; i = sharedIndex.incrementAndGet()) {
char c = chars[i];
@ -47,10 +37,11 @@ class InlineTableConverter implements ValueConverter {
} else if (quoted) {
currentKey.append(c);
} else if (inValue && !Character.isWhitespace(c)) {
Object converted = CONVERTERS.convert(s, sharedIndex);
Object converted = CONVERTERS.convert(s, sharedIndex, new Context(new Identifier(currentKey.toString()), context.line));
if (converted == INVALID) {
return INVALID;
if (converted instanceof Results.Errors) {
errors.add((Results.Errors) converted);
return errors;
}
results.put(currentKey.toString().trim(), converted);
@ -58,10 +49,11 @@ class InlineTableConverter implements ValueConverter {
inValue = false;
} else if (c == '{') {
sharedIndex.incrementAndGet();
Object converted = convert(s, sharedIndex);
Object converted = convert(s, sharedIndex, new Context(new Identifier(currentKey.toString()), context.line));
if (converted == INVALID) {
return INVALID;
if (converted instanceof Results.Errors) {
errors.add((Results.Errors) converted);
return errors;
}
results.put(currentKey.toString().trim(), converted);
@ -85,7 +77,11 @@ class InlineTableConverter implements ValueConverter {
}
if (!terminated) {
return INVALID;
errors.unterminated(context.identifier.getName(), s.substring(startIndex), startLine);
}
if (errors.hasErrors()) {
return errors;
}
return results;

View file

@ -1,8 +1,5 @@
package com.moandjiezana.toml;
import static com.moandjiezana.toml.ValueConverterUtils.INVALID;
import static com.moandjiezana.toml.ValueConverterUtils.isComment;
import java.util.concurrent.atomic.AtomicInteger;
@ -16,19 +13,8 @@ class LiteralStringConverter implements ValueConverter {
}
@Override
public Object convert(String s) {
AtomicInteger index = new AtomicInteger();
Object converted = convert(s, index);
if (converted == INVALID || !isComment(s.substring(index.incrementAndGet()))) {
return INVALID;
}
return converted;
}
@Override
public Object convert(String s, AtomicInteger index) {
public Object convert(String s, AtomicInteger index, Context context) {
int startLine = context.line.get();
char[] chars = s.toCharArray();
boolean terminated = false;
int startIndex = index.incrementAndGet();
@ -43,7 +29,9 @@ class LiteralStringConverter implements ValueConverter {
}
if (!terminated) {
return INVALID;
Results.Errors errors = new Results.Errors();
errors.unterminated(context.identifier.getName(), s.substring(startIndex), startLine);
return errors;
}
String substring = s.substring(startIndex, index.get());

View file

@ -1,8 +1,5 @@
package com.moandjiezana.toml;
import static com.moandjiezana.toml.ValueConverterUtils.INVALID;
import static com.moandjiezana.toml.ValueConverterUtils.isComment;
import java.util.concurrent.atomic.AtomicInteger;
class MultilineLiteralStringConverter implements ValueConverter {
@ -15,19 +12,9 @@ class MultilineLiteralStringConverter implements ValueConverter {
}
@Override
public Object convert(String s) {
AtomicInteger index = new AtomicInteger();
Object converted = convert(s, index);
if (converted == INVALID || !isComment(s.substring(index.incrementAndGet()))) {
return INVALID;
}
return converted;
}
@Override
public Object convert(String s, AtomicInteger index) {
public Object convert(String s, AtomicInteger index, Context context) {
AtomicInteger line = context.line;
int startLine = line.get();
char[] chars = s.toCharArray();
int originalStartIndex = index.get();
int startIndex = index.addAndGet(3);
@ -35,11 +22,16 @@ class MultilineLiteralStringConverter implements ValueConverter {
if (chars[startIndex] == '\n') {
startIndex = index.incrementAndGet();
line.incrementAndGet();
}
for (int i = startIndex; i < chars.length; i = index.incrementAndGet()) {
char c = chars[i];
if (c == '\n') {
line.incrementAndGet();
}
if (c == '\'' && chars.length > i + 2 && chars[i + 1] == '\'' && chars[i + 2] == '\'') {
endIndex = i;
index.addAndGet(2);
@ -48,7 +40,9 @@ class MultilineLiteralStringConverter implements ValueConverter {
}
if (endIndex == -1) {
return ValueConverterUtils.unterminated(s.substring(originalStartIndex, s.length()));
Results.Errors errors = new Results.Errors();
errors.unterminated(context.identifier.getName(), s.substring(originalStartIndex), startLine);
return errors;
}
return s.substring(startIndex, endIndex);

View file

@ -1,9 +1,5 @@
package com.moandjiezana.toml;
import static com.moandjiezana.toml.ValueConverterUtils.INVALID;
import static com.moandjiezana.toml.ValueConverterUtils.isComment;
import static com.moandjiezana.toml.ValueConverterUtils.unterminated;
import java.util.concurrent.atomic.AtomicInteger;
class MultilineStringConverter implements ValueConverter {
@ -16,19 +12,9 @@ class MultilineStringConverter implements ValueConverter {
}
@Override
public Object convert(String s) {
AtomicInteger index = new AtomicInteger();
Object converted = convert(s, index);
if (converted == INVALID || !isComment(s.substring(index.incrementAndGet()))) {
return INVALID;
}
return converted;
}
@Override
public Object convert(String s, AtomicInteger index) {
public Object convert(String s, AtomicInteger index, Context context) {
AtomicInteger line = context.line;
int startLine = line.get();
char[] chars = s.toCharArray();
int originalStartIndex = index.get();
int startIndex = index.addAndGet(3);
@ -36,12 +22,15 @@ class MultilineStringConverter implements ValueConverter {
if (chars[startIndex] == '\n') {
startIndex = index.incrementAndGet();
line.incrementAndGet();
}
for (int i = startIndex; i < chars.length; i = index.incrementAndGet()) {
char c = chars[i];
if (c == '"' && chars.length > i + 2 && chars[i + 1] == '"' && chars[i + 2] == '"') {
if (c == '\n') {
line.incrementAndGet();
} else if (c == '"' && chars.length > i + 2 && chars[i + 1] == '"' && chars[i + 2] == '"') {
endIndex = i;
index.addAndGet(2);
break;
@ -49,7 +38,9 @@ class MultilineStringConverter implements ValueConverter {
}
if (endIndex == -1) {
return unterminated(s.substring(originalStartIndex, s.length()));
Results.Errors errors = new Results.Errors();
errors.unterminated(context.identifier.getName(), s.substring(originalStartIndex), startLine);
return errors;
}
s = s.substring(startIndex, endIndex);

View file

@ -1,8 +1,5 @@
package com.moandjiezana.toml;
import static com.moandjiezana.toml.ValueConverterUtils.INVALID;
import static com.moandjiezana.toml.ValueConverterUtils.isComment;
import java.util.concurrent.atomic.AtomicInteger;
class NumberConverter implements ValueConverter {
@ -16,19 +13,7 @@ class NumberConverter implements ValueConverter {
}
@Override
public Object convert(String s) {
AtomicInteger index = new AtomicInteger();
Object converted = convert(s, index);
if (converted == INVALID || (s.length() > index.get() + 1 && !isComment(s.substring(index.incrementAndGet())))) {
return INVALID;
}
return converted;
}
@Override
public Object convert(String s, AtomicInteger index) {
public Object convert(String s, AtomicInteger index, Context context) {
char[] chars = s.toCharArray();
boolean signable = true;
boolean dottable = false;
@ -86,7 +71,9 @@ class NumberConverter implements ValueConverter {
return Double.parseDouble(exponentString[0]) * Math.pow(10, Double.parseDouble(exponentString[1]));
} else {
return INVALID;
Results.Errors errors = new Results.Errors();
errors.invalidValue(context.identifier.getName(), sb.toString(), context.line.get());
return errors;
}
}
}

View file

@ -116,6 +116,13 @@ class Results {
.append('\n');
}
public void heterogenous(String key, int line) {
sb.append(key)
.append(" becomes a heterogeneous array on line ")
.append(line)
.append('\n');
}
boolean hasErrors() {
return sb.length() > 0;
}
@ -124,6 +131,10 @@ class Results {
public String toString() {
return sb.toString();
}
public void add(Errors other) {
sb.append(other.sb);
}
}
Set<String> tables = new HashSet<String>();
final Errors errors = new Errors();

View file

@ -1,8 +1,5 @@
package com.moandjiezana.toml;
import static com.moandjiezana.toml.ValueConverterUtils.INVALID;
import static com.moandjiezana.toml.ValueConverterUtils.isComment;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -18,19 +15,7 @@ class StringConverter implements ValueConverter {
}
@Override
public Object convert(String value) {
AtomicInteger index = new AtomicInteger();
Object converted = convert(value, index);
if (converted == INVALID || !isComment(value.substring(index.incrementAndGet()))) {
return INVALID;
}
return converted;
}
@Override
public Object convert(String value, AtomicInteger sharedIndex) {
public Object convert(String value, AtomicInteger sharedIndex, Context context) {
int startIndex = sharedIndex.incrementAndGet();
int endIndex = -1;
char[] chars = value.toCharArray();
@ -44,15 +29,19 @@ class StringConverter implements ValueConverter {
}
if (endIndex == -1) {
return INVALID;
Results.Errors errors = new Results.Errors();
errors.unterminated(context.identifier.getName(), value.substring(startIndex - 1), context.line.get());
return errors;
}
value = value.substring(startIndex, endIndex);
value = replaceUnicodeCharacters(value);
String raw = value.substring(startIndex, endIndex);
value = replaceUnicodeCharacters(raw);
value = replaceSpecialCharacters(value);
if (value == null) {
return INVALID;
Results.Errors errors = new Results.Errors();
errors.invalidValue(context.identifier.getName(), raw, context.line.get());
return errors;
}
return value;

View file

@ -1,12 +1,9 @@
package com.moandjiezana.toml;
import static com.moandjiezana.toml.IdentifierConverter.IDENTIFIER_CONVERTER;
import static com.moandjiezana.toml.ValueConverterUtils.INVALID;
import java.util.concurrent.atomic.AtomicInteger;
import com.moandjiezana.toml.ValueConverterUtils.Unterminated;
class TomlParser {
Results run(String tomlString) {
@ -52,14 +49,11 @@ class TomlParser {
value = null;
line.incrementAndGet();
} else if (!inComment && identifier != null && identifier.isKey() && value == null && !Character.isWhitespace(c)) {
int startIndex = index.get();
Object converted = ValueConverters.CONVERTERS.convert(tomlString, index);
Object converted = ValueConverters.CONVERTERS.convert(tomlString, index, new Context(identifier, line));
value = converted;
if (converted == INVALID) {
results.errors.invalidValue(identifier.getName(), tomlString.substring(startIndex, Math.min(index.get(), tomlString.length() - 1)), line.get());
} else if (converted instanceof Unterminated) {
results.errors.unterminated(identifier.getName(), ((Unterminated) converted).payload, line.get());
if (converted instanceof Results.Errors) {
results.errors.add((Results.Errors) converted);
} else {
results.addValue(identifier.getName(), converted);
}

View file

@ -9,18 +9,13 @@ interface ValueConverter {
*/
boolean canConvert(String s);
/**
* @param s must already have been validated by {@link #canConvert(String)}
* @return a value or {@link ValueConverterUtils#INVALID}
*/
Object convert(String s);
/**
* Partial validation. Stops after type terminator, rather than at EOI.
*
* @param s must already have been validated by {@link #canConvert(String)}
* @param index where to start in s
* @return a value or {@link ValueConverterUtils#INVALID}
* @param line current line number, used for error reporting
* @return a value or a {@link Results.Errors}
*/
Object convert(String s, AtomicInteger index);
Object convert(String s, AtomicInteger index, Context context);
}

View file

@ -2,19 +2,6 @@ package com.moandjiezana.toml;
class ValueConverterUtils {
static final Object INVALID = new Object();
static Unterminated unterminated(String payload) {
return new Unterminated(payload);
}
static class Unterminated {
final String payload;
private Unterminated(String payload) {
this.payload = payload;
}
}
static boolean isComment(String line) {
if (line == null || line.isEmpty()) {

View file

@ -9,7 +9,6 @@ import static com.moandjiezana.toml.MultilineLiteralStringConverter.MULTILINE_LI
import static com.moandjiezana.toml.MultilineStringConverter.MULTILINE_STRING_PARSER;
import static com.moandjiezana.toml.NumberConverter.NUMBER_PARSER;
import static com.moandjiezana.toml.StringConverter.STRING_PARSER;
import static com.moandjiezana.toml.ValueConverterUtils.INVALID;
import java.util.concurrent.atomic.AtomicInteger;
@ -21,15 +20,17 @@ class ValueConverters {
MULTILINE_STRING_PARSER, MULTILINE_LITERAL_STRING_CONVERTER, LITERAL_STRING_PARSER, STRING_PARSER, DATE_PARSER, NUMBER_PARSER, BOOLEAN_PARSER, ARRAY_PARSER, INLINE_TABLE_PARSER
};
Object convert(String value, AtomicInteger index) {
Object convert(String value, AtomicInteger index, Context context) {
String substring = value.substring(index.get());
for (ValueConverter valueParser : PARSERS) {
if (valueParser.canConvert(substring)) {
return valueParser.convert(value, index);
return valueParser.convert(value, index, context);
}
}
return INVALID;
Results.Errors errors = new Results.Errors();
errors.invalidValue(context.identifier.getName(), substring, context.line.get());
return errors;
}
private ValueConverters() {}

View file

@ -85,4 +85,32 @@ public class ErrorMessagesTest {
new Toml().parse("\nk\n=3");
}
@Test
public void should_display_correct_line_number_with_literal_multiline_string() throws Exception {
e.expectMessage("on line 7");
new Toml().parse("[table]\n\n k = '''abc\n\ndef\n'''\n # comment \n j = 4.\n l = 5");
}
@Test
public void should_display_correct_line_number_with_multiline_string() throws Exception {
e.expectMessage("on line 8");
new Toml().parse("[table]\n\n k = \"\"\"\nabc\n\ndef\n\"\"\"\n # comment \n j = 4.\n l = 5");
}
@Test
public void should_display_correct_line_number_with_array() throws Exception {
e.expectMessage("on line 9");
new Toml().parse("[table]\n\n k = [\"\"\"\nabc\n\ndef\n\"\"\"\n, \n # comment \n j = 4.,\n l = 5\n]");
}
@Test
public void should_message_heterogeneous_array() throws Exception {
e.expectMessage("k becomes a heterogeneous array on line 2");
new Toml().parse("k = [ 1,\n 1.1 ]");
}
}