Xtext Tip: Use validation to enforce constraints
Sometimes, you need some constraints for your DSL and you can use the grammar language for that. However, this is not a good idea. For example, in a previous post, I created a grammar rule to parse hexadecimal numbers with exactly two digits. To do this, we created the following rule:
terminal HEX_VALUE returns ecore::EInt:
'0x' (('0'..'9') | ('a'..'z') | ('A'..'Z')) (('0'..'9') | ('a'..'z') | ('A'..'Z'));
This rules does exactly that, but when it is not valid it shows a cryptic error. The error does not help the user to fix this.
A more ergonomic solution is to create more flexible grammar and then add a validation. A more flexible rule for our hexadecimal value would be:
terminal HEX_VALUE returns ecore::EInt:
'0x' (('0'..'9') | ('a'..'z') | ('A'..'Z'))*;
Normally, we would add a check in the validation file created by Xtext. However, the error here will happen before the validation, it will happen at conversion time. To fix this, we can enhance our ValueConverter
as follows:
package com.idiomaticsoft.dsl.hex.converter;
import org.eclipse.xtext.common.services.DefaultTerminalConverters;
import org.eclipse.xtext.conversion.IValueConverter;
import org.eclipse.xtext.conversion.ValueConverter;
import org.eclipse.xtext.conversion.ValueConverterException;
import org.eclipse.xtext.nodemodel.INode;
public class MyValueConverter extends DefaultTerminalConverters {
@ValueConverter(rule = "HEX_VALUE")
public IValueConverter<Integer> HexValue() {
return new HexValueConverter();
}
public static class HexValueConverter implements IValueConverter<Integer> {
@Override
public Integer toValue(String string, INode node) throws ValueConverterException {
if (string.substring(2).length() != 2) {
throw new ValueConverterException("The value " + string + " is invalid. Please use a value of the form '0xXX'.", node, null);
}
return Integer.parseInt(string.substring(2), 16);
}
@Override
public String toString(Integer value) throws ValueConverterException {
return "0x" + Integer.toHexString(value);
}
}
}
After this change, we provide the user with a more ergonomic error message.
The full code for this example can be found here.