diff --git a/src/function/builtin/LOAD.java b/src/function/builtin/LOAD.java index 22e6963..29d9c50 100644 --- a/src/function/builtin/LOAD.java +++ b/src/function/builtin/LOAD.java @@ -1,82 +1,84 @@ package function.builtin; -import java.io.FileInputStream; -import java.io.FileNotFoundException; +import java.io.*; +import java.text.MessageFormat; -import function.LispFunction; -import function.builtin.cons.LENGTH; +import environment.Environment; +import function.*; import parser.LispParser; import sexpression.*; -/** - * LOAD represents the LOAD function in Lisp. - */ public class LOAD extends LispFunction { - // The number of arguments that LOAD takes. - private static final int NUM_ARGS = 1; - public SExpression call(Cons argList) { - // retrieve the number of arguments passed to LOAD - int argListLength = LENGTH.getLength(argList); + private ArgumentValidator argumentValidator; + private Environment environment; - // make sure we have received the proper number of arguments - if (argListLength != NUM_ARGS) { - Cons originalSExpr = new Cons(new Symbol("LOAD"), argList); - String errMsg = "too " + - ((argListLength > NUM_ARGS) ? "many" : "few") + - " arguments given to LOAD: " + originalSExpr; + public LOAD() { + this.argumentValidator = new ArgumentValidator("LOAD"); + this.argumentValidator.setExactNumberOfArguments(1); + this.argumentValidator.setEveryArgumentExpectedType(LispString.class); + this.environment = Environment.getInstance(); + } - throw new RuntimeException(errMsg); - } + public SExpression call(Cons argumentList) { + argumentValidator.validate(argumentList); - SExpression argCar = argList.getCar(); - - // make sure the argument is a string - if (! argCar.stringp()) { - throw new RuntimeException("LOAD: " + argCar + " is not a string"); - } - - LispString quotedName = (LispString) argCar; - String fileName = quotedName.toString(); - - // remove the surrounding quotes from the file name - fileName = fileName.substring(1, (fileName.length() - 1)); + LispString quotedName = (LispString) argumentList.getCar(); + String fileName = removeSurroundingQuotes(quotedName.toString()); return processFile(fileName); } - // Evaluate all the S-expressions found in the file with the specified - // name. - // - // Parameters: fileName - the name of the file to be evaluated - // Returns: 'T' if the file was processed successfully; 'NIL' otherwise + private String removeSurroundingQuotes(String fileName) { + return fileName.substring(1, (fileName.length() - 1)); + } + private SExpression processFile(String fileName) { + boolean wasSuccessful = false; + LispParser parser = attemptToCreateParserOnFile(fileName); + + if (parser != null) + wasSuccessful = isSuccessfulEvaluation(parser); + + return wasSuccessful ? Symbol.T : Nil.getInstance(); + } + + private LispParser attemptToCreateParserOnFile(String fileName) { LispParser parser = null; - // attempt to create a new 'LispParser' on the specified file try { parser = new LispParser(new FileInputStream(fileName), fileName); } catch (FileNotFoundException e) { - System.out.println("LOAD: could not open " + fileName); - - return Nil.getInstance(); + printCouldNotOpenFileMessage(fileName); } - // attempt to evaluate all the S-expressions contained in the file - while (! parser.isEof()) { + return parser; + } + + private boolean isSuccessfulEvaluation(LispParser parser) { + while (!parser.isEof()) { try { SExpression sexpr = parser.getNextSExpression(); EVAL.eval(sexpr); } catch (RuntimeException e) { - System.out.println("LOAD: " + e.getMessage()); + printErrorMessage(e.getMessage()); - return Nil.getInstance(); + return false; } } + + return true; + } - // success! - return Symbol.T; + private void printCouldNotOpenFileMessage(String fileName) { + String message = MessageFormat.format("could not open ''{0}''", fileName); + printErrorMessage(message); + } + + private void printErrorMessage(String errorMessage) { + String message = MessageFormat.format("LOAD: {0}", errorMessage); + environment.getErrorOutput().println(message); } } diff --git a/src/function/builtin/special/LET.java b/src/function/builtin/special/LET.java index 8fdcfbb..6a8b04b 100644 --- a/src/function/builtin/special/LET.java +++ b/src/function/builtin/special/LET.java @@ -5,9 +5,6 @@ import function.builtin.EVAL; import sexpression.*; import table.SymbolTable; -/** - * LET represents the LET form in Lisp. - */ public class LET extends LispFunction { public SExpression call(Cons argList) { @@ -93,11 +90,6 @@ public class LET extends LispFunction { } } - /** - * Determine if the arguments passed to this Lisp function should be evaluated. - * - * @return false - */ public boolean evaluateArguments() { return false; } diff --git a/test/function/builtin/LOADTester.java b/test/function/builtin/LOADTester.java new file mode 100644 index 0000000..3ff8f2e --- /dev/null +++ b/test/function/builtin/LOADTester.java @@ -0,0 +1,69 @@ +package function.builtin; + +import static org.junit.Assert.assertEquals; +import static testutil.TestUtilities.*; + +import java.io.*; + +import org.junit.*; + +import environment.Environment; +import function.ArgumentValidator.*; + +public class LOADTester { + + private ByteArrayOutputStream outputStream; + + private void assertPrinted(String expected) { + assertEquals(expected, outputStream.toString()); + } + + private void assertNothingPrinted() { + assertPrinted(""); + } + + @Before + public void setUp() { + this.outputStream = new ByteArrayOutputStream(); + Environment.getInstance().setErrorOutput(new PrintStream(outputStream)); + } + + @Test + public void loadGoodFile_ReturnsTAndPrintsNothing() { + String input = "(load \"test/function/builtin/test-files/load-good.lisp\")"; + + assertSExpressionsMatch(parseString("T"), evaluateString(input)); + assertNothingPrinted(); + } + @Test + public void loadBadFile_ReturnsNilAndPrintsError() { + String input = "(load \"test/function/builtin/test-files/load-bad.lisp\")"; + + assertSExpressionsMatch(parseString("nil"), evaluateString(input)); + assertPrinted("LOAD: expression begins with ')' - line 1, column 1\n"); + } + + @Test + public void loadNonExistentFile_ReturnsNilAndPrintsError() { + String input = "(load \"doesNotExist.lisp\")"; + + assertSExpressionsMatch(parseString("nil"), evaluateString(input)); + assertPrinted("LOAD: could not open 'doesNotExist.lisp'\n"); + } + + @Test(expected = BadArgumentTypeException.class) + public void testLoadWithBadArgumentType() { + evaluateString("(load '1)"); + } + + @Test(expected = TooManyArgumentsException.class) + public void testLoadWithTooManyArguments() { + evaluateString("(load \"1\" \"2\")"); + } + + @Test(expected = TooFewArgumentsException.class) + public void testLoadWithTooFewArguments() { + evaluateString("(load)"); + } + +} diff --git a/test/function/builtin/test-files/load-bad.lisp b/test/function/builtin/test-files/load-bad.lisp new file mode 100644 index 0000000..c078133 --- /dev/null +++ b/test/function/builtin/test-files/load-bad.lisp @@ -0,0 +1,50 @@ +)(defun extend-null (the-list) + (cond + ((equal (length the-list) 0) t) + (t nil) + ) +) + +(defun mapcar (function-name the-list) + (cond + ((null the-list) nil) + (t (cons (funcall function-name (first the-list)) + (mapcar function-name (rest the-list)))) + ) +) + +(defun maplist (function-name the-list) + (cond + ((null the-list) nil) + (t (cons (funcall function-name the-list) + (maplist function-name (rest the-list)))) + ) +) + +(defun extend-apply (function-name param-list) + (eval (cons function-name param-list))) + +(defun append (listA listB) + (cond + ((null listA) listB) + (t (cons (first listA) (append (rest listA) listB))) + ) +) + +(defun second (listA) (first (rest listA))) +(defun third (listA) (first (rest (rest listA)))) +(defun fourth (listA) (first (rest (rest (rest listA))))) +(defun fifth (listA) (first (rest (rest (rest (rest listA)))))) +(defun sixth (listA) (first (rest (rest (rest (rest (rest listA))))))) +(defun seventh (listA) (first (rest (rest (rest (rest (rest (rest listA)))))))) +(defun eighth (listA) (first (rest (rest (rest (rest (rest (rest (rest listA))))))))) +(defun ninth (listA) (first (rest (rest (rest (rest (rest (rest (rest (rest listA)))))))))) +(defun tenth (listA) (first (rest (rest (rest (rest (rest (rest (rest (rest (rest listA))))))))))) + +(defun nth (n listA) + (cond + ((equal 0 n) (first listA)) + (t (nth (- n 1) (rest listA))) + ) +) + diff --git a/test/function/builtin/test-files/load-good.lisp b/test/function/builtin/test-files/load-good.lisp new file mode 100644 index 0000000..403108d --- /dev/null +++ b/test/function/builtin/test-files/load-good.lisp @@ -0,0 +1,50 @@ +(defun extend-null (the-list) + (cond + ((equal (length the-list) 0) t) + (t nil) + ) +) + +(defun mapcar (function-name the-list) + (cond + ((null the-list) nil) + (t (cons (funcall function-name (first the-list)) + (mapcar function-name (rest the-list)))) + ) +) + +(defun maplist (function-name the-list) + (cond + ((null the-list) nil) + (t (cons (funcall function-name the-list) + (maplist function-name (rest the-list)))) + ) +) + +(defun extend-apply (function-name param-list) + (eval (cons function-name param-list))) + +(defun append (listA listB) + (cond + ((null listA) listB) + (t (cons (first listA) (append (rest listA) listB))) + ) +) + +(defun second (listA) (first (rest listA))) +(defun third (listA) (first (rest (rest listA)))) +(defun fourth (listA) (first (rest (rest (rest listA))))) +(defun fifth (listA) (first (rest (rest (rest (rest listA)))))) +(defun sixth (listA) (first (rest (rest (rest (rest (rest listA))))))) +(defun seventh (listA) (first (rest (rest (rest (rest (rest (rest listA)))))))) +(defun eighth (listA) (first (rest (rest (rest (rest (rest (rest (rest listA))))))))) +(defun ninth (listA) (first (rest (rest (rest (rest (rest (rest (rest (rest listA)))))))))) +(defun tenth (listA) (first (rest (rest (rest (rest (rest (rest (rest (rest (rest listA))))))))))) + +(defun nth (n listA) + (cond + ((equal 0 n) (first listA)) + (t (nth (- n 1) (rest listA))) + ) +) +