110 lines
		
	
	
		
			3.5 KiB
		
	
	
	
		
			Kotlin
		
	
	
	
	
	
			
		
		
	
	
			110 lines
		
	
	
		
			3.5 KiB
		
	
	
	
		
			Kotlin
		
	
	
	
	
	
| 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<String>()
 | |
|     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"
 | |
|     }
 | |
| }
 |