diff --git a/src/main/kotlin/function/builtin/BackquoteEvaluator.java b/src/main/kotlin/function/builtin/BackquoteEvaluator.java deleted file mode 100644 index 16bfa09..0000000 --- a/src/main/kotlin/function/builtin/BackquoteEvaluator.java +++ /dev/null @@ -1,204 +0,0 @@ -package function.builtin; - -import error.LispException; -import function.ArgumentValidator; -import sexpression.AtSignExpression; -import sexpression.BackquoteExpression; -import sexpression.CommaExpression; -import sexpression.Cons; -import sexpression.Nil; -import sexpression.SExpression; - -import static function.builtin.Eval.eval; - -class BackquoteEvaluator { - - private ArgumentValidator listValidator; - private ArgumentValidator atSignValidator; - private BackquoteExpression backTick; - private Cons resolvedList; - private Cons leader; - private Cons follower; - - public BackquoteEvaluator(BackquoteExpression backTick) { - this.listValidator = new ArgumentValidator("`|list|"); - this.atSignValidator = new ArgumentValidator("@|list|"); - this.backTick = backTick; - this.resolvedList = new Cons(Nil.INSTANCE, Nil.INSTANCE); - this.leader = resolvedList; - this.follower = resolvedList; - } - - public SExpression evaluate() { - SExpression expression = backTick.getExpression(); - - if (expression.isCons()) - expression = resolveList((Cons) expression); - else if (expression.isComma()) - expression = eval(((CommaExpression) expression).getExpression()); - else if (expression.isAtSign()) - throw new AtSignNotInCommaException(); - - return expression; - } - - public SExpression resolveList(Cons list) { - listValidator.validate(list); - createResolvedList(list); - - return resolvedList; - } - - private void createResolvedList(Cons list) { - for (; list.isCons(); list = (Cons) list.getRest()) - resolveExpression(list.getFirst()); - - follower.setRest(Nil.INSTANCE); - } - - private void resolveExpression(SExpression expression) { - if (expression.isAtSign()) - throw new AtSignNotInCommaException(); - else if (expression.isComma()) - resolveCommaExpression(expression); - else if (expression.isList()) - resolveListExpression(expression); - else - addResolvedExpression(expression); - } - - private void resolveCommaExpression(SExpression expression) { - CommaEvaluationResult result = evaluateComma((CommaExpression) expression); - - if (result.isAtSign()) - unpackResolvedList((Cons) result.getResult()); - else - addResolvedExpression(result.getResult()); - } - - private CommaEvaluationResult evaluateComma(CommaExpression comma) { - SExpression expression = comma.getExpression(); - validateCommaExpression(expression); - - if (expression.isAtSign()) - return new CommaEvaluationAtSignResult(evaluateAtSign((AtSignExpression) expression)); - else - return new CommaEvaluationResult(eval(expression)); - } - - private void validateCommaExpression(SExpression expression) { - if (expression.isComma()) - throw new NestedCommaException(); - } - - private Cons evaluateAtSign(AtSignExpression atSign) { - SExpression expression = atSign.getExpression(); - validateAtSignUnevaluatedExpression(expression); - SExpression evaluation = eval(expression); - - return getValidatedList(evaluation); - } - - private void validateAtSignUnevaluatedExpression(SExpression expression) { - if (expression.isComma()) - throw new NestedCommaException(); - else if (expression.isAtSign()) - throw new NestedAtSignException(); - } - - private Cons getValidatedList(SExpression evaluation) { - if (!evaluation.isList()) - throw new AtSignNotListException(); - - Cons evaluatedList = (Cons) evaluation; - atSignValidator.validate(evaluatedList); - - return evaluatedList; - } - - private void unpackResolvedList(Cons list) { - for (; list.isCons(); list = (Cons) list.getRest()) - addResolvedExpression(list.getFirst()); - } - - private void addResolvedExpression(SExpression expression) { - leader.setFirst(expression); - leader.setRest(new Cons(Nil.INSTANCE, Nil.INSTANCE)); - follower = leader; - leader = (Cons) leader.getRest(); - } - - private void resolveListExpression(SExpression expression) { - BackquoteEvaluator evaluator = new BackquoteEvaluator(new BackquoteExpression(expression)); - addResolvedExpression(evaluator.evaluate()); - } - - private static class CommaEvaluationResult { - - private SExpression result; - - public CommaEvaluationResult(SExpression result) { - this.result = result; - } - - public SExpression getResult() { - return result; - } - - public boolean isAtSign() { - return false; - } - } - - private static class CommaEvaluationAtSignResult extends CommaEvaluationResult { - - public CommaEvaluationAtSignResult(SExpression result) { - super(result); - } - - @Override - public boolean isAtSign() { - return true; - } - } - - public static class NestedCommaException extends LispException { - - private static final long serialVersionUID = 1L; - - @Override - public String getMessage() { - return "nested comma"; - } - } - - public static class NestedAtSignException extends LispException { - - private static final long serialVersionUID = 1L; - - @Override - public String getMessage() { - return "nested at sign"; - } - } - - public static class AtSignNotInCommaException extends LispException { - - private static final long serialVersionUID = 1L; - - @Override - public String getMessage() { - return "at sign not in comma"; - } - } - - public static class AtSignNotListException extends LispException { - - private static final long serialVersionUID = 1L; - - @Override - public String getMessage() { - return "at sign did not evaluate to a list"; - } - } -} diff --git a/src/main/kotlin/function/builtin/BackquoteEvaluator.kt b/src/main/kotlin/function/builtin/BackquoteEvaluator.kt new file mode 100644 index 0000000..e6b06c0 --- /dev/null +++ b/src/main/kotlin/function/builtin/BackquoteEvaluator.kt @@ -0,0 +1,148 @@ +package function.builtin + +import error.LispException +import function.ArgumentValidator +import function.builtin.Eval.Companion.eval +import sexpression.AtSignExpression +import sexpression.BackquoteExpression +import sexpression.CommaExpression +import sexpression.Cons +import sexpression.Nil +import sexpression.SExpression + +internal class BackquoteEvaluator(private val backTick: BackquoteExpression) { + + private val listValidator: ArgumentValidator = ArgumentValidator("`|list|") + private val atSignValidator: ArgumentValidator = ArgumentValidator("@|list|") + private val resolvedList: Cons = Cons(Nil, Nil) + private var leader = resolvedList + private var follower = resolvedList + + fun evaluate(): SExpression { + var expression = backTick.expression + + if (expression.isCons) + expression = resolveList(expression as Cons) + else if (expression.isComma) + expression = eval((expression as CommaExpression).expression) + else if (expression.isAtSign) + throw AtSignNotInCommaException() + + return expression + } + + private fun resolveList(list: Cons): SExpression { + listValidator.validate(list) + createResolvedList(list) + + return resolvedList + } + + private fun createResolvedList(list: Cons) { + list.forEach { resolveExpression(it.first) } + + follower.rest = Nil + } + + private fun resolveExpression(expression: SExpression) = when { + expression.isAtSign -> throw AtSignNotInCommaException() + expression.isComma -> resolveCommaExpression(expression) + expression.isList -> resolveListExpression(expression) + else -> addResolvedExpression(expression) + } + + private fun resolveCommaExpression(expression: SExpression) { + val result = evaluateComma(expression as CommaExpression) + + if (result.isAtSign) + unpackResolvedList(result.result as Cons) + else + addResolvedExpression(result.result) + } + + private fun evaluateComma(comma: CommaExpression): CommaEvaluationResult { + val expression = comma.expression + validateCommaExpression(expression) + + return if (expression.isAtSign) + CommaEvaluationAtSignResult(evaluateAtSign(expression as AtSignExpression)) + else + CommaEvaluationResult(eval(expression)) + } + + private fun validateCommaExpression(expression: SExpression) { + if (expression.isComma) + throw NestedCommaException() + } + + private fun evaluateAtSign(atSign: AtSignExpression): Cons { + val expression = atSign.expression + validateAtSignUnevaluatedExpression(expression) + val evaluation = eval(expression) + + return getValidatedList(evaluation) + } + + private fun validateAtSignUnevaluatedExpression(expression: SExpression) { + if (expression.isComma) + throw NestedCommaException() + else if (expression.isAtSign) + throw NestedAtSignException() + } + + private fun getValidatedList(evaluation: SExpression): Cons { + if (!evaluation.isList) + throw AtSignNotListException() + + val evaluatedList = evaluation as Cons + atSignValidator.validate(evaluatedList) + + return evaluatedList + } + + private fun unpackResolvedList(list: Cons) { + list.forEach { addResolvedExpression(it.first) } + } + + private fun addResolvedExpression(expression: SExpression) { + leader.first = expression + leader.rest = Cons(Nil, Nil) + follower = leader + leader = leader.rest as Cons + } + + private fun resolveListExpression(expression: SExpression) { + val evaluator = BackquoteEvaluator(BackquoteExpression(expression)) + addResolvedExpression(evaluator.evaluate()) + } + + private open class CommaEvaluationResult(val result: SExpression) { + + open val isAtSign = false + } + + private class CommaEvaluationAtSignResult(result: SExpression) : CommaEvaluationResult(result) { + + override val isAtSign = true + } + + class NestedCommaException : LispException() { + + override val message = "nested comma" + } + + class NestedAtSignException : LispException() { + + override val message = "nested at sign" + } + + class AtSignNotInCommaException : LispException() { + + override val message = "at sign not in comma" + } + + class AtSignNotListException : LispException() { + + override val message = "at sign did not evaluate to a list" + } +} diff --git a/src/main/kotlin/function/builtin/LOAD.java b/src/main/kotlin/function/builtin/LOAD.java deleted file mode 100644 index 031cc0a..0000000 --- a/src/main/kotlin/function/builtin/LOAD.java +++ /dev/null @@ -1,117 +0,0 @@ -package function.builtin; - -import environment.RuntimeEnvironment; -import error.LispException; -import error.LispWarning; -import function.ArgumentValidator; -import function.FunctionNames; -import function.LispFunction; -import parser.LispParser; -import sexpression.Cons; -import sexpression.LispString; -import sexpression.Nil; -import sexpression.SExpression; -import sexpression.Symbol; -import util.Path; - -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.util.Stack; - -import static function.builtin.Eval.eval; -import static java.text.MessageFormat.format; - -@FunctionNames({ "LOAD" }) -public class LOAD extends LispFunction { - - private static Stack pathPrefixes = new Stack<>(); - private ArgumentValidator argumentValidator; - private RuntimeEnvironment environment; - - public LOAD(String name) { - this.argumentValidator = new ArgumentValidator(name); - this.argumentValidator.setExactNumberOfArguments(1); - this.argumentValidator.setEveryArgumentExpectedType(LispString.class); - this.environment = RuntimeEnvironment.INSTANCE; - } - - @Override - public SExpression call(Cons argumentList) { - argumentValidator.validate(argumentList); - - LispString quotedName = (LispString) argumentList.getFirst(); - String fileName = removeSurroundingQuotes(quotedName.toString()); - - return processFile(fileName); - } - - private String removeSurroundingQuotes(String fileName) { - return fileName.substring(1, (fileName.length() - 1)); - } - - private SExpression processFile(String fileName) { - boolean isSuccessful = false; - String prefixedFileName = prefixFileNameIfNecessary(fileName); - LispParser parser = attemptToCreateParserOnFile(prefixedFileName); - - if (parser != null) - isSuccessful = isSuccessfulEvaluationWithPathPrefix(prefixedFileName, parser); - - return isSuccessful ? Symbol.Companion.getT() : Nil.INSTANCE; - } - - private String prefixFileNameIfNecessary(String fileName) { - if (pathPrefixes.isEmpty()) - return environment.getPath() + fileName; - - return pathPrefixes.peek() + fileName; - } - - private LispParser attemptToCreateParserOnFile(String fileName) { - LispParser parser = null; - - try { - parser = new LispParser(new FileInputStream(fileName), fileName); - } catch (FileNotFoundException e) { - environment.getErrorManager().handle(new CouldNotLoadFileWarning(fileName)); - } - - return parser; - } - - private boolean isSuccessfulEvaluationWithPathPrefix(String prefixedFileName, LispParser parser) { - pathPrefixes.push(Path.INSTANCE.getPathPrefix(prefixedFileName)); - boolean isSuccessful = isSuccessfulEvaluation(parser); - pathPrefixes.pop(); - - return isSuccessful; - } - - private boolean isSuccessfulEvaluation(LispParser parser) { - while (!parser.isEof()) { - try { - eval(parser.nextSExpression()); - } catch (LispException e) { - environment.getErrorManager().handle(e); - return false; - } - } - - return true; - } - - public static class CouldNotLoadFileWarning extends LispWarning { - - private static final long serialVersionUID = 1L; - private String fileName; - - public CouldNotLoadFileWarning(String fileName) { - this.fileName = fileName; - } - - @Override - public String getMessage() { - return format("could not load ''{0}''", fileName); - } - } -} diff --git a/src/main/kotlin/function/builtin/Load.kt b/src/main/kotlin/function/builtin/Load.kt new file mode 100644 index 0000000..e37b933 --- /dev/null +++ b/src/main/kotlin/function/builtin/Load.kt @@ -0,0 +1,96 @@ +package function.builtin + +import environment.RuntimeEnvironment +import error.LispException +import error.LispWarning +import function.ArgumentValidator +import function.FunctionNames +import function.LispFunction +import function.builtin.Eval.Companion.eval +import parser.LispParser +import sexpression.Cons +import sexpression.LispString +import sexpression.Nil +import sexpression.SExpression +import sexpression.Symbol +import util.Path +import java.io.FileInputStream +import java.io.FileNotFoundException +import java.util.Stack + +@FunctionNames("LOAD") +class Load(name: String) : LispFunction() { + + private val argumentValidator: ArgumentValidator = ArgumentValidator(name).apply { + setExactNumberOfArguments(1) + setEveryArgumentExpectedType(LispString::class.java) + } + + override fun call(argumentList: Cons): SExpression { + argumentValidator.validate(argumentList) + + val quotedName = argumentList.first.toString() + val fileName = quotedName.removeSurrounding("\"") + + return processFile(fileName) + } + + private fun processFile(fileName: String): SExpression { + var isSuccessful = false + val prefixedFileName = prefixFileNameIfNecessary(fileName) + val parser = attemptToCreateParserOnFile(prefixedFileName) + + if (parser != null) + isSuccessful = isSuccessfulEvaluationWithPathPrefix(prefixedFileName, parser) + + return if (isSuccessful) Symbol.T else Nil + } + + private fun prefixFileNameIfNecessary(fileName: String) = + if (pathPrefixes.isEmpty()) + RuntimeEnvironment.path!! + fileName + else + pathPrefixes.peek() + fileName + + private fun attemptToCreateParserOnFile(fileName: String): LispParser? { + var parser: LispParser? = null + + try { + parser = LispParser(FileInputStream(fileName), fileName) + } catch (e: FileNotFoundException) { + RuntimeEnvironment.errorManager!!.handle(CouldNotLoadFileWarning(fileName)) + } + + return parser + } + + private fun isSuccessfulEvaluationWithPathPrefix(prefixedFileName: String, parser: LispParser): Boolean { + pathPrefixes.push(Path.getPathPrefix(prefixedFileName)) + val isSuccessful = isSuccessfulEvaluation(parser) + pathPrefixes.pop() + + return isSuccessful + } + + private fun isSuccessfulEvaluation(parser: LispParser): Boolean { + while (!parser.isEof()) { + try { + eval(parser.nextSExpression()) + } catch (e: LispException) { + RuntimeEnvironment.errorManager!!.handle(e) + return false + } + } + + return true + } + + class CouldNotLoadFileWarning(fileName: String) : LispWarning() { + + override val message = "could not load '$fileName'" + } + + companion object { + private val pathPrefixes = Stack() + } +} diff --git a/src/main/kotlin/function/builtin/SET.java b/src/main/kotlin/function/builtin/SET.java deleted file mode 100644 index cef84b7..0000000 --- a/src/main/kotlin/function/builtin/SET.java +++ /dev/null @@ -1,56 +0,0 @@ -package function.builtin; - -import function.ArgumentValidator; -import function.FunctionNames; -import function.LispFunction; -import sexpression.Cons; -import sexpression.SExpression; -import sexpression.Symbol; -import table.ExecutionContext; -import table.FunctionTable; -import table.SymbolTable; - -@FunctionNames({ "SET" }) -public class SET extends LispFunction { - - public static SExpression set(Cons argumentList) { - return FunctionTable.INSTANCE.lookupFunction("SET").call(argumentList); - } - - private ArgumentValidator argumentValidator; - private ExecutionContext executionContext; - - public SET(String name) { - this.argumentValidator = new ArgumentValidator(name); - this.argumentValidator.setExactNumberOfArguments(2); - this.argumentValidator.setFirstArgumentExpectedType(Symbol.class); - this.executionContext = ExecutionContext.INSTANCE; - } - - @Override - public SExpression call(Cons argumentList) { - argumentValidator.validate(argumentList); - - Cons rest = (Cons) argumentList.getRest(); - SExpression symbol = argumentList.getFirst(); - SExpression value = rest.getFirst(); - - SymbolTable table = findScopeOfSymbol(symbol); - table.set(symbol.toString(), value); - - return value; - } - - private SymbolTable findScopeOfSymbol(SExpression symbol) { - SymbolTable table = executionContext.getScope(); - - while (!isSymbolInTable(symbol, table) && !table.isGlobal()) - table = table.getParent(); - - return table; - } - - private boolean isSymbolInTable(SExpression symbol, SymbolTable table) { - return table.contains(symbol.toString()); - } -} diff --git a/src/main/kotlin/function/builtin/Set.kt b/src/main/kotlin/function/builtin/Set.kt new file mode 100644 index 0000000..7a9693b --- /dev/null +++ b/src/main/kotlin/function/builtin/Set.kt @@ -0,0 +1,52 @@ +package function.builtin + +import function.ArgumentValidator +import function.FunctionNames +import function.LispFunction +import sexpression.Cons +import sexpression.SExpression +import sexpression.Symbol +import table.ExecutionContext +import table.FunctionTable +import table.SymbolTable + +@FunctionNames("SET") +class Set(name: String) : LispFunction() { + + private val argumentValidator: ArgumentValidator = ArgumentValidator(name).apply { + setExactNumberOfArguments(2) + setFirstArgumentExpectedType(Symbol::class.java) + + } + + override fun call(argumentList: Cons): SExpression { + argumentValidator.validate(argumentList) + + val rest = argumentList.rest as Cons + val symbol = argumentList.first + val value = rest.first + + val table = findScopeOfSymbol(symbol) + table[symbol.toString()] = value + + return value + } + + private fun findScopeOfSymbol(symbol: SExpression): SymbolTable { + var table: SymbolTable = ExecutionContext.scope + + while (!isSymbolInTable(symbol, table) && !table.isGlobal()) + table = table.parent!! + + return table + } + + private fun isSymbolInTable(symbol: SExpression, table: SymbolTable) = + table.contains(symbol.toString()) + + companion object { + + @JvmStatic + fun set(argumentList: Cons) = FunctionTable.lookupFunction("SET")!!.call(argumentList) + } +} diff --git a/src/main/kotlin/function/builtin/special/SETQ.java b/src/main/kotlin/function/builtin/special/SETQ.java index 8d3f3a6..4fae57e 100644 --- a/src/main/kotlin/function/builtin/special/SETQ.java +++ b/src/main/kotlin/function/builtin/special/SETQ.java @@ -8,7 +8,7 @@ import sexpression.SExpression; import sexpression.Symbol; import static function.builtin.Eval.eval; -import static function.builtin.SET.set; +import static function.builtin.Set.set; import static function.builtin.cons.LIST.makeList; @FunctionNames({ "SETQ" }) diff --git a/src/test/kotlin/function/builtin/BackquoteEvaluatorTest.java b/src/test/kotlin/function/builtin/BackquoteEvaluatorTest.java deleted file mode 100644 index 318eeac..0000000 --- a/src/test/kotlin/function/builtin/BackquoteEvaluatorTest.java +++ /dev/null @@ -1,180 +0,0 @@ -package function.builtin; - -import function.ArgumentValidator.DottedArgumentListException; -import function.builtin.BackquoteEvaluator.AtSignNotInCommaException; -import function.builtin.BackquoteEvaluator.AtSignNotListException; -import function.builtin.BackquoteEvaluator.NestedAtSignException; -import function.builtin.BackquoteEvaluator.NestedCommaException; -import org.junit.Test; -import sexpression.AtSignExpression; -import sexpression.BackquoteExpression; -import sexpression.CommaExpression; -import sexpression.Cons; -import sexpression.LispNumber; -import sexpression.LispString; -import sexpression.Nil; -import sexpression.SExpression; -import sexpression.Symbol; - -import static testutil.TestUtilities.assertIsErrorWithMessage; -import static testutil.TestUtilities.assertSExpressionsMatch; -import static testutil.TestUtilities.makeList; - -public class BackquoteEvaluatorTest { - - private BackquoteEvaluator createBackquoteEvaluator(SExpression expression) { - return new BackquoteEvaluator(new BackquoteExpression(expression)); - } - - @Test - public void evaluateNil() { - BackquoteEvaluator evaluator = createBackquoteEvaluator(Nil.INSTANCE); - - assertSExpressionsMatch(Nil.INSTANCE, evaluator.evaluate()); - } - - @Test - public void evaluateNumber() { - SExpression input = new LispNumber("99"); - BackquoteEvaluator evaluator = createBackquoteEvaluator(input); - - assertSExpressionsMatch(input, evaluator.evaluate()); - } - - @Test - public void evaluateList() { - SExpression input = makeList(new LispNumber("1"), new LispNumber("99")); - BackquoteEvaluator evaluator = createBackquoteEvaluator(input); - - assertSExpressionsMatch(input, evaluator.evaluate()); - } - - @Test - public void evaluateComma() { - SExpression input = new CommaExpression(makeList(new Symbol("+"), new LispNumber("1"), new LispNumber("9"))); - BackquoteEvaluator evaluator = createBackquoteEvaluator(input); - - assertSExpressionsMatch(new LispNumber("10"), evaluator.evaluate()); - } - - @Test - public void evaluateListWithComma() { - SExpression input = makeList(new CommaExpression(makeList(new Symbol("+"), - new LispNumber("1"), - new LispNumber("9")))); - BackquoteEvaluator evaluator = createBackquoteEvaluator(input); - - assertSExpressionsMatch(makeList(new LispNumber("10")), evaluator.evaluate()); - } - - @Test(expected = NestedCommaException.class) - public void evaluateListWithNestedComma() { - SExpression input = makeList(new CommaExpression(new CommaExpression(new Symbol("+")))); - BackquoteEvaluator evaluator = createBackquoteEvaluator(input); - - evaluator.evaluate(); - } - - @Test(expected = AtSignNotInCommaException.class) - public void evaluateListWithNoCommaPrecedingAtSign() { - SExpression input = makeList(new AtSignExpression(makeList(new Symbol("+")))); - BackquoteEvaluator evaluator = createBackquoteEvaluator(input); - - evaluator.evaluate(); - } - - @Test(expected = AtSignNotInCommaException.class) - public void evaluateAtSign() { - SExpression input = new AtSignExpression(makeList(new Symbol("+"))); - BackquoteEvaluator evaluator = createBackquoteEvaluator(input); - - evaluator.evaluate(); - } - - @Test(expected = NestedAtSignException.class) - public void evaluateListWithNestedAtSigns() { - SExpression input = - makeList(new CommaExpression(new AtSignExpression(new AtSignExpression(makeList(new Symbol("+")))))); - BackquoteEvaluator evaluator = createBackquoteEvaluator(input); - - evaluator.evaluate(); - } - - @Test(expected = NestedCommaException.class) - public void evaluateListWithCommaAfterAtSign() { - SExpression input = - makeList(new CommaExpression(new AtSignExpression(new CommaExpression(makeList(new Symbol("+")))))); - BackquoteEvaluator evaluator = createBackquoteEvaluator(input); - - evaluator.evaluate(); - } - - @Test - public void evaluateListWithAtSign() { - SExpression input = makeList(new CommaExpression(new AtSignExpression(makeList(new Symbol("LIST"), - new LispNumber("1"), - new LispNumber("9"))))); - SExpression expected = makeList(new LispNumber("1"), new LispNumber("9")); - BackquoteEvaluator evaluator = createBackquoteEvaluator(input); - - assertSExpressionsMatch(expected, evaluator.evaluate()); - } - - @Test(expected = AtSignNotListException.class) - public void atSignDoesNotEvaluateToList() { - SExpression input = makeList(new CommaExpression(new AtSignExpression(new LispNumber("1")))); - BackquoteEvaluator evaluator = createBackquoteEvaluator(input); - - evaluator.evaluate(); - } - - @Test - public void evaluateListWithCommasAndAtSign() { - Cons list1 = makeList(new Symbol("LIST"), new LispNumber("1"), new LispNumber("9")); - Cons list2 = makeList(new Symbol("+"), new LispNumber("20"), new LispNumber("5")); - Cons list3 = makeList(new Symbol("LIST"), new LispNumber("7"), new LispNumber("6")); - - SExpression input = makeList(new LispNumber("78"), - new CommaExpression(new AtSignExpression(list1)), - new CommaExpression(list2), - new CommaExpression(list3), - new LispString("\"sky\"")); - - SExpression expected = makeList(new LispNumber("78"), - new LispNumber("1"), - new LispNumber("9"), - new LispNumber("25"), - makeList(new LispNumber("7"), new LispNumber("6")), - new LispString("\"sky\"")); - - BackquoteEvaluator evaluator = createBackquoteEvaluator(input); - - assertSExpressionsMatch(expected, evaluator.evaluate()); - } - - @Test(expected = DottedArgumentListException.class) - public void evaluateDottedList() { - BackquoteEvaluator evaluator = - createBackquoteEvaluator(new Cons(Symbol.Companion.getT(), Symbol.Companion.getT())); - - evaluator.evaluate(); - } - - @Test(expected = DottedArgumentListException.class) - public void atSignWithDottedList() { - SExpression input = makeList(new CommaExpression(new AtSignExpression(makeList(new Symbol("CONS"), - Symbol.Companion.getT(), - Symbol.Companion.getT())))); - BackquoteEvaluator evaluator = createBackquoteEvaluator(input); - - evaluator.evaluate(); - } - - @Test - public void backquoteExceptionsHaveCorrectAttributes() { - assertIsErrorWithMessage(new NestedCommaException()); - assertIsErrorWithMessage(new NestedAtSignException()); - assertIsErrorWithMessage(new AtSignNotInCommaException()); - assertIsErrorWithMessage(new AtSignNotListException()); - } -} diff --git a/src/test/kotlin/function/builtin/BackquoteEvaluatorTest.kt b/src/test/kotlin/function/builtin/BackquoteEvaluatorTest.kt new file mode 100644 index 0000000..63924af --- /dev/null +++ b/src/test/kotlin/function/builtin/BackquoteEvaluatorTest.kt @@ -0,0 +1,192 @@ +package function.builtin + +import function.ArgumentValidator.DottedArgumentListException +import function.builtin.BackquoteEvaluator.AtSignNotInCommaException +import function.builtin.BackquoteEvaluator.AtSignNotListException +import function.builtin.BackquoteEvaluator.NestedAtSignException +import function.builtin.BackquoteEvaluator.NestedCommaException +import org.junit.jupiter.api.Assertions.assertThrows +import org.junit.jupiter.api.Test +import sexpression.AtSignExpression +import sexpression.BackquoteExpression +import sexpression.CommaExpression +import sexpression.Cons +import sexpression.LispNumber +import sexpression.LispString +import sexpression.Nil +import sexpression.SExpression +import sexpression.Symbol +import sexpression.Symbol.Companion.T +import testutil.LispTestInstance +import testutil.TestUtilities.assertIsErrorWithMessage +import testutil.TestUtilities.assertSExpressionsMatch +import testutil.TestUtilities.makeList + +@LispTestInstance +class BackquoteEvaluatorTest { + + private fun createBackquoteEvaluator(expression: SExpression): BackquoteEvaluator { + return BackquoteEvaluator(BackquoteExpression(expression)) + } + + @Test + fun evaluateNil() { + val evaluator = createBackquoteEvaluator(Nil) + + assertSExpressionsMatch(Nil, evaluator.evaluate()) + } + + @Test + fun evaluateNumber() { + val input = LispNumber("99") + val evaluator = createBackquoteEvaluator(input) + + assertSExpressionsMatch(input, evaluator.evaluate()) + } + + @Test + fun evaluateList() { + val input = makeList(LispNumber("1"), LispNumber("99")) + val evaluator = createBackquoteEvaluator(input) + + assertSExpressionsMatch(input, evaluator.evaluate()) + } + + @Test + fun evaluateComma() { + val input = CommaExpression(makeList(Symbol("+"), LispNumber("1"), LispNumber("9"))) + val evaluator = createBackquoteEvaluator(input) + + assertSExpressionsMatch(LispNumber("10"), evaluator.evaluate()) + } + + @Test + fun evaluateListWithComma() { + val input = makeList(CommaExpression(makeList(Symbol("+"), LispNumber("1"), LispNumber("9")))) + val evaluator = createBackquoteEvaluator(input) + + assertSExpressionsMatch(makeList(LispNumber("10")), evaluator.evaluate()) + } + + @Test + fun evaluateListWithNestedComma() { + val input = makeList(CommaExpression(CommaExpression(Symbol("+")))) + val evaluator = createBackquoteEvaluator(input) + + assertThrows(NestedCommaException::class.java) { + evaluator.evaluate() + } + } + + @Test + fun evaluateListWithNoCommaPrecedingAtSign() { + val input = makeList(AtSignExpression(makeList(Symbol("+")))) + val evaluator = createBackquoteEvaluator(input) + + assertThrows(AtSignNotInCommaException::class.java) { + evaluator.evaluate() + } + } + + @Test + fun evaluateAtSign() { + val input = AtSignExpression(makeList(Symbol("+"))) + val evaluator = createBackquoteEvaluator(input) + + assertThrows(AtSignNotInCommaException::class.java) { + evaluator.evaluate() + } + } + + @Test + fun evaluateListWithNestedAtSigns() { + val input = makeList(CommaExpression(AtSignExpression(AtSignExpression(makeList(Symbol("+")))))) + val evaluator = createBackquoteEvaluator(input) + + assertThrows(NestedAtSignException::class.java) { + evaluator.evaluate() + } + } + + @Test + fun evaluateListWithCommaAfterAtSign() { + val input = makeList(CommaExpression(AtSignExpression(CommaExpression(makeList(Symbol("+")))))) + val evaluator = createBackquoteEvaluator(input) + + assertThrows(NestedCommaException::class.java) { + evaluator.evaluate() + } + } + + @Test + fun evaluateListWithAtSign() { + val input = makeList(CommaExpression(AtSignExpression(makeList(Symbol("LIST"), + LispNumber("1"), + LispNumber("9"))))) + val expected = makeList(LispNumber("1"), LispNumber("9")) + val evaluator = createBackquoteEvaluator(input) + + assertSExpressionsMatch(expected, evaluator.evaluate()) + } + + @Test + fun atSignDoesNotEvaluateToList() { + val input = makeList(CommaExpression(AtSignExpression(LispNumber("1")))) + val evaluator = createBackquoteEvaluator(input) + + assertThrows(AtSignNotListException::class.java) { + evaluator.evaluate() + } + } + + @Test + fun evaluateListWithCommasAndAtSign() { + val list1 = makeList(Symbol("LIST"), LispNumber("1"), LispNumber("9")) + val list2 = makeList(Symbol("+"), LispNumber("20"), LispNumber("5")) + val list3 = makeList(Symbol("LIST"), LispNumber("7"), LispNumber("6")) + + val input = makeList(LispNumber("78"), + CommaExpression(AtSignExpression(list1)), + CommaExpression(list2), + CommaExpression(list3), + LispString("\"sky\"")) + + val expected = makeList(LispNumber("78"), + LispNumber("1"), + LispNumber("9"), + LispNumber("25"), + makeList(LispNumber("7"), LispNumber("6")), + LispString("\"sky\"")) + + val evaluator = createBackquoteEvaluator(input) + + assertSExpressionsMatch(expected, evaluator.evaluate()) + } + + @Test + fun evaluateDottedList() { + val evaluator = createBackquoteEvaluator(Cons(T, T)) + + assertThrows(DottedArgumentListException::class.java) { + evaluator.evaluate() + } + } + + @Test + fun atSignWithDottedList() { + val input = makeList(CommaExpression(AtSignExpression(makeList(Symbol("CONS"), T, T)))) + val evaluator = createBackquoteEvaluator(input) + + assertThrows(DottedArgumentListException::class.java) { + evaluator.evaluate() + } + } + + @Test + fun backquoteExceptionsHaveCorrectAttributes() { + assertIsErrorWithMessage(NestedCommaException()) + assertIsErrorWithMessage(NestedAtSignException()) + assertIsErrorWithMessage(AtSignNotInCommaException()) + assertIsErrorWithMessage(AtSignNotListException()) + } +} diff --git a/src/test/kotlin/function/builtin/LOADTest.java b/src/test/kotlin/function/builtin/LOADTest.java deleted file mode 100644 index 5a54ec6..0000000 --- a/src/test/kotlin/function/builtin/LOADTest.java +++ /dev/null @@ -1,138 +0,0 @@ -package function.builtin; - -import environment.RuntimeEnvironment; -import error.ErrorManager; -import function.ArgumentValidator.BadArgumentTypeException; -import function.ArgumentValidator.TooFewArgumentsException; -import function.ArgumentValidator.TooManyArgumentsException; -import org.junit.Test; -import testutil.SymbolAndFunctionCleaner; - -import java.io.ByteArrayOutputStream; -import java.io.PrintStream; - -import static org.junit.Assert.assertTrue; -import static testutil.TestUtilities.evaluateString; -import static testutil.TypeAssertions.assertNil; -import static testutil.TypeAssertions.assertT; - -public class LOADTest extends SymbolAndFunctionCleaner { - - private ByteArrayOutputStream outputStream; - private ByteArrayOutputStream errorOutputStream; - private RuntimeEnvironment environment; - - public LOADTest() { - this.environment = RuntimeEnvironment.INSTANCE; - } - - private void assertWarningMessagePrinted() { - assertTrue(outputStream.toByteArray().length > 0); - assertTrue(errorOutputStream.toByteArray().length == 0); - } - - private void assertErrorMessagePrinted() { - assertTrue(errorOutputStream.toByteArray().length > 0); - assertTrue(outputStream.toByteArray().length == 0); - } - - private void assertNothingPrinted() { - assertTrue(errorOutputStream.toByteArray().length == 0); - assertTrue(outputStream.toByteArray().length == 0); - } - - @Override - public void additionalSetUp() { - outputStream = new ByteArrayOutputStream(); - errorOutputStream = new ByteArrayOutputStream(); - - environment.reset(); - environment.setOutput(new PrintStream(outputStream)); - environment.setErrorOutput(new PrintStream(errorOutputStream)); - environment.setErrorManager(new ErrorManager()); - environment.setPath(""); - environment.setWarningOutputDecorator(s -> s); - environment.setErrorOutputDecorator(s -> s); - } - - @Override - public void additionalTearDown() { - environment.reset(); - } - - @Test - public void loadEmptyFileName_ReturnsNilAndPrintsWarning() { - String input = "(load \"\")"; - - assertNil(evaluateString(input)); - assertWarningMessagePrinted(); - } - - @Test - public void loadGoodFile_ReturnsTAndPrintsNothing() { - String file = LOADTest.class.getResource("load-good.lisp").getFile(); - String input = "(load \"" + file + "\")"; - - assertT(evaluateString(input)); - assertNothingPrinted(); - } - - @Test - public void loadBadFile_ReturnsNilAndPrintsError() { - String file = LOADTest.class.getResource("load-bad.lisp").getFile(); - String input = "(load \"" + file + "\")"; - - assertNil(evaluateString(input)); - assertErrorMessagePrinted(); - } - - @Test - public void loadNonExistentFile_ReturnsNilAndPrintsWarning() { - String input = "(load \"doesNotExist.lisp\")"; - - assertNil(evaluateString(input)); - assertWarningMessagePrinted(); - } - - @Test - public void nestedLoadsInTheSameDirectory() { - String file = LOADTest.class.getResource("nested/nested.lisp").getFile(); - String input = "(load \"" + file + "\")"; - - assertT(evaluateString(input)); - assertNothingPrinted(); - } - - @Test - public void nestedLoadsInDifferentDirectories() { - String file = LOADTest.class.getResource("nested/one/load-one.lisp").getFile(); - String input = "(load \"" + file + "\")"; - - assertT(evaluateString(input)); - assertNothingPrinted(); - } - - @Test(expected = BadArgumentTypeException.class) - public void loadWithBadArgumentType() { - evaluateString("(load '1)"); - } - - @Test(expected = TooManyArgumentsException.class) - public void loadWithTooManyArguments() { - evaluateString("(load \"1\" \"2\")"); - } - - @Test(expected = TooFewArgumentsException.class) - public void loadWithTooFewArguments() { - evaluateString("(load)"); - } - - @Test - public void loadUsesRuntimePath() { - environment.setPath(LOADTest.class.getResource("nested/one/").getPath()); - String input = "(load \"load-one.lisp\")"; - - assertT(evaluateString(input)); - assertNothingPrinted(); - } -} diff --git a/src/test/kotlin/function/builtin/LoadTest.kt b/src/test/kotlin/function/builtin/LoadTest.kt new file mode 100644 index 0000000..5fefc07 --- /dev/null +++ b/src/test/kotlin/function/builtin/LoadTest.kt @@ -0,0 +1,138 @@ +package function.builtin + +import environment.RuntimeEnvironment +import error.ErrorManager +import function.ArgumentValidator.BadArgumentTypeException +import function.ArgumentValidator.TooFewArgumentsException +import function.ArgumentValidator.TooManyArgumentsException +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Assertions.assertThrows +import org.junit.jupiter.api.Test +import testutil.LispTestInstance +import testutil.SymbolAndFunctionCleaner +import testutil.TestUtilities.evaluateString +import testutil.TypeAssertions.assertNil +import testutil.TypeAssertions.assertT +import java.io.ByteArrayOutputStream +import java.io.PrintStream + +@LispTestInstance +class LoadTest : SymbolAndFunctionCleaner() { + + private var outputStream = ByteArrayOutputStream() + private var errorOutputStream = ByteArrayOutputStream() + + private fun assertWarningMessagePrinted() { + assertThat(outputStream.toByteArray()).isNotEmpty() + assertThat(errorOutputStream.toByteArray()).isEmpty() + } + + private fun assertErrorMessagePrinted() { + assertThat(errorOutputStream.toByteArray()).isNotEmpty() + assertThat(outputStream.toByteArray()).isEmpty() + } + + private fun assertNothingPrinted() { + assertThat(errorOutputStream.toByteArray()).isEmpty() + assertThat(outputStream.toByteArray()).isEmpty() + } + + override fun additionalSetUp() { + outputStream.reset() + errorOutputStream.reset() + + RuntimeEnvironment.reset() + RuntimeEnvironment.output = PrintStream(outputStream) + RuntimeEnvironment.errorOutput = PrintStream(errorOutputStream) + RuntimeEnvironment.errorManager = ErrorManager() + RuntimeEnvironment.path = "" + RuntimeEnvironment.warningOutputDecorator = { s -> s } + RuntimeEnvironment.errorOutputDecorator = { s -> s } + } + + override fun additionalTearDown() { + RuntimeEnvironment.reset() + } + + @Test + fun loadEmptyFileName_ReturnsNilAndPrintsWarning() { + val input = "(load \"\")" + + assertNil(evaluateString(input)) + assertWarningMessagePrinted() + } + + @Test + fun loadGoodFile_ReturnsTAndPrintsNothing() { + val file = LoadTest::class.java.getResource("load-good.lisp").file + val input = "(load \"$file\")" + + assertT(evaluateString(input)) + assertNothingPrinted() + } + + @Test + fun loadBadFile_ReturnsNilAndPrintsError() { + val file = LoadTest::class.java.getResource("load-bad.lisp").file + val input = "(load \"$file\")" + + assertNil(evaluateString(input)) + assertErrorMessagePrinted() + } + + @Test + fun loadNonExistentFile_ReturnsNilAndPrintsWarning() { + val input = "(load \"doesNotExist.lisp\")" + + assertNil(evaluateString(input)) + assertWarningMessagePrinted() + } + + @Test + fun nestedLoadsInTheSameDirectory() { + val file = LoadTest::class.java.getResource("nested/nested.lisp").file + val input = "(load \"$file\")" + + assertT(evaluateString(input)) + assertNothingPrinted() + } + + @Test + fun nestedLoadsInDifferentDirectories() { + val file = LoadTest::class.java.getResource("nested/one/load-one.lisp").file + val input = "(load \"$file\")" + + assertT(evaluateString(input)) + assertNothingPrinted() + } + + @Test + fun loadWithBadArgumentType() { + assertThrows(BadArgumentTypeException::class.java) { + evaluateString("(load '1)") + } + } + + @Test + fun loadWithTooManyArguments() { + assertThrows(TooManyArgumentsException::class.java) { + evaluateString("(load \"1\" \"2\")") + } + } + + @Test + fun loadWithTooFewArguments() { + assertThrows(TooFewArgumentsException::class.java) { + evaluateString("(load)") + } + } + + @Test + fun loadUsesRuntimePath() { + RuntimeEnvironment.path = LoadTest::class.java.getResource("nested/one/").path + val input = "(load \"load-one.lisp\")" + + assertT(evaluateString(input)) + assertNothingPrinted() + } +} diff --git a/src/test/kotlin/function/builtin/SETTest.java b/src/test/kotlin/function/builtin/SETTest.java deleted file mode 100644 index f8a462e..0000000 --- a/src/test/kotlin/function/builtin/SETTest.java +++ /dev/null @@ -1,83 +0,0 @@ -package function.builtin; - -import function.ArgumentValidator.BadArgumentTypeException; -import function.ArgumentValidator.TooFewArgumentsException; -import function.ArgumentValidator.TooManyArgumentsException; -import function.builtin.Eval.UndefinedSymbolException; -import org.junit.Test; -import sexpression.LispNumber; -import table.SymbolTable; -import testutil.SymbolAndFunctionCleaner; - -import static org.junit.Assert.assertNull; -import static testutil.TestUtilities.assertSExpressionsMatch; -import static testutil.TestUtilities.evaluateString; - -public class SETTest extends SymbolAndFunctionCleaner { - - @Test - public void set() { - evaluateString("(set 'a 23)"); - assertSExpressionsMatch(new LispNumber("23"), evaluateString("a")); - } - - @Test - public void lookupDefinedSymbol() { - evaluateString("(set 'a 23)"); - assertSExpressionsMatch(new LispNumber("23"), getExecutionContext().lookupSymbolValue("A")); - } - - @Test - public void lookupUndefinedSymbol() { - assertNull(getExecutionContext().lookupSymbolValue("A")); - } - - @Test - public void setGlobalVariable() { - evaluateString("(set 'a 23)"); - SymbolTable global = getExecutionContext().getScope(); - getExecutionContext().setScope(new SymbolTable(global)); - - evaluateString("(set 'a 94)"); - getExecutionContext().setScope(global); - assertSExpressionsMatch(new LispNumber("94"), evaluateString("a")); - } - - @Test(expected = UndefinedSymbolException.class) - public void setLocalVariableDefined_DoesNotSetGlobal() { - SymbolTable global = getExecutionContext().getScope(); - SymbolTable local = new SymbolTable(global); - local.set("A", new LispNumber("99")); - getExecutionContext().setScope(local); - - evaluateString("(set 'a 94)"); - getExecutionContext().setScope(global); - evaluateString("a"); - } - - @Test - public void setLocalVariableUndefined_SetsGlobal() { - SymbolTable global = getExecutionContext().getScope(); - SymbolTable local = new SymbolTable(global); - getExecutionContext().setScope(local); - - evaluateString("(set 'a 94)"); - getExecutionContext().setScope(global); - assertSExpressionsMatch(new LispNumber("94"), evaluateString("a")); - } - - @Test(expected = BadArgumentTypeException.class) - public void setWithNonSymbol() { - evaluateString("(set '1 2)"); - } - - @Test(expected = TooFewArgumentsException.class) - public void setWithTooFewArguments() { - evaluateString("(set 'x)"); - } - - @Test(expected = TooManyArgumentsException.class) - public void setWithTooManyArguments() { - evaluateString("(set 'a 'b 'c)"); - } -} diff --git a/src/test/kotlin/function/builtin/SetTest.kt b/src/test/kotlin/function/builtin/SetTest.kt new file mode 100644 index 0000000..c30db82 --- /dev/null +++ b/src/test/kotlin/function/builtin/SetTest.kt @@ -0,0 +1,94 @@ +package function.builtin + +import function.ArgumentValidator.BadArgumentTypeException +import function.ArgumentValidator.TooFewArgumentsException +import function.ArgumentValidator.TooManyArgumentsException +import function.builtin.Eval.UndefinedSymbolException +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Assertions.assertThrows +import org.junit.jupiter.api.Test +import sexpression.LispNumber +import table.SymbolTable +import testutil.LispTestInstance +import testutil.SymbolAndFunctionCleaner +import testutil.TestUtilities.assertSExpressionsMatch +import testutil.TestUtilities.evaluateString + +@LispTestInstance +class SetTest : SymbolAndFunctionCleaner() { + + @Test + fun set() { + evaluateString("(set 'a 23)") + assertSExpressionsMatch(LispNumber("23"), evaluateString("a")) + } + + @Test + fun lookupDefinedSymbol() { + evaluateString("(set 'a 23)") + assertSExpressionsMatch(LispNumber("23"), executionContext.lookupSymbolValue("A")!!) + } + + @Test + fun lookupUndefinedSymbol() { + assertThat(executionContext.lookupSymbolValue("A")).isNull() + } + + @Test + fun setGlobalVariable() { + evaluateString("(set 'a 23)") + val global = executionContext.scope + executionContext.scope = SymbolTable(global) + + evaluateString("(set 'a 94)") + executionContext.scope = global + assertSExpressionsMatch(LispNumber("94"), evaluateString("a")) + } + + @Test + fun setLocalVariableDefined_DoesNotSetGlobal() { + val global = executionContext.scope + val local = SymbolTable(global) + local["A"] = LispNumber("99") + executionContext.scope = local + + evaluateString("(set 'a 94)") + executionContext.scope = global + + assertThrows(UndefinedSymbolException::class.java) { + evaluateString("a") + } + } + + @Test + fun setLocalVariableUndefined_SetsGlobal() { + val global = executionContext.scope + val local = SymbolTable(global) + executionContext.scope = local + + evaluateString("(set 'a 94)") + executionContext.scope = global + assertSExpressionsMatch(LispNumber("94"), evaluateString("a")) + } + + @Test + fun setWithNonSymbol() { + assertThrows(BadArgumentTypeException::class.java) { + evaluateString("(set '1 2)") + } + } + + @Test + fun setWithTooFewArguments() { + assertThrows(TooFewArgumentsException::class.java) { + evaluateString("(set 'x)") + } + } + + @Test + fun setWithTooManyArguments() { + assertThrows(TooManyArgumentsException::class.java) { + evaluateString("(set 'a 'b 'c)") + } + } +}