I have this GitHub repository - repllib.java. Basically, it's a simple class library for coding REPL functionality with simple format. A typical session may look like this:
>>> add 1 1
>>> size
1
>>> add data point 2 2
>>> size
2
>>> print
1,000000 1,000000
2,000000 2,000000
>>> print 1
2,000000 2,000000
>>> print 0 2
1,000000 1,000000
2,000000 2,000000
>>> coefficient
0.9999999999999998
>>> quit
Code
com.github.coderodde.repllib.ReplParser.java:
package com.github.coderodde.repllib;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* This class implements the REPL (read, evaluate, process, loop) parser..
*
* @version 1.0.1
* @since 1.0.0
*/
public final class ReplParser {
private final List<ReplStatement> statementList = new ArrayList<>();
/**
* Adds a statement to this parser.
*
* @param statement the statement to add.
* @throws ReplDuplicateStatementException if this parser already contains
* an equivalent statement.
*/
public void addStatement(final ReplStatement statement) {
Objects.requireNonNull(statement, "Input statement is null");
if (statementList.contains(statement)) {
final String exceptionMessage =
String.format("Duplicate statement: [%s]",
statement.toString());
throw new ReplDuplicateStatementException(exceptionMessage);
}
statementList.add(statement);
}
public boolean contains(final ReplStatement statement) {
return statementList.contains(statement);
}
public void removeStatement(final ReplStatement statement) {
Objects.requireNonNull(statement, "Input statement is null");
statementList.remove(statement);
}
public ReplStatement get(final int index) {
return statementList.get(index);
}
public int size() {
return statementList.size();
}
public boolean isEmpty() {
return statementList.isEmpty();
}
public void parse(String commandLine) {
Objects.requireNonNull(commandLine, "Input command line is null");
commandLine = commandLine.trim();
if (commandLine.isEmpty()) {
throw new IllegalArgumentException("Input command line is blank");
}
parseImpl(commandLine);
}
private void parseImpl(final String commandLine) {
for (final ReplStatement statement : statementList) {
if (statement.matchesCommandLine(commandLine)) {
statement.actImpl(commandLine);
return;
}
}
final String exceptionMessage =
String.format("Statement \"%s\" has no match", commandLine);
throw new ReplNoMatchingStatementException(exceptionMessage);
}
}
com.github.coderodde.repllib.ReplStatement.java:
package com.github.coderodde.repllib;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* This class implements a REPL statement consisting of a list of token
* descriptors.
*
* @version 1.0.1
* @since 1.0.0
*/
public final class ReplStatement {
private static final String TO_STRING_INTEGER = "<integer>";
private static final String TO_STRING_REAL = "<real>";
private static final String TO_STRING_STRING = "<string>";
private final List<ReplTokenDescriptor> tokenDescriptorList =
new ArrayList<>();
private final ReplAction action;
public ReplStatement(final List<ReplTokenDescriptor> tokenDescriptorList,
final ReplAction action) {
Objects.requireNonNull(tokenDescriptorList,
"Token descriptor list is null");
Objects.requireNonNull(action, "Input action is null");
if (tokenDescriptorList.isEmpty()) {
throw new IllegalArgumentException(
"Token descriptor list is empty");
}
this.tokenDescriptorList.addAll(tokenDescriptorList);
this.action = action;
}
public String toString() {
final StringBuilder sb = new StringBuilder().append("[");
boolean first = true;
for (final ReplTokenDescriptor descriptor : tokenDescriptorList) {
if (first) {
first = false;
} else {
sb.append(", ");
}
if (descriptor instanceof ReplKeyword) {
sb.append(((ReplKeyword) descriptor).getKeyword());
} else if (descriptor instanceof ReplInteger) {
sb.append(TO_STRING_INTEGER);
} else if (descriptor instanceof ReplReal) {
sb.append(TO_STRING_REAL);
} else if (descriptor instanceof ReplString) {
sb.append(TO_STRING_STRING);
}
}
return sb.append("]").toString();
}
@Override
public boolean equals(final Object o) {
if (o == this) {
return true;
}
if (o == null) {
return false;
}
if (!getClass().equals(o.getClass())) {
return false;
}
final ReplStatement other = (ReplStatement) o;
if (tokenDescriptorList.size() != other.tokenDescriptorList.size()) {
return false;
}
for (int i = 0; i < tokenDescriptorList.size(); i++) {
final ReplTokenDescriptor token1 = tokenDescriptorList.get(i);
final ReplTokenDescriptor token2 = other.tokenDescriptorList.get(i);
if (token1 instanceof ReplKeyword) {
if (!(token2 instanceof ReplKeyword)) {
return false;
}
final String keyword1 = ((ReplKeyword) token1).getKeyword();
final String keyword2 = ((ReplKeyword) token2).getKeyword();
if (!keyword1.equals(keyword2)) {
return false;
}
} else if (token1 instanceof ReplString) {
if (!(token2 instanceof ReplString)) {
return false;
}
} else if (token1 instanceof ReplInteger) {
if (!(token2 instanceof ReplInteger)) {
return false;
}
} else if (token1 instanceof ReplReal) {
if (!(token2 instanceof ReplReal)) {
return false;
}
} else {
throw new IllegalArgumentException("Should not get here");
}
}
return true;
}
boolean matchesCommandLine(final String commandLine) {
final String[] commandLineParts = commandLine.split("\\s");
if (commandLineParts.length != tokenDescriptorList.size()) {
return false;
}
for (int i = 0; i < tokenDescriptorList.size(); i++) {
final ReplTokenDescriptor descriptor = tokenDescriptorList.get(i);
final String commandLinePart = commandLineParts[i];
if (descriptor instanceof ReplKeyword) {
final String keyword = ((ReplKeyword) descriptor).getKeyword();
if (!commandLinePart.equals(keyword)) {
return false;
}
} else if (descriptor instanceof ReplInteger) {
if (!stringIsInteger(commandLinePart)) {
return false;
}
} else if (descriptor instanceof ReplReal) {
if (!stringIsReal(commandLinePart)) {
return false;
}
}
}
return true;
}
void actImpl(final String commandLine) {
final String[] commandLineParts = commandLine.split("\\s");
final List<Object> actionParameterList = new ArrayList<>();
for (int i = 0; i < commandLineParts.length; i++) {
final ReplTokenDescriptor descriptor = tokenDescriptorList.get(i);
if (descriptor instanceof ReplKeyword) {
continue;
}
Object o = null;
final String argument = commandLineParts[i];
if (descriptor instanceof ReplInteger) {
actionParameterList.add((Object) Long.parseLong(argument));
} else if (descriptor instanceof ReplReal) {
actionParameterList.add((Object) Double.parseDouble(argument));
} else if (descriptor instanceof ReplString) {
actionParameterList.add((Object) argument);
}
}
action.act(actionParameterList);
}
private static boolean stringIsInteger(final String str) {
try {
Long.parseLong(str);
return true;
} catch (final NumberFormatException ex) {
return false;
}
}
private static boolean stringIsReal(final String str) {
try {
Double.parseDouble(str);
return true;
} catch (final NumberFormatException ex) {
return false;
}
}
}
com.github.coderodde.repllib.demo.CorrelationCoefficientDemoApp.java:
package com.github.coderodde.repllib.demo;
import com.github.coderodde.repllib.ReplAction;
import static com.github.coderodde.repllib.ReplAction.toDouble;
import static com.github.coderodde.repllib.ReplAction.toInt;
import com.github.coderodde.repllib.ReplInteger;
import com.github.coderodde.repllib.ReplKeyword;
import com.github.coderodde.repllib.ReplParser;
import com.github.coderodde.repllib.ReplReal;
import com.github.coderodde.repllib.ReplStatement;
import com.github.coderodde.repllib.ReplTokenDescriptor;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class CorrelationCoefficientDemoApp {
private final List<DataPoint> dataPointList = new ArrayList<>();
public void run() {
final Scanner scanner = new Scanner(System.in);
final ReplParser parser = buildParser();
while (true) {
try {
System.out.print(">>> ");
final String commandLine = scanner.nextLine();
parser.parse(commandLine);
} catch (final Exception ex) {
System.err.printf("ERROR: %s\n", ex.getMessage());
}
}
}
public void addDataPoint(final double x, final double y) {
dataPointList.add(new DataPoint(x, y));
}
public double computeCorrelation() {
return computeTerm1() / (computeTerm2() * computeTerm3());
}
private double computeMeanX() {
double sum = 0;
for (final DataPoint dp : dataPointList) {
sum += dp.x;
}
return sum / dataPointList.size();
}
private double computeMeanY() {
double sum = 0;
for (final DataPoint dp : dataPointList) {
sum += dp.y;
}
return sum / dataPointList.size();
}
private double computeTerm1() {
final double meanX = computeMeanX();
final double meanY = computeMeanY();
double sum = 0;
for (final DataPoint dp : dataPointList) {
sum += (dp.x - meanX) * (dp.y - meanY);
}
return sum;
}
private double computeTerm2() {
final double meanX = computeMeanX();
double sum = 0;
for (final DataPoint dp : dataPointList) {
sum += Math.pow(dp.x - meanX, 2.0);
}
return Math.sqrt(sum);
}
private double computeTerm3() {
final double meanY = computeMeanY();
double sum = 0;
for (final DataPoint dp : dataPointList) {
sum += Math.pow(dp.y - meanY, 2.0);
}
return Math.sqrt(sum);
}
public static void main(String[] args) {
final CorrelationCoefficientDemoApp app =
new CorrelationCoefficientDemoApp();
app.run();
}
private ReplParser buildParser() {
final ReplParser parser = new ReplParser();
final ReplKeyword keywordAdd = new ReplKeyword("add");
final ReplKeyword keywordData = new ReplKeyword("data");
final ReplKeyword keywordPoint = new ReplKeyword("point");
final ReplKeyword keywordSize = new ReplKeyword("size");
final ReplKeyword keywordPrint = new ReplKeyword("print");
final ReplKeyword keywordQuit = new ReplKeyword("quit");
final ReplKeyword keywordCoefficient = new ReplKeyword("coefficient");
final ReplInteger replInteger = new ReplInteger();
final ReplReal replReal = new ReplReal();
final List<ReplTokenDescriptor> descriptorList = new ArrayList<>();
descriptorList.add(keywordAdd);
descriptorList.add(keywordData);
descriptorList.add(keywordPoint);
descriptorList.add(replReal);
descriptorList.add(replReal);
final ReplAction addDataPointAction = new ReplAction() {
@Override
public void act(List<Object> actionParameterList) {
final double x = toDouble(actionParameterList.get(0));
final double y = toDouble(actionParameterList.get(1));
CorrelationCoefficientDemoApp.this.addDataPoint(x, y);
}
};
final ReplStatement statementAddDataPoint =
new ReplStatement(descriptorList,
addDataPointAction);
parser.addStatement(statementAddDataPoint);
descriptorList.clear();
descriptorList.add(keywordAdd);
descriptorList.add(keywordPoint);
descriptorList.add(replReal);
descriptorList.add(replReal);
final ReplStatement statementAddPoint =
new ReplStatement(descriptorList, addDataPointAction);
parser.addStatement(statementAddPoint);
descriptorList.clear();
descriptorList.add(keywordAdd);
descriptorList.add(replReal);
descriptorList.add(replReal);
final ReplStatement statementAdd =
new ReplStatement(descriptorList, addDataPointAction);
parser.addStatement(statementAdd);
descriptorList.clear();
descriptorList.add(keywordSize);
final ReplStatement statementSize =
new ReplStatement(descriptorList, new ReplAction() {
@Override
public void act(List<Object> actionParameterList) {
System.out.println(dataPointList.size());
}
});
parser.addStatement(statementSize);
descriptorList.clear();
descriptorList.add(keywordCoefficient);
final ReplStatement statementCoefficient =
new ReplStatement(descriptorList, new ReplAction() {
@Override
public void act(List<Object> actionParameterList) {
System.out.println(computeCorrelation());
}
});
parser.addStatement(statementCoefficient);
descriptorList.clear();
descriptorList.add(keywordPrint);
final ReplStatement statementPrintAll =
new ReplStatement(descriptorList, new ReplAction() {
@Override
public void act(List<Object> actionParameterList) {
for (final DataPoint dp : dataPointList) {
System.out.println(dp.toString());
}
}
});
parser.addStatement(statementPrintAll);
descriptorList.clear();
descriptorList.add(keywordPrint);
descriptorList.add(replInteger);
final ReplStatement statementPrintOne =
new ReplStatement(descriptorList, new ReplAction() {
@Override
public void act(List<Object> actionParameterList) {
final int index = toInt(actionParameterList.get(0));
System.out.println(dataPointList.get(index).toString());
}
});
parser.addStatement(statementPrintOne);
descriptorList.clear();
descriptorList.add(keywordPrint);
descriptorList.add(replInteger);
descriptorList.add(replInteger);
final ReplStatement statementPrintRange =
new ReplStatement(descriptorList, new ReplAction() {
@Override
public void act(List<Object> actionParameterList) {
final int index0 = toInt(actionParameterList.get(0));
final int index1 = toInt(actionParameterList.get(1));
final List<DataPoint> view = dataPointList.subList(index0,
index1);
for (final DataPoint dp : view) {
System.out.println(dp.toString());
}
}
});
parser.addStatement(statementPrintRange);
descriptorList.clear();
descriptorList.add(keywordQuit);
final ReplStatement statementQuit =
new ReplStatement(descriptorList, new ReplAction() {
@Override
public void act(List<Object> actionParameterList) {
System.out.println("\nBye!");
System.exit(0);
}
});
parser.addStatement(statementQuit);
return parser;
}
private static final class DataPoint {
final double x;
final double y;
DataPoint(final double x, final double y) {
this.x = x;
this.y = y;
}
public String toString() {
return String.format("%f %f", x, y);
}
}
}
com.github.coderodde.repllib.ReplParserTest.java:
package com.github.coderodde.repllib;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
public class ReplParserTest {
private static final ReplKeyword keywordFoo = new ReplKeyword("foo");
private static final ReplKeyword keywordBar = new ReplKeyword("bar");
private static final ReplKeyword keywordBaz = new ReplKeyword("baz");
private static final ReplString replString = new ReplString();
private static final ReplInteger replInteger = new ReplInteger();
private static final ReplReal replReal = new ReplReal();
@Test(expected = ReplDuplicateStatementException.class)
public void throwsOnDuplicateTokenDescriptorsWithDifferentActions() {
final ReplParser parser = new ReplParser();
final List<ReplTokenDescriptor> descriptorList = new ArrayList<>();
descriptorList.add(keywordFoo);
descriptorList.add(keywordBar);
final ReplStatement statement1 =
new ReplStatement(descriptorList, new ReplAction() {
@Override
public void act(List<Object> actionParameterList) {
int i = 0;
i++;
}
});
final ReplStatement statement2 =
new ReplStatement(descriptorList, new ReplAction() {
@Override
public void act(List<Object> actionParameterList) {
int i = 0;
i--;
}
});
parser.addStatement(statement1);
parser.addStatement(statement2); // Should throw.
}
@Test(expected = ReplDuplicateStatementException.class)
public void throwsOnDuplicateTokenDescriptorsWithSameActions() {
final ReplParser parser = new ReplParser();
final List<ReplTokenDescriptor> descriptorList = new ArrayList<>();
descriptorList.add(keywordFoo);
descriptorList.add(keywordBar);
final ReplAction action = new ReplAction() {
@Override
public void act(List<Object> actionParameterList) {
int i = 0;
i++;
}
};
final ReplStatement statement1 =
new ReplStatement(descriptorList, action);
final ReplStatement statement2 =
new ReplStatement(descriptorList, action);
parser.addStatement(statement1);
parser.addStatement(statement2); // Should throw.
}
@Test(expected = ReplNoMatchingStatementException.class)
public void throwsOnUnmatchingStatement() {
final ReplParser parser = new ReplParser();
final List<ReplTokenDescriptor> descriptorList = new ArrayList<>();
descriptorList.add(keywordBar);
descriptorList.add(keywordBaz);
descriptorList.add(new ReplInteger());
final ReplAction action = new ReplAction() {
@Override
public void act(List<Object> actionParameterList) {
int i = 0;
i++;
}
};
final ReplStatement statement =
new ReplStatement(descriptorList, action);
parser.addStatement(statement);
parser.parse("bar baz 1.4"); // Should throw.
}
}
Critique request
I would like to hear comments how to make my library to adhere to JDK 24 coding best practices. Also, perhaps there is a better way to implement what I have attempted to implement?