Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
Calculator with reverse Polish notation as a starter project that I use to learn Kotlin.
Hosted on replit https://replit.com/@jdonges1/RPNCalc

Supported operations: `+`, `-`, `*`, `/`, `sqr`, `exp`, `sin`, `cos`, `pow`.

Example:
```
Input>9 sqr 18 + 3 /
33.0
Input>3 * 1 +
100.0
Input>2 3 pow
8.0
Input>0.5 sin
0.4794255386
Input>exit
```
73 changes: 73 additions & 0 deletions RPNCalculatorTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Test
import kotlin.math.PI

class RPNCalculatorTest {
private fun eval(expr: String): String {
val calc = RPNCalculator()
return calc.evaluateInput(expr)
}

private fun evalDouble(expr: String): Double? = eval(expr).toDoubleOrNull()

@Test
fun testAdd() {
assertEquals(5.0, evalDouble("2 3 +"))
}

@Test
fun testSub() {
assertEquals(-1.0, evalDouble("2 3 -"))
}

@Test
fun testMul() {
assertEquals(6.0, evalDouble("2 3 *"))
}

@Test
fun testDiv() {
assertEquals(2.0, evalDouble("6 3 /"))
}

@Test
fun testDivByZero() {
assertNull(evalDouble("1 0 /"))
}

@Test
fun testSqr() {
assertEquals(9.0, evalDouble("3 sqr"))
}

@Test
fun testExp() {
val result = evalDouble("0 exp")
assertNotNull(result)
result?.let { assertEquals(1.0, it, 1e-9) }
}

@Test
fun testSin() {
val result = evalDouble("${PI/2} sin")
assertNotNull(result)
result?.let { assertEquals(1.0, it, 1e-9) }
}

@Test
fun testCos() {
val result = evalDouble("0 cos")
assertNotNull(result)
result?.let { assertEquals(1.0, it, 1e-9) }
}

@Test
fun testPow() {
assertEquals(8.0, evalDouble("2 3 pow"))
}

@Test
fun testPowZero() {
assertEquals(1.0, evalDouble("5 0 pow"))
}
}
70 changes: 50 additions & 20 deletions main.kt
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@

import kotlin.math.*

// Stack functions: I'd rather like to use push pop and peek in a stack
fun <T> ArrayDeque<T>.push(item: T) {
this.addLast(item)
Expand Down Expand Up @@ -29,7 +31,7 @@ data class InvalidToken(val value: String) : Token()

fun tokenize(input: String): List<Token> {
val tokens = mutableListOf<Token>()
val tokenPatterns = """([-]*\d+(\.\d+)?)|([+\-*/]|sqr)""".toRegex()
val tokenPatterns = """([-]*\d+(\.\d+)?)|([+\-*/]|sqr|exp|sin|cos|pow)""".toRegex()
val matches = tokenPatterns.findAll(input)
for (match in matches) {
val (number, _, operator) = match.destructured
Expand All @@ -47,33 +49,36 @@ sealed class Operator {
abstract fun apply(stack: ArrayDeque<Double>)
}

private fun binaryOp(stack: ArrayDeque<Double>, op: (Double, Double) -> Double) {
if (stack.size >= 2) {
val right = stack.pop() ?: 0.0
val left = stack.pop() ?: 0.0
stack.push(op(left, right))
} else println("Stack error.")
}

private fun unaryOp(stack: ArrayDeque<Double>, op: (Double) -> Double) {
if (stack.size >= 1) {
val value = stack.pop() ?: 0.0
stack.push(op(value))
} else println("Stack error.")
}

object Add : Operator() {
override fun apply(stack: ArrayDeque<Double>) {
if (stack.size >= 2) {
val right = stack.pop() ?: 0.0
val left = stack.pop() ?: 0.0
stack.push(left + right)
} else println("Stack error.")
binaryOp(stack) { left, right -> left + right }
}
}

object Sub : Operator() {
override fun apply(stack: ArrayDeque<Double>) {
if (stack.size >= 2) {
val right = stack.pop() ?: 0.0
val left = stack.pop() ?: 0.0
stack.push(left - right)
} else println("Stack error.")
binaryOp(stack) { left, right -> left - right }
}
}

object Mul : Operator() {
override fun apply(stack: ArrayDeque<Double>) {
if (stack.size >= 2) {
val right = stack.pop() ?: 0.0
val left = stack.pop() ?: 0.0
stack.push(left * right)
} else println("Stack error.")
binaryOp(stack) { left, right -> left * right }
}
}

Expand All @@ -89,10 +94,31 @@ object Div : Operator() {

object Sqr : Operator() {
override fun apply(stack: ArrayDeque<Double>) {
if (stack.size >= 1) {
val operand = stack.pop() ?: 0.0
stack.push(operand * operand)
} else println("Stack error.")
unaryOp(stack) { it * it }
}
}

object Exp : Operator() {
override fun apply(stack: ArrayDeque<Double>) {
unaryOp(stack) { kotlin.math.exp(it) }
}
}

object Sin : Operator() {
override fun apply(stack: ArrayDeque<Double>) {
unaryOp(stack) { kotlin.math.sin(it) }
}
}

object Cos : Operator() {
override fun apply(stack: ArrayDeque<Double>) {
unaryOp(stack) { kotlin.math.cos(it) }
}
}

object Pow : Operator() {
override fun apply(stack: ArrayDeque<Double>) {
binaryOp(stack) { left, right -> left.pow(right) }
}
}

Expand All @@ -110,6 +136,10 @@ fun operatorFromString(opCode: String): Operator {
"*" -> return Mul
"/" -> return Div
"sqr" -> return Sqr
"exp" -> return Exp
"sin" -> return Sin
"cos" -> return Cos
"pow" -> return Pow
}

return Nop
Expand Down