Working With Design Patterns: Interpreter, Page 3
Parsing
Unfortunately, the Interpreter pattern discussions rarely discuss how to create the composite structure in the first place. For my amusement, I chose to test-drive a simple parser that could take a string and generate the appropriate interpreter hierarchy. Listing 8 starts things off with a simple test to verify that I can parse a single terminal expression.
Listing 8: A first test for the parser.
import static org.junit.Assert.*;
import org.junit.*;
public class ParserTest {
@Test
public void singleExpression() {
Parser parser = new Parser();
Expression expression = parser.parse("contains text");
assertEquals(Contains.class, expression.getClass());
Contains contains = (Contains)expression;
String[] keywords = contains.getKeywords();
assertEquals("text", keywords[0]);
}
}
Getting this test to pass is a simple matter (see Listing 9).
Listing 9: A very specific Parser implementation.
public class Parser {
public Expression parse(String expression) {
String[] tokens = expression.split(" ");
return new Contains(tokens[1]);
}
}
Listing 10 adds just a little more complexity—the "contains" keyword now needs to allow specifying multiple keywords.
Listing 10: A test for supporting multiple keywords.
import static org.junit.Assert.*;
import org.junit.*;
public class ParserTest {
private Parser parser;
@Before
public void createParser() {
parser = new Parser();
}
@Test
public void singleContainsExpression() {
Expression expression = parser.parse("contains text");
assertKeywords((Contains)expression, "text");
}
@Test
public void containsMultipleKeywords() {
Expression expression = parser.parse("contains text bozo");
assertKeywords((Contains)expression, "text", "bozo");
}
private void assertKeywords(Contains contains,
String... expected) {
assertArrayEquals(expected, contains.getKeywords());
}
}
Listing 11 provides the implementation for supporting multiple keywords. Note that the constructor for the Contains class will need to change to support taking a String array.
Listing 11: Supporting multiple keywords.
import java.util.*;
public class Parser {
private List<String> arguments = new ArrayList<String>();
public Expression parse(String expression) {
String[] tokens = expression.split(" ");
for (String token: tokens) {
if (!token.equals("contains"))
pushArgument(token);
}
return new Contains
((String[])arguments.toArray(new String[0]));
}
private void pushArgument(String token) {
arguments.add(token);
}
}
The next simplest thing to implement would be another terminal expression, such as "olderThan." Listings 12 and 13 lay out the test and changes to the Parser class.
Listing 12: Driving support for an additional terminal expression.
import static org.junit.Assert.*;
import java.util.*;
import org.junit.*;
public class ParserTest {
private Parser parser;
@Before
public void createParser() {
parser = new Parser();
}
@Test
public void singleContainsExpression() {
Expression expression = parser.parse("contains text");
assertKeywords((Contains)expression, "text");
}
@Test
public void containsMultipleKeywords() {
Expression expression = parser.parse("contains text bozo");
assertKeywords((Contains)expression, "text", "bozo");
}
@Test
public void olderThan() {
Expression expression = parser.parse("olderThan 03/31/2008");
OlderThan olderThan = (OlderThan)expression;
assertDate(2008, Calendar.MARCH, 31, olderThan.getDate());
}
private void assertDate(int year, int month,
int dayOfMonth, Date date) {
Calendar calendar = GregorianCalendar.getInstance();
calendar.setTime(date);
assertEquals(year, calendar.get(Calendar.YEAR));
assertEquals(month, calendar.get(Calendar.MONTH));
assertEquals(dayOfMonth,
calendar.get(Calendar.DAY_OF_MONTH));
}
private void assertKeywords(Contains contains,
String... expected) {
assertArrayEquals(expected, contains.getKeywords());
}
}
