Added DEFINE-MACRO for user defined special functions (forms)

This commit is contained in:
Mike Cifelli 2017-02-25 19:11:31 -05:00
parent 4f4bc8f71a
commit 78c7bf4d9c
8 changed files with 382 additions and 62 deletions

View File

@ -0,0 +1,49 @@
package function.builtin;
import function.*;
import sexpression.*;
import table.*;
public class SET extends LispFunction {
private ArgumentValidator argumentValidator;
private ExecutionContext executionContext;
public SET() {
this.argumentValidator = new ArgumentValidator("SET");
this.argumentValidator.setExactNumberOfArguments(2);
this.argumentValidator.setFirstArgumentExpectedType(Symbol.class);
this.executionContext = ExecutionContext.getInstance();
}
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.put(symbol.toString(), value);
return value;
}
private SymbolTable findScopeOfSymbol(SExpression symbol) {
SymbolTable table = executionContext.getScope();
while (!isSymbolInTable(symbol, table) && !isGlobalTable(table))
table = table.getParent();
return table;
}
private boolean isSymbolInTable(SExpression symbol, SymbolTable table) {
return table.contains(symbol.toString());
}
private boolean isGlobalTable(SymbolTable table) {
return table.getParent() == null;
}
}

View File

@ -0,0 +1,17 @@
package function.builtin.special;
import function.*;
import sexpression.*;
public class DEFINE_MACRO extends Define {
public DEFINE_MACRO() {
super("DEFINE-MACRO");
}
@Override
protected UserDefinedFunction createFunction(SExpression functionName, Cons lambdaList, Cons functionBody) {
return new UserDefinedSpecialFunction(functionName.toString(), lambdaList, functionBody);
}
}

View File

@ -1,70 +1,17 @@
package function.builtin.special;
import java.text.MessageFormat;
import environment.RuntimeEnvironment;
import error.*;
import function.*;
import function.builtin.cons.LIST;
import function.UserDefinedFunction;
import sexpression.*;
import table.FunctionTable;
public class DEFUN extends LispSpecialFunction {
private ArgumentValidator argumentValidator;
private ArgumentValidator lambdaListIsListValidator;
private ArgumentValidator lambdaListValidator;
private ErrorManager errorManager;
public class DEFUN extends Define {
public DEFUN() {
this.argumentValidator = new ArgumentValidator("DEFUN");
this.argumentValidator.setMinimumNumberOfArguments(2);
this.argumentValidator.setFirstArgumentExpectedType(Symbol.class);
this.lambdaListIsListValidator = new ArgumentValidator("DEFUN|lambda-list|");
this.lambdaListIsListValidator.setEveryArgumentExpectedType(Cons.class);
this.lambdaListValidator = new ArgumentValidator("DEFUN|parameter|");
this.lambdaListValidator.setEveryArgumentExpectedType(Symbol.class);
this.errorManager = RuntimeEnvironment.getInstance().getErrorManager();
super("DEFUN");
}
public SExpression call(Cons argumentList) {
argumentValidator.validate(argumentList);
Cons remainingArguments = (Cons) argumentList.getRest();
SExpression functionName = argumentList.getFirst();
SExpression secondArgument = remainingArguments.getFirst();
lambdaListIsListValidator.validate(LIST.makeList(secondArgument));
Cons lambdaList = (Cons) secondArgument;
lambdaListValidator.validate(lambdaList);
Cons functionBody = (Cons) remainingArguments.getRest();
UserDefinedFunction function = new UserDefinedFunction(functionName.toString(), lambdaList, functionBody);
if (FunctionTable.isAlreadyDefined(functionName.toString()))
errorManager.handle(new RedefiningFunctionWarning(functionName.toString()));
FunctionTable.defineFunction(functionName.toString(), function);
return functionName;
}
public class RedefiningFunctionWarning extends LispWarning {
private static final long serialVersionUID = 1L;
private String functionName;
public RedefiningFunctionWarning(String functionName) {
this.functionName = functionName;
}
@Override
public String getMessage() {
return MessageFormat.format("redefining function {0}", functionName);
}
@Override
protected UserDefinedFunction createFunction(SExpression functionName, Cons lambdaList, Cons functionBody) {
return new UserDefinedFunction(functionName.toString(), lambdaList, functionBody);
}
}

View File

@ -0,0 +1,72 @@
package function.builtin.special;
import java.text.MessageFormat;
import environment.RuntimeEnvironment;
import error.*;
import function.*;
import function.builtin.cons.LIST;
import sexpression.*;
import table.FunctionTable;
public abstract class Define extends LispSpecialFunction {
private ArgumentValidator argumentValidator;
private ArgumentValidator lambdaListIsListValidator;
private ArgumentValidator lambdaListValidator;
private ErrorManager errorManager;
public Define(String functionName) {
this.argumentValidator = new ArgumentValidator(functionName);
this.argumentValidator.setMinimumNumberOfArguments(2);
this.argumentValidator.setFirstArgumentExpectedType(Symbol.class);
this.lambdaListIsListValidator = new ArgumentValidator(functionName + "|lambda-list|");
this.lambdaListIsListValidator.setEveryArgumentExpectedType(Cons.class);
this.lambdaListValidator = new ArgumentValidator(functionName + "|parameter|");
this.lambdaListValidator.setEveryArgumentExpectedType(Symbol.class);
this.errorManager = RuntimeEnvironment.getInstance().getErrorManager();
}
public SExpression call(Cons argumentList) {
argumentValidator.validate(argumentList);
Cons remainingArguments = (Cons) argumentList.getRest();
SExpression functionName = argumentList.getFirst();
SExpression secondArgument = remainingArguments.getFirst();
lambdaListIsListValidator.validate(LIST.makeList(secondArgument));
Cons lambdaList = (Cons) secondArgument;
lambdaListValidator.validate(lambdaList);
Cons functionBody = (Cons) remainingArguments.getRest();
UserDefinedFunction function = createFunction(functionName, lambdaList, functionBody);
if (FunctionTable.isAlreadyDefined(functionName.toString()))
errorManager.handle(new RedefiningFunctionWarning(functionName.toString()));
FunctionTable.defineFunction(functionName.toString(), function);
return functionName;
}
protected abstract UserDefinedFunction createFunction(SExpression functionName, Cons lambdaList, Cons functionBody);
public class RedefiningFunctionWarning extends LispWarning {
private static final long serialVersionUID = 1L;
private String functionName;
public RedefiningFunctionWarning(String functionName) {
this.functionName = functionName;
}
@Override
public String getMessage() {
return MessageFormat.format("redefining function {0}", functionName);
}
}
}

View File

@ -52,6 +52,7 @@ public class FunctionTable {
functionTable.put("CDR", new REST());
functionTable.put("COND", new COND());
functionTable.put("CONS", new CONS());
functionTable.put("DEFINE-MACRO", new DEFINE_MACRO());
functionTable.put("DEFUN", new DEFUN());
functionTable.put("EQ", new EQ());
functionTable.put("EQUAL", new EQUAL());
@ -72,7 +73,9 @@ public class FunctionTable {
functionTable.put("PRINT", new PRINT());
functionTable.put("QUOTE", new QUOTE());
functionTable.put("REST", new REST());
functionTable.put("SET", new SET());
functionTable.put("SETF", new SETF());
functionTable.put("SETQ", new SETF());
functionTable.put("SYMBOL-FUNCTION", new SYMBOL_FUNCTION());
}

View File

@ -0,0 +1,97 @@
package function.builtin;
import static org.junit.Assert.assertNull;
import static testutil.TestUtilities.*;
import org.junit.*;
import function.ArgumentValidator.*;
import function.builtin.EVAL.UndefinedSymbolException;
import sexpression.LispNumber;
import table.*;
public class SETTester {
private ExecutionContext executionContext;
public SETTester() {
this.executionContext = ExecutionContext.getInstance();
}
@Before
public void setUp() {
executionContext.clearContext();
}
@After
public void tearDown() {
executionContext.clearContext();
}
@Test
public void testSet() {
evaluateString("(set 'a 23)");
assertSExpressionsMatch(new LispNumber("23"), evaluateString("a"));
}
@Test
public void lookupDefinedSymbol() {
evaluateString("(set 'a 23)");
assertSExpressionsMatch(new LispNumber("23"), executionContext.lookupSymbolValue("A"));
}
@Test
public void lookupUndefinedSymbol() {
assertNull(executionContext.lookupSymbolValue("A"));
}
@Test
public void setGlobalVariable() {
evaluateString("(set 'a 23)");
SymbolTable global = executionContext.getScope();
executionContext.setScope(new SymbolTable(global));
evaluateString("(set 'a 94)");
executionContext.setScope(global);
assertSExpressionsMatch(new LispNumber("94"), evaluateString("a"));
}
@Test(expected = UndefinedSymbolException.class)
public void setLocalVariableDefined_DoesNotSetGlobal() {
SymbolTable global = executionContext.getScope();
SymbolTable local = new SymbolTable(global);
local.put("A", new LispNumber("99"));
executionContext.setScope(local);
evaluateString("(set 'a 94)");
executionContext.setScope(global);
evaluateString("a");
}
@Test
public void setLocalVariableUndefined_SetsGlobal() {
SymbolTable global = executionContext.getScope();
SymbolTable local = new SymbolTable(global);
executionContext.setScope(local);
evaluateString("(set 'a 94)");
executionContext.setScope(global);
assertSExpressionsMatch(new LispNumber("94"), evaluateString("a"));
}
@Test(expected = BadArgumentTypeException.class)
public void testSetWithNonSymbol() {
evaluateString("(set '1 2)");
}
@Test(expected = TooFewArgumentsException.class)
public void testSetWithTooFewArguments() {
evaluateString("(set 'x)");
}
@Test(expected = TooManyArgumentsException.class)
public void testSetWithTooManyArguments() {
evaluateString("(set 'a 'b 'c)");
}
}

View File

@ -0,0 +1,131 @@
package function.builtin.special;
import static org.junit.Assert.assertTrue;
import static testutil.TestUtilities.*;
import java.io.*;
import org.junit.*;
import environment.RuntimeEnvironment;
import error.ErrorManager;
import function.ArgumentValidator.*;
import table.FunctionTable;
public class DEFINE_MACROTester {
private ByteArrayOutputStream outputStream;
private RuntimeEnvironment environment;
public DEFINE_MACROTester() {
this.environment = RuntimeEnvironment.getInstance();
}
private void assertSomethingPrinted() {
assertTrue(outputStream.toByteArray().length > 0);
}
@Before
public void setUp() {
outputStream = new ByteArrayOutputStream();
environment.setOutput(new PrintStream(outputStream));
environment.setErrorManager(new ErrorManager());
environment.setWarningOutputDecorator(s -> s);
FunctionTable.reset();
}
@After
public void tearDown() {
FunctionTable.reset();
}
@Test
public void testDefineMacro() {
String input = "(define-macro f () t)";
assertSExpressionsMatch(parseString("f"), evaluateString(input));
assertSExpressionsMatch(parseString("t"), evaluateString("(f)"));
}
@Test
public void testDefineMacroWithEmptyBody() {
String input = "(define-macro f ())";
assertSExpressionsMatch(parseString("f"), evaluateString(input));
assertSExpressionsMatch(parseString("()"), evaluateString("(f)"));
}
@Test
public void testDefineMacroDoesNotEvaluatesArguments() {
evaluateString("(define-macro f (x) (car x))");
assertSExpressionsMatch(parseString("quote"), evaluateString("(f '(1 2 3))"));
}
@Test
public void testDefineMacroAdd() {
evaluateString("(define-macro f (x) (+ (eval x) 23))");
assertSExpressionsMatch(parseString("27"), evaluateString("(f (+ 2 2))"));
}
@Test
public void testDefineMacroSetVariable() {
evaluateString("(define-macro f (x) (set x 23))");
evaluateString("(f y)");
assertSExpressionsMatch(parseString("23"), evaluateString("y"));
}
@Test
public void testDefineMacroVariableCapture() {
evaluateString("(setf x 0)");
evaluateString("(define-macro f (x) (set x 23))");
evaluateString("(f x)");
assertSExpressionsMatch(parseString("0"), evaluateString("x"));
}
@Test
public void testDefineMacroAvoidVariableCaptureConvention() {
evaluateString("(setf x 0)");
evaluateString("(define-macro f (-x-) (set -x- 23))");
evaluateString("(f x)");
assertSExpressionsMatch(parseString("23"), evaluateString("x"));
}
@Test
public void redefineMacro_DisplaysWarning() {
String input = "(define-macro myFunction () nil)";
evaluateString(input);
evaluateString(input);
assertSomethingPrinted();
}
@Test
public void redefineMacro_ActuallyRedefinesMacro() {
evaluateString("(define-macro myMacro () nil)");
evaluateString("(define-macro myMacro () T)");
assertSomethingPrinted();
assertSExpressionsMatch(parseString("t"), evaluateString("(myMacro)"));
}
@Test(expected = DottedArgumentListException.class)
public void testDefineMacroWithDottedLambdaList() {
evaluateString("(funcall 'define-macro 'x (cons 'a 'b) ())");
}
@Test(expected = BadArgumentTypeException.class)
public void testDefineMacroWithNonSymbolName() {
evaluateString("(define-macro 1 () ())");
}
@Test(expected = BadArgumentTypeException.class)
public void testDefineMacroWithBadLambdaList() {
evaluateString("(define-macro x a ())");
}
@Test(expected = TooFewArgumentsException.class)
public void testDefineMacroWithTooFewArguments() {
evaluateString("(define-macro x)");
}
}

View File

@ -57,6 +57,12 @@ public class DEFUNTester {
assertSExpressionsMatch(parseString("()"), evaluateString("(f)"));
}
@Test
public void testDefunEvaluatesArguments() {
evaluateString("(defun f (x) (car x))");
assertSExpressionsMatch(parseString("1"), evaluateString("(f '(1 2 3))"));
}
@Test
public void redefineFunction_DisplaysWarning() {
String input = "(defun myFunction () nil)";
@ -77,9 +83,7 @@ public class DEFUNTester {
@Test(expected = DottedArgumentListException.class)
public void testDefunWithDottedLambdaList() {
String input = "(funcall 'defun 'x (cons 'a 'b) ())";
evaluateString(input);
evaluateString("(funcall 'defun 'x (cons 'a 'b) ())");
}
@Test(expected = BadArgumentTypeException.class)