package function import error.LispException import function.builtin.Eval.Companion.eval import sexpression.Cons import sexpression.Nil import sexpression.SExpression import sexpression.Symbol import table.ExecutionContext import table.SymbolTable open class UserDefinedFunction(private val name: String, lambdaList: Cons, private val body: Cons) : LispFunction() { val lambdaExpression = Cons(Symbol(name), Cons(lambdaList, body)) private val functionScope = ExecutionContext.scope private var formalParameters = mutableListOf() private var argumentValidator = ArgumentValidator(name) private var keywordRestParameter = "" private var isKeywordRestPresent = false init { createFormalParameters(lambdaList) setupArgumentValidator() } private fun createFormalParameters(lambdaList: Cons) = lambdaList.forEach { val parameter = it.first.toString() when { isKeywordRestPresent -> setupKeywordRestParameter(parameter, it) KEYWORD_REST == parameter -> isKeywordRestPresent = true else -> formalParameters.add(parameter) } } private fun setupKeywordRestParameter(parameter: String, lambdaList: Cons) { if (keywordRestParameter.isNotBlank()) throw IllegalKeywordRestPositionException(name, lambdaList) keywordRestParameter = parameter } private fun setupArgumentValidator() { if (isKeywordRestPresent) argumentValidator.setMinimumNumberOfArguments(formalParameters.size) else argumentValidator.setExactNumberOfArguments(formalParameters.size) } override fun call(argumentList: Cons): SExpression { ExecutionContext.pushFunctionCall(this) val result = callTailRecursive(argumentList) ExecutionContext.popFunctionCall() return result } private tailrec fun callTailRecursive(argumentList: Cons): SExpression { argumentValidator.validate(argumentList) val result = evaluateInFunctionScope(argumentList) if (ExecutionContext.isRecur) { ExecutionContext.clearRecur() return callTailRecursive(result as Cons) } return result } private fun evaluateInFunctionScope(argumentList: Cons): SExpression { val callingScope = ExecutionContext.scope val executionScope = bindParameterValuesToFunctionScope(argumentList) ExecutionContext.scope = executionScope val lastEvaluation = evaluateBody() ExecutionContext.scope = callingScope return lastEvaluation } private fun bindParameterValuesToFunctionScope(argumentList: Cons): SymbolTable { var arguments = argumentList val executionScope = SymbolTable(functionScope) for (parameter in formalParameters) { val currentArg = arguments.first executionScope[parameter] = currentArg arguments = arguments.rest as Cons } if (isKeywordRestPresent) executionScope[keywordRestParameter] = arguments return executionScope } private fun evaluateBody() = body.fold(Nil as SExpression) { _, cons -> eval(cons.first) } class IllegalKeywordRestPositionException(functionName: String, parameters: Cons) : LispException() { override val message = "unexpected parameters following '$KEYWORD_REST' in definition of $functionName: $parameters" } companion object { private const val KEYWORD_REST = "&REST" } }