Added DEFINE-MACRO for user defined special functions (forms)
This commit is contained in:
parent
4f4bc8f71a
commit
78c7bf4d9c
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
@ -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)");
|
||||
}
|
||||
|
||||
}
|
|
@ -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)");
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue