Improved error handling code and line numbers included in error messages

This commit is contained in:
moandji.ezana 2015-02-09 16:29:20 +02:00
parent 55d598c3b1
commit 91e5c65577
6 changed files with 190 additions and 33 deletions

View file

@ -8,6 +8,10 @@
* Dropped dependency on Parboiled and its significant transitive dependencies
* Updated Gson to 2.3.1
### Added
* Line numbers included in error messages
### Fixed
* Fixed short-form Unicode escapes

View file

@ -7,8 +7,100 @@ import java.util.Map;
import java.util.Set;
class Results {
static class Errors {
private final StringBuilder sb = new StringBuilder();
void duplicateTable(String table, int line) {
sb.append("Duplicate table definition: [")
.append(table)
.append("]\n");
}
void emptyImplicitTable(String table, int line) {
sb.append("Invalid table definition due to empty implicit table name: ");
if (!table.startsWith("[")) {
sb.append('[');
}
sb.append(table);
if (!table.endsWith("]")) {
sb.append(']');
}
sb.append("\n");
}
void invalidTable(String table, int line) {
sb.append("Invalid table definition on line ")
.append(line)
.append(": ");
if (!table.startsWith("[")) {
sb.append('[');
}
sb.append(table);
if (!table.endsWith("]")) {
sb.append(']');
}
sb.append("]\n");
}
void duplicateKey(String key, int line) {
sb.append("Duplicate key: ")
.append(key)
.append('\n');
}
void invalidKey(String key, int line) {
sb.append("Invalid key");
if (line > -1) {
sb.append(" on line ")
.append(line);
}
sb.append(": ")
.append(key)
.append('\n');
}
void invalidTableArray(String tableArray, int line) {
sb.append("Invalid table array definition on line ")
.append(line)
.append(": ")
.append(tableArray)
.append('\n');
}
void invalidValue(String key, String value, int line) {
sb.append("Invalid value on line ")
.append(line)
.append(": ")
.append(key)
.append(" = ")
.append(value)
.append('\n');
}
void unterminated(String key, String multiline, int line) {
sb.append("Unterminated multiline value on line ")
.append(line)
.append(": ")
.append(key)
.append(" = ")
.append(multiline.trim())
.append('\n');
}
boolean hasErrors() {
return sb.length() > 0;
}
@Override
public String toString() {
return sb.toString();
}
}
Set<String> tables = new HashSet<String>();
StringBuilder errors = new StringBuilder();
final Errors errors = new Errors();
private Deque<Container> stack = new ArrayDeque<Container>();
Results() {
@ -20,7 +112,7 @@ class Results {
if (currentTable.accepts(key)) {
currentTable.put(key, value);
} else {
errors.append("Key " + key + " is defined twice!\n");
errors.duplicateKey(key, -1);
}
}
@ -56,7 +148,7 @@ class Results {
stack.push(((Container.TableArray) newContainer).getCurrent());
}
} else {
errors.append("Duplicate key and table definitions for " + tableName + "!\n");
errors.duplicateTable(tableName, -1);
break;
}
}
@ -64,11 +156,11 @@ class Results {
void startTables(String tableName) {
if (!tables.add(tableName)) {
errors.append("Table " + tableName + " defined twice!\n");
errors.duplicateTable(tableName, -1);
}
if (tableName.endsWith(".")) {
errors.append("Implicit table name cannot be empty: " + tableName);
errors.emptyImplicitTable(tableName, -1);
}
while (stack.size() > 1) {
@ -80,7 +172,7 @@ class Results {
String tablePart = tableParts[i].name;
Container currentContainer = stack.peek();
if (tablePart.isEmpty()) {
errors.append("Empty implicit table: " + tableName + "!\n");
errors.emptyImplicitTable(tableName, -1);
} else if (currentContainer.get(tablePart) instanceof Container) {
Container nextTable = (Container) currentContainer.get(tablePart);
stack.push(nextTable);
@ -90,7 +182,7 @@ class Results {
} else if (currentContainer.accepts(tablePart)) {
startTable(tablePart);
} else {
errors.append("Duplicate key and table definitions for " + tableName + "!\n");
errors.duplicateTable(tableName, -1);
break;
}
}

View file

@ -124,7 +124,7 @@ public class Toml {
*/
public Toml parse(String tomlString) throws IllegalStateException {
Results results = new TomlParser().run(tomlString);
if (results.errors.length() > 0) {
if (results.errors.hasErrors()) {
throw new IllegalStateException(results.errors.toString());
}

View file

@ -19,6 +19,7 @@ class TomlParser {
}
String[] lines = tomlString.split("[\\n\\r]");
int lastKeyLine = 1;
StringBuilder multilineBuilder = new StringBuilder();
Multiline multiline = Multiline.NONE;
@ -41,7 +42,7 @@ class TomlParser {
if (tableName != null) {
results.startTableArray(tableName);
} else {
results.errors.append("Invalid table array definition: " + line + "\n\n");
results.errors.invalidTableArray(line, i + 1);
}
continue;
@ -52,14 +53,14 @@ class TomlParser {
if (tableName != null) {
results.startTables(tableName);
} else {
results.errors.append("Invalid table definition: " + line + "\n\n");
results.errors.invalidTable(line.trim(), i + 1);
}
continue;
}
if (multiline.isNotMultiline() && !line.contains("=")) {
results.errors.append("Invalid key definition: " + line);
results.errors.invalidKey(line, i + 1);
continue;
}
@ -139,24 +140,24 @@ class TomlParser {
} else {
key = Keys.getKey(pair[0]);
if (key == null) {
results.errors.append("Invalid key name: " + pair[0] + "\n");
results.errors.invalidKey(pair[0], i + 1);
continue;
}
value = pair[1].trim();
}
lastKeyLine = i + 1;
Object convertedValue = VALUE_ANALYSIS.convert(value);
if (convertedValue != INVALID) {
results.addValue(key, convertedValue);
} else {
results.errors.append("Invalid key/value: " + key + " = " + value + "\n");
results.errors.invalidValue(key, value, i + 1);
}
}
if (multiline != Multiline.NONE) {
results.errors.append("Unterminated multiline " + multiline.toString().toLowerCase().replace('_', ' ') + "\n");
results.errors.unterminated(key, multilineBuilder.toString().trim(), lastKeyLine);
}
return results;

View file

@ -2,15 +2,10 @@ package com.moandjiezana.toml;
import static org.junit.Assert.assertEquals;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
public class BareKeysTest {
@Rule
public final ExpectedException exception = ExpectedException.none();
@Test
public void should_ignore_spaces_around_key_segments() throws Exception {
Toml toml = new Toml().parse("[ a . b . c ] \n key = \"a\"");
@ -18,32 +13,23 @@ public class BareKeysTest {
assertEquals("a", toml.getString("a.b.c.key"));
}
@Test
@Test(expected = IllegalStateException.class)
public void should_fail_when_characters_outside_accept_range_are_used_in_table_name() throws Exception {
exception.expect(IllegalStateException.class);
exception.expectMessage("Invalid table definition: [~]");
new Toml().parse("[~]");
}
@Test
@Test(expected = IllegalStateException.class)
public void should_fail_when_characters_outside_accept_range_are_used_in_key_name() throws Exception {
exception.expect(IllegalStateException.class);
new Toml().parse("~ = 1");
}
@Test
@Test(expected = IllegalStateException.class)
public void should_fail_on_sharp_sign_in_table_name() throws Exception {
exception.expect(IllegalStateException.class);
new Toml().parse("[group#]\nkey=1");
}
@Test
@Test(expected = IllegalStateException.class)
public void should_fail_on_spaces_in_table_name() throws Exception {
exception.expect(IllegalStateException.class);
new Toml().parse("[valid key]");
}

View file

@ -0,0 +1,74 @@
package com.moandjiezana.toml;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
public class ErrorMessagesTest {
@Rule
public final ExpectedException e = ExpectedException.none();
@Test
public void should_message_invalid_table() throws Exception {
e.expectMessage("Invalid table definition on line 1: [in valid]");
new Toml().parse("[in valid]");
}
@Test
public void should_message_duplicate_table() throws Exception {
e.expectMessage("Duplicate table definition: [again]");
new Toml().parse("[again]\n[again]");
}
@Test
public void should_message_empty_implicit_table_name() throws Exception {
e.expectMessage("Invalid table definition due to empty implicit table name: [a..b]");
new Toml().parse("[a..b]");
}
@Test
public void should_message_duplicate_key() throws Exception {
e.expectMessage("Duplicate key: k");
new Toml().parse("k = 1\n k = 2");
}
@Test
public void should_message_invalid_key() throws Exception {
e.expectMessage("Invalid key on line 1: k\"");
new Toml().parse("k\" = 1");
}
@Test
public void should_message_invalid_table_array() throws Exception {
e.expectMessage("Invalid table array definition on line 1: [[in valid]]");
new Toml().parse("[[in valid]]");
}
@Test
public void should_message_invalid_value() throws Exception {
e.expectMessage("Invalid value on line 1: k = 1 t");
new Toml().parse("k = 1 t");
}
@Test
public void should_message_unterminated_value() throws Exception {
e.expectMessage("Unterminated multiline value on line 1: k = '''abc");
new Toml().parse("k = '''abc");
}
@Test
public void should_message_key_without_equals() throws Exception {
e.expectMessage("Invalid key on line 2: k");
new Toml().parse("\nk\n=3");
}
}