diff --git a/src/function/LispFunction.java b/src/function/LispFunction.java index 4372f3e..6b6704e 100644 --- a/src/function/LispFunction.java +++ b/src/function/LispFunction.java @@ -5,7 +5,7 @@ import sexpression.SExpression; public abstract class LispFunction { - public abstract SExpression call(Cons argList); + public abstract SExpression call(Cons argumentList); public boolean isArgumentListEvaluated() { return true; diff --git a/src/function/builtin/math/MODULO.java b/src/function/builtin/math/MODULO.java new file mode 100644 index 0000000..b092b96 --- /dev/null +++ b/src/function/builtin/math/MODULO.java @@ -0,0 +1,45 @@ +package function.builtin.math; + +import error.LispException; +import function.ArgumentValidator; +import function.FunctionNames; +import function.LispFunction; +import sexpression.Cons; +import sexpression.LispNumber; +import sexpression.SExpression; + +@FunctionNames({ "MOD", "MODULO", "%" }) +public class MODULO extends LispFunction { + + private ArgumentValidator argumentValidator; + + public MODULO(String name) { + this.argumentValidator = new ArgumentValidator(name); + this.argumentValidator.setExactNumberOfArguments(2); + this.argumentValidator.setEveryArgumentExpectedType(LispNumber.class); + } + + @Override + public SExpression call(Cons argumentList) { + argumentValidator.validate(argumentList); + LispNumber dividend = (LispNumber) argumentList.getFirst(); + LispNumber divisor = (LispNumber) ((Cons) argumentList.getRest()).getFirst(); + + try { + return new LispNumber(dividend.getValue().mod(divisor.getValue())); + } catch (ArithmeticException e) { + throw new ModulusNotPositiveException(); + } + } + + public static class ModulusNotPositiveException extends LispException { + + private static final long serialVersionUID = 1L; + + @Override + public String getMessage() { + return "modulus not positive"; + } + } + +} diff --git a/src/table/FunctionTable.java b/src/table/FunctionTable.java index ac69719..57932e1 100644 --- a/src/table/FunctionTable.java +++ b/src/table/FunctionTable.java @@ -26,6 +26,7 @@ import function.builtin.cons.LIST; import function.builtin.cons.REST; import function.builtin.math.DIVIDE; import function.builtin.math.MINUS; +import function.builtin.math.MODULO; import function.builtin.math.MULTIPLY; import function.builtin.math.PLUS; import function.builtin.predicate.ATOM; @@ -88,6 +89,7 @@ public class FunctionTable { allBuiltIns.add(LISTP.class); allBuiltIns.add(LOAD.class); allBuiltIns.add(MINUS.class); + allBuiltIns.add(MODULO.class); allBuiltIns.add(MULTIPLY.class); allBuiltIns.add(NULL.class); allBuiltIns.add(OR.class); diff --git a/test/function/LispFunctionTest.java b/test/function/LispFunctionTest.java index 20243fc..7bb4760 100644 --- a/test/function/LispFunctionTest.java +++ b/test/function/LispFunctionTest.java @@ -14,7 +14,7 @@ public class LispFunctionTest { LispFunction lispFunction = new LispFunction() { @Override - public SExpression call(Cons argList) { + public SExpression call(Cons argumentList) { return null; } }; diff --git a/test/function/LispSpecialFunctionTest.java b/test/function/LispSpecialFunctionTest.java index 9ff4622..87c1b6a 100644 --- a/test/function/LispSpecialFunctionTest.java +++ b/test/function/LispSpecialFunctionTest.java @@ -14,7 +14,7 @@ public class LispSpecialFunctionTest { LispFunction lispFunction = new LispSpecialFunction() { @Override - public SExpression call(Cons argList) { + public SExpression call(Cons argumentList) { return null; } }; diff --git a/test/function/builtin/math/MODULOTest.java b/test/function/builtin/math/MODULOTest.java new file mode 100644 index 0000000..efe838b --- /dev/null +++ b/test/function/builtin/math/MODULOTest.java @@ -0,0 +1,115 @@ +package function.builtin.math; + +import static testutil.TestUtilities.assertSExpressionsMatch; +import static testutil.TestUtilities.evaluateString; + +import org.junit.Test; + +import function.ArgumentValidator.BadArgumentTypeException; +import function.ArgumentValidator.TooFewArgumentsException; +import function.ArgumentValidator.TooManyArgumentsException; +import function.builtin.math.MODULO.ModulusNotPositiveException; +import sexpression.LispNumber; +import testutil.SymbolAndFunctionCleaner; + +public class MODULOTest extends SymbolAndFunctionCleaner { + + @Test + public void mod() { + String input = "(mod 5 3)"; + + assertSExpressionsMatch(new LispNumber("2"), evaluateString(input)); + } + + @Test + public void modulo() { + String input = "(modulo 11 7)"; + + assertSExpressionsMatch(new LispNumber("4"), evaluateString(input)); + } + + @Test + public void moduloSymbol() { + String input = "(% 8 5)"; + + assertSExpressionsMatch(new LispNumber("3"), evaluateString(input)); + } + + @Test + public void dividendGreaterThanDivisor() { + String input = "(mod 21 19)"; + + assertSExpressionsMatch(new LispNumber("2"), evaluateString(input)); + } + + @Test + public void dividendLessThanDivisor() { + String input = "(mod 5 239)"; + + assertSExpressionsMatch(new LispNumber("5"), evaluateString(input)); + } + + @Test + public void dividendEqualToDivisor() { + String input = "(mod 5 5)"; + + assertSExpressionsMatch(new LispNumber("0"), evaluateString(input)); + } + + @Test + public void dividendMultipleOfDivisor() { + String input = "(mod 20 5)"; + + assertSExpressionsMatch(new LispNumber("0"), evaluateString(input)); + } + + @Test + public void divisorOfOne() { + String input = "(mod 5 1)"; + + assertSExpressionsMatch(new LispNumber("0"), evaluateString(input)); + } + + @Test + public void dividendOfZero() { + String input = "(mod 0 2309)"; + + assertSExpressionsMatch(new LispNumber("0"), evaluateString(input)); + } + + @Test + public void negativeDividend() { + String input = "(mod -23 25)"; + + assertSExpressionsMatch(new LispNumber("2"), evaluateString(input)); + } + + @Test(expected = ModulusNotPositiveException.class) + public void divisorOfZero() { + String input = "(mod 5 0)"; + + assertSExpressionsMatch(new LispNumber("0"), evaluateString(input)); + } + + @Test(expected = ModulusNotPositiveException.class) + public void negativeDivisor() { + String input = "(mod 5 -10)"; + + assertSExpressionsMatch(new LispNumber("0"), evaluateString(input)); + } + + @Test(expected = BadArgumentTypeException.class) + public void modWithNonNumber() { + evaluateString("(mod 'a 'b)"); + } + + @Test(expected = TooFewArgumentsException.class) + public void modWithTooFewArguments() { + evaluateString("(mod 1)"); + } + + @Test(expected = TooManyArgumentsException.class) + public void modWithTooManyArguments() { + evaluateString("(mod 1 2 3)"); + } +} diff --git a/test/table/FunctionTableTest.java b/test/table/FunctionTableTest.java index 0e24546..254d023 100644 --- a/test/table/FunctionTableTest.java +++ b/test/table/FunctionTableTest.java @@ -34,7 +34,7 @@ public class FunctionTableTest { public GoodFunction(String name) {} @Override - public SExpression call(Cons argList) { + public SExpression call(Cons argumentList) { return NIL; } } @@ -43,7 +43,7 @@ public class FunctionTableTest { public static class BadFunction extends LispFunction { @Override - public SExpression call(Cons argList) { + public SExpression call(Cons argumentList) { return NIL; } } @@ -51,7 +51,7 @@ public class FunctionTableTest { public static class UglyFunction extends LispFunction { @Override - public SExpression call(Cons argList) { + public SExpression call(Cons argumentList) { return NIL; } } @@ -60,7 +60,7 @@ public class FunctionTableTest { return new LispFunction() { @Override - public SExpression call(Cons argList) { + public SExpression call(Cons argumentList) { return T; } };