1
\$\begingroup\$

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?

\$\endgroup\$
3
  • 1
    \$\begingroup\$ This is not really just a REPL-library, is it? A library that provides a read-eval-print loop framework? I was expecting an API to which you could plug a generic component that Reads input, then a generic component that Executes the statements described by the input and lastly a generic component that Prints the result. All this executed in a Loop. \$\endgroup\$ Commented Apr 22 at 5:37
  • \$\begingroup\$ Nice idea, yet I have no clue what you have in mind. Consider to answer a funky answer. \$\endgroup\$
    – coderodde
    Commented Apr 22 at 5:45
  • \$\begingroup\$ What I mean is that REPL is an extremely generic concept that only concerns the reading and evaluating something in a loop. Your code on the other hand is tightly coupled to a very specific implementation with contsraints that are not specific to REPL (such as keywords and prevention of duplicate statements). So the subject of the post does not really represent the code. en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop \$\endgroup\$ Commented Apr 22 at 7:25

0

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.