diff --git a/src/error/LineColumnException.java b/src/error/LineColumnException.java index 76a27b1..2490732 100644 --- a/src/error/LineColumnException.java +++ b/src/error/LineColumnException.java @@ -13,11 +13,6 @@ public abstract class LineColumnException extends LispException { this.position = position; } - @Override - public int getSeverity() { - return 0; - } - @Override public String getMessage() { return MessageFormat.format("{0} - line {1}, column {2}", getMessagePrefix(), position.getLineNumber(), diff --git a/src/error/LispException.java b/src/error/LispException.java index 4b63e9f..f6fb7b3 100644 --- a/src/error/LispException.java +++ b/src/error/LispException.java @@ -4,6 +4,8 @@ public abstract class LispException extends RuntimeException { private static final long serialVersionUID = 1L; - public abstract int getSeverity(); - + public int getSeverity() { + return 0; + } + } diff --git a/src/eval/argument/ArgumentValidator.java b/src/eval/argument/ArgumentValidator.java new file mode 100644 index 0000000..352fbbc --- /dev/null +++ b/src/eval/argument/ArgumentValidator.java @@ -0,0 +1,112 @@ +package eval.argument; + +import java.text.MessageFormat; + +import error.LispException; +import eval.LENGTH; +import sexpression.*; + +public class ArgumentValidator { + + private Class argumentType; + private String functionName; + private Integer maximumNumberOfArguments; + private Integer minimumNumberOfArguments; + + public ArgumentValidator(String functionName) { + this.argumentType = SExpression.class; + this.functionName = functionName; + this.minimumNumberOfArguments = null; + this.maximumNumberOfArguments = null; + } + + public void setArgumentType(Class argumentType) { + this.argumentType = argumentType; + } + + public void setMaximumNumberOfArguments(int maximumNumberOfArguments) { + this.maximumNumberOfArguments = maximumNumberOfArguments; + } + + public void setMinimumNumberOfArguments(int minimumNumberOfArguments) { + this.minimumNumberOfArguments = minimumNumberOfArguments; + } + + public void setExactNumberOfArguments(int exactNumberOfArguments) { + this.minimumNumberOfArguments = exactNumberOfArguments; + this.maximumNumberOfArguments = exactNumberOfArguments; + } + + public void validate(Cons argumentList) { + if (containsTooFewArguments(argumentList)) + throw new TooFewArgumentsException(functionName, argumentList); + else if (containsTooManyArguments(argumentList)) + throw new TooManyArgumentsException(functionName, argumentList); + else if (!isExpectedArgumentType(argumentList.getCar())) + throw new BadArgumentTypeException(functionName, argumentList.getCar()); + } + + private boolean containsTooFewArguments(Cons argumentList) { + return (minimumNumberOfArguments != null) && (LENGTH.getLength(argumentList) < minimumNumberOfArguments); + } + + private boolean containsTooManyArguments(Cons argumentList) { + return (maximumNumberOfArguments != null) && (LENGTH.getLength(argumentList) > maximumNumberOfArguments); + } + + private boolean isExpectedArgumentType(SExpression firstArgument) { + return argumentType.isInstance(firstArgument); + } + + public static class TooFewArgumentsException extends LispException { + + private static final long serialVersionUID = 1L; + private String functionName; + private Cons originalSExpression; + + public TooFewArgumentsException(String functionName, Cons argumentList) { + this.functionName = functionName; + this.originalSExpression = new Cons(new Symbol(this.functionName), argumentList); + } + + @Override + public String getMessage() { + return MessageFormat.format("too few arguments given to {0}: {1}", functionName, originalSExpression); + } + } + + public static class TooManyArgumentsException extends LispException { + + private static final long serialVersionUID = 1L; + private String functionName; + private Cons originalSExpression; + + public TooManyArgumentsException(String functionName, Cons argumentList) { + this.functionName = functionName; + this.originalSExpression = new Cons(new Symbol(this.functionName), argumentList); + } + + @Override + public String getMessage() { + return MessageFormat.format("too many arguments given to {0}: {1}", functionName, originalSExpression); + } + } + + public static class BadArgumentTypeException extends LispException { + + private static final long serialVersionUID = 1L; + private String functionName; + private String argument; + + public BadArgumentTypeException(String functionName, SExpression argument) { + this.functionName = functionName; + this.argument = argument.toString(); + } + + @Override + public String getMessage() { + return MessageFormat.format("{0}: {1} is not the expected type", functionName, argument); + } + } + +} diff --git a/src/interpreter/InteractiveLispInterpreter.java b/src/interpreter/InteractiveLispInterpreter.java index 4f2f2d9..ed23a33 100644 --- a/src/interpreter/InteractiveLispInterpreter.java +++ b/src/interpreter/InteractiveLispInterpreter.java @@ -9,8 +9,8 @@ public class InteractiveLispInterpreter extends LispInterpreter { private static final String GREETING = "SUNY Potsdam Lisp Interpreter - Version 4.4.3"; private static final String PROMPT = "~ "; - public InteractiveLispInterpreter(InputStream inputStream, ErrorManager errorManager, PrintStream outputStream) { - super(inputStream, errorManager, outputStream); + public InteractiveLispInterpreter(InputStream inputStream, PrintStream outputStream, ErrorManager errorManager) { + super(inputStream, outputStream, errorManager); } protected void printGreeting() { diff --git a/src/interpreter/LispInterpreter.java b/src/interpreter/LispInterpreter.java index edef39f..dfe1ae5 100644 --- a/src/interpreter/LispInterpreter.java +++ b/src/interpreter/LispInterpreter.java @@ -17,7 +17,7 @@ public class LispInterpreter { private ErrorManager errorManager; protected PrintStream outputStream; - public LispInterpreter(InputStream inputStream, ErrorManager errorManager, PrintStream outputStream) { + public LispInterpreter(InputStream inputStream, PrintStream outputStream, ErrorManager errorManager) { this.errorManager = errorManager; this.parser = new LispParser(inputStream, inputStream.toString()); this.outputStream = outputStream; @@ -63,11 +63,6 @@ public class LispInterpreter { private static final long serialVersionUID = 1L; - @Override - public int getSeverity() { - return 0; - } - @Override public String getMessage() { return e.getMessage(); diff --git a/src/main/LispMain.java b/src/main/LispMain.java index 3f998c9..3f9cc0e 100644 --- a/src/main/LispMain.java +++ b/src/main/LispMain.java @@ -11,30 +11,22 @@ public class LispMain { public static void main(String[] args) { LispInterpreter interpreter = null; - ErrorManager errorManager = new ErrorManager(new TerminateInterpreter(), System.err::print); + ErrorManager errorManager = new ErrorManager(() -> System.exit(1), System.err::print); if (args.length > 0) { String fileName = args[0]; try { - interpreter = new LispInterpreter(new FileInputStream(fileName), errorManager, System.out); + interpreter = new LispInterpreter(new FileInputStream(fileName), System.out, errorManager); } catch (FileNotFoundException e) { errorManager.generateError(new LispFileNotFoundException(e)); } } else - interpreter = new InteractiveLispInterpreter(System.in, errorManager, System.out); + interpreter = new InteractiveLispInterpreter(System.in, System.out, errorManager); interpreter.interpret(); } - private static class TerminateInterpreter implements Runnable { - - @Override - public void run() { - System.exit(1); - } - } - public static class LispFileNotFoundException extends LispException { private static final long serialVersionUID = 1L; diff --git a/src/sexpression/LispNumber.java b/src/sexpression/LispNumber.java index 07eb263..3860056 100644 --- a/src/sexpression/LispNumber.java +++ b/src/sexpression/LispNumber.java @@ -41,11 +41,6 @@ public class LispNumber extends Atom { this.text = text; } - @Override - public int getSeverity() { - return 0; - } - @Override public String getMessage() { return MessageFormat.format("{0} is not a valid integer", text); diff --git a/test/error/ErrorManagerTester.java b/test/error/ErrorManagerTester.java index dcdb89d..8d3c7fb 100644 --- a/test/error/ErrorManagerTester.java +++ b/test/error/ErrorManagerTester.java @@ -5,7 +5,6 @@ import static org.junit.Assert.assertTrue; import java.util.HashSet; import java.util.Set; -import java.util.function.Consumer; import org.junit.Before; import org.junit.Test; @@ -18,23 +17,7 @@ public class ErrorManagerTester { private Set indicatorSet; private ErrorManager createErrorManagerWithIndicators() { - Runnable terminationFunction = new Runnable() { - - @Override - public void run() { - indicatorSet.add(TERMINATED); - } - }; - - Consumer outputFunction = new Consumer() { - - @Override - public void accept(String t) { - indicatorSet.add(MESSAGE); - } - }; - - return new ErrorManager(terminationFunction, outputFunction); + return new ErrorManager(() -> indicatorSet.add(TERMINATED), (String) -> indicatorSet.add(MESSAGE)); } private LispException createLispException(int severity) { diff --git a/test/eval/argument/ArgumentValidatorTester.java b/test/eval/argument/ArgumentValidatorTester.java new file mode 100644 index 0000000..500d07d --- /dev/null +++ b/test/eval/argument/ArgumentValidatorTester.java @@ -0,0 +1,131 @@ +package eval.argument; + +import static org.junit.Assert.*; + +import org.junit.*; + +import error.ErrorManager; +import eval.argument.ArgumentValidator.*; +import sexpression.*; + +public class ArgumentValidatorTester { + + private ArgumentValidator validator; + + private Cons makeArgumentListOfSize(int size) { + Cons argumentList = Nil.getUniqueInstance(); + + for (int i = 0; i < size; i++) + argumentList = new Cons(Nil.getUniqueInstance(), argumentList); + + return argumentList; + } + + @Before + public void setUp() throws Exception { + validator = new ArgumentValidator("TEST"); + } + + @Test + public void noConstraints_DoesNotThrowExceptionWithNoArguments() { + validator.validate(makeArgumentListOfSize(0)); + } + + @Test + public void noConstraints_DoesNotThrowExceptionWithOneArgument() { + validator.validate(makeArgumentListOfSize(1)); + } + + @Test + public void noConstraints_DoesNotThrowExceptionWithManyArguments() { + validator.validate(makeArgumentListOfSize(20)); + } + + @Test(expected = TooFewArgumentsException.class) + public void tooFewArgumentsWithMinimumSet_ThrowsException() { + validator.setMinimumNumberOfArguments(1); + validator.validate(makeArgumentListOfSize(0)); + } + + @Test(expected = TooManyArgumentsException.class) + public void tooManyArgumentsWithMaximumSet_ThrowsException() { + validator.setMaximumNumberOfArguments(1); + validator.validate(makeArgumentListOfSize(2)); + } + + @Test + public void exactNumberOfArguments_DoesNotThrowException() { + validator.setExactNumberOfArguments(5); + validator.validate(makeArgumentListOfSize(5)); + } + + @Test(expected = TooFewArgumentsException.class) + public void tooFewArgumentsWithExactSet_ThrowsException() { + validator.setExactNumberOfArguments(3); + validator.validate(makeArgumentListOfSize(2)); + } + + @Test(expected = TooManyArgumentsException.class) + public void tooManyArgumentsWithExactSet_ThrowsException() { + validator.setExactNumberOfArguments(3); + validator.validate(makeArgumentListOfSize(4)); + } + + @Test + public void tooManyArgumentsException_HasCorrectSeverity() { + TooManyArgumentsException e = new TooManyArgumentsException("TEST", Nil.getUniqueInstance()); + + assertTrue(e.getSeverity() < ErrorManager.CRITICAL_LEVEL); + } + + @Test + public void tooManyArgumentsException_HasMessageText() { + TooManyArgumentsException e = new TooManyArgumentsException("TEST", Nil.getUniqueInstance()); + + assertNotNull(e.getMessage()); + assertTrue(e.getMessage().length() > 0); + } + + @Test + public void tooFewArgumentsException_HasCorrectSeverity() { + TooFewArgumentsException e = new TooFewArgumentsException("TEST", Nil.getUniqueInstance()); + + assertTrue(e.getSeverity() < ErrorManager.CRITICAL_LEVEL); + } + + @Test + public void tooFewArgumentsException_HasMessageText() { + TooFewArgumentsException e = new TooFewArgumentsException("TEST", Nil.getUniqueInstance()); + + assertNotNull(e.getMessage()); + assertTrue(e.getMessage().length() > 0); + } + + @Test + public void BadArgumentTypeException_HasCorrectSeverity() { + BadArgumentTypeException e = new BadArgumentTypeException("TEST", Nil.getUniqueInstance()); + + assertTrue(e.getSeverity() < ErrorManager.CRITICAL_LEVEL); + } + + @Test + public void BadArgumentTypeException_HasMessageText() { + BadArgumentTypeException e = new BadArgumentTypeException("TEST", Nil.getUniqueInstance()); + + assertNotNull(e.getMessage()); + assertTrue(e.getMessage().length() > 0); + } + + @Test + public void correctArgumentType_DoesNotThrowException() { + validator.setArgumentType(Nil.class); + validator.validate(makeArgumentListOfSize(1)); + } + + @Test(expected = BadArgumentTypeException.class) + public void badArgumentType_ThrowsException() { + validator.setArgumentType(LispString.class); + validator.validate(makeArgumentListOfSize(1)); + } + +}