I have generated a DSL for my domain. I have implemented all the read access methods of my DSL, now I am thinking about how to solve the writing methods. Maybe someone can give me a good advice.
To be able to implement and test my DSL, I have choosen to use an interface with which all the data access will be handled. In my tests I can mock and fill these data collections, later when I integrate the DSL into some domain services, these collections will be filled with real data. For reading acces this works good. But how can I solve the writing access. My current implementation is not coupled with services which can write data. So I thought I can collect all the writing objects and the caller of my dsl will retrieve this collection and will do the writing stuff. How can I return such a collection with my grammar, currently I am returning only true or false. Maybe there is a better approach, but I do not want to include writing services in my DSL implementation.
Here is part of my grammar, the setCommand and setIfCommand is not yet correctly implemented
grammar EXPRESS;
prog: expr+ EOF;
expr:
statement #StatementExpr
|NOT expr #NotExpr
| expr AND expr #AndExpr
| expr (OR | XOR) expr #OrExpr
| function #FunctionExpr
| LPAREN expr RPAREN #ParenExpr
| writeCommand #WriteExpr
;
writeCommand: setCommand | setIfCommand;
statement: ID '=' getCommand NEWLINE #Assign;
setCommand: 'set' LPAREN variable = variableType '|' value = parameter RPAREN;
setIfCommand: 'setIf' LPAREN variableType '|' expr '?' p1 = parameter ':' p2 = parameter RPAREN;
Here the part of my visitor
@Override
public Value visitSetCommand(FELParser.SetCommandContext ctx) {
//'set' LPAREN variable = variableType '|' value = parameter RPAREN SEMI;
Value variable = this.visit(ctx.variableType());
Value newValue = this.visit(ctx.parameter());
memory.put(variable.asString(), newValue);
return Value.TRUE;
}
@Override
public Value visitSetIfCommand(FELParser.SetIfCommandContext ctx) {
//'setIf' LPAREN variableType '|' expr '?' p1 = parameter ':' p2 =
parameter RPAREN SEMI;
Value variable = this.visit(ctx.variableType());
Value expression = this.visit(ctx.expr());
Value trueValue = this.visit(ctx.p1);
Value falseValue = this.visit(ctx.p2);
if (expression.asBoolean()){
memory.put(variable.asString(), new Value(trueValue));
}
else{
memory.put(variable.asString(), new Value(falseValue));
}
return Value.TRUE;
}
And here a testcase
@Test
public void whenSetCommand_ThenTrueAndValueInBuffer() {
String expression = "set(buffer, foo|bar) isSet(buffer,foo)";
ParseTree tree = getParseTree(expression);
Mockito.when(data.getFieldValues()).thenReturn(fieldValues);
Value answer = new FELCustomVisitor(data).visit(tree);
assertThat(answer.asBoolean()).isTrue();
}
public void beforeEach(){
data = initPDIXDataMock();
Mockito.when(data.getFieldValues()).thenReturn(new HashMap<>());
Mockito.when(data.getFormAttachments()).thenReturn(new HashMap<>());
Mockito.when(data.getInstanceAttachments()).thenReturn(new HashMap<>());
Mockito.when(data.getMandatorAttachments()).thenReturn(new HashMap<>());
Mockito.when(data.getFormAttributes()).thenReturn(new HashMap<>());
Mockito.when(data.getInstanceAttributes()).thenReturn(new HashMap<>());
Mockito.when(data.getMandatorAttributes()).thenReturn(new HashMap<>());
}
One idea is to add this to the return object, in my case Value. There I could add a list of methods which need to be processed by the calling service of my DSL. here is my value object
public class Value {
public static final Value TRUE = new Value(true);
public static final Value FALSE = new Value(false);
public static final Value NULL = new Value(null);
private final Object value;
public Value(Object value) {
this.value = value;
}
public Boolean asBoolean() {
return (Boolean) this.value;
}
public String asString(){
return (String) this.value;
}
public double asDouble(){
return Double.parseDouble(asString());
}
@Override
public String toString() {
return String.format("Value{type: %s, value: %s}",
this.value == null ? "null" : this.value.getClass().getSimpleName(), this.value);
}
public PDIXAttribute asAttribute() {
return (PDIXAttribute) this.value;
}
public PDIXAttachments asAttachment() {
return (PDIXAttachments) this.value;
}