Avatar of blueglyph

blueglyph's solution

to Forth in the Kotlin Track

Published at Jul 28 2019 · 0 comments
Instructions
Test suite
Solution
import java.util.*
import kotlin.IllegalArgumentException

class ForthEvaluator {

	private val opName = hashMapOf("+" to "Addition", "-" to "Subtraction", "*" to "Multiplication", "/" to "Division")
	private val stack = Stack<Int>()
	private val defs = hashMapOf<String,Array<Token>>()
	private var defMode = false
	private var defName = ""
	private val defList = mutableListOf<Token>()

	enum class Keyword(val s: String) {
		DEF(":"), ENDDEF(";"), NUM("""\d+"""), OP("""[+\-*/]"""), DUP("dup"), DROP("drop"), OVER("over"), SWAP("swap"), ID(".+");
	}

	class Token(val string: String) {
		val keyword: Keyword = lexer(string) ?: throw IllegalArgumentException("unknown symbol $string")

		override fun toString() = "${keyword.name}($string)"

		companion object {
			fun lexer(keyword: String): Keyword? {
				for (v in Keyword.values())
					if (Regex(v.s).matches(keyword))
						return v
				return null
			}

			fun parser(prg: String) = prg.toLowerCase().split(" ").filter(String::isNotEmpty).map(::Token).toTypedArray()
		}
	}

	fun evaluateProgram(prgList: List<String>): List<Int> {
		stack.clear()
		for (prg in prgList) {
			val tokens = Token.parser(prg)
			execute(tokens)
		}
		return stack
	}

	private fun checkStack(n: Int, op: String) = require(stack.size >= n) {
		"$op requires that the stack contain at least $n value${if (n == 1) "" else "s"}"
	}

	private fun execute(tokens: Array<Token>) {
		var ptr = 0
		while (ptr < tokens.size) {
			val tok = tokens[ptr]
			val defined = defs[tok.string]
			when {
				// In defmode, only add new tokens to the definition until ENDDEF:
				defMode -> when (tok.keyword) {
					Keyword.ENDDEF -> {
						defs[defName] = defList.toTypedArray()
						defMode = false
					}
					Keyword.DEF -> throw IllegalArgumentException("Embedded definitions not allowed")
					else -> defList.add(tok)
				}

				// If this is a defined symbol, execute the definition:
				defined != null -> execute(defined)

				// Default case, execute the next token:
				else -> when (tok.keyword) {
					Keyword.NUM -> stack.push(tok.string.toInt())
					Keyword.OP -> {
						checkStack(2, opName[tok.string]!!)
						val op1 = stack.pop()
						val op2 = stack.pop()
						stack.push(when (tok.string) {
							"+" -> op2 + op1
							"-" -> op2 - op1
							"*" -> op2 * op1
							"/" -> if (op1 != 0) op2 / op1 else throw IllegalArgumentException("Division by 0 is not allowed")
							else -> throw RuntimeException("Unexpected operator ${tok.string}")
						})
					}
					Keyword.DROP -> {
						checkStack(1, "Dropping")
						stack.pop()
					}
					Keyword.DUP -> {
						checkStack(1, "Duplicating")
						val op = stack.pop()
						stack.push(op)
						stack.push(op)
					}
					Keyword.SWAP -> {
						checkStack(2, "Swapping")
						val op1 = stack.pop()
						val op2 = stack.pop()
						stack.push(op1)
						stack.push(op2)
					}
					Keyword.OVER -> {
						checkStack(2, "Overing")
						stack.push(stack[stack.size - 2])
					}
					Keyword.DEF -> {
						if (tokens[ptr + 1].keyword == Keyword.NUM)
							throw IllegalArgumentException("Cannot redefine numbers")
						if (tokens[ptr + 1].keyword == Keyword.ENDDEF)
							throw IllegalArgumentException("Cannot redefine ';'")
						defMode = true
						defName = tokens[++ptr].string
						defList.clear()
					}
					Keyword.ID -> throw IllegalArgumentException("No definition available for operator \"${tok.string}\"")
					else -> throw IllegalArgumentException("unknown token $tok")
				}
			}
			ptr++
		}
	}
}

Community comments

Find this solution interesting? Ask the author a question to learn more.

What can you learn from this solution?

A huge amount can be learned from reading other people’s code. This is why we wanted to give exercism users the option of making their solutions public.

Here are some questions to help you reflect on this solution and learn the most from it.

  • What compromises have been made?
  • Are there new concepts here that you could read more about to improve your understanding?