Convert Set and Load to kotlin

This commit is contained in:
Mike Cifelli 2018-10-22 19:52:08 -04:00
parent 7416e0e163
commit 924357b5cd
13 changed files with 721 additions and 779 deletions

View File

@ -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";
}
}
}

View File

@ -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"
}
}

View File

@ -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<String> 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);
}
}
}

View File

@ -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<String>()
}
}

View File

@ -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());
}
}

View File

@ -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)
}
}

View File

@ -8,7 +8,7 @@ import sexpression.SExpression;
import sexpression.Symbol; import sexpression.Symbol;
import static function.builtin.Eval.eval; 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; import static function.builtin.cons.LIST.makeList;
@FunctionNames({ "SETQ" }) @FunctionNames({ "SETQ" })

View File

@ -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());
}
}

View File

@ -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())
}
}

View File

@ -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();
}
}

View File

@ -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()
}
}

View File

@ -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)");
}
}

View File

@ -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)")
}
}
}