Skip to content

Instantly share code, notes, and snippets.

@davegurnell
Last active December 1, 2017 12:58
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save davegurnell/9e3adcdbdb27c0090e5700d2a6d3bdfb to your computer and use it in GitHub Desktop.
Save davegurnell/9e3adcdbdb27c0090e5700d2a6d3bdfb to your computer and use it in GitHub Desktop.
Postfix calculator with ADT-based error handling
package calc
sealed trait Term
case object Add extends Term
case object Sub extends Term
case object Mul extends Term
case object Div extends Term
case class Num(value: Int) extends Term
sealed trait Error
case object BadTerm extends Error
case object EmptyStack extends Error
case object TooManyResults extends Error
case object NotEnoughResults extends Error
case object NotEnoughOperands extends Error
object PostfixCalc {
type ErrorOr[A] = Either[Error, A]
def apply(line: String): ErrorOr[Int] =
for {
terms <- parse(line)
result <- evaluate(terms)
} yield result
// --------------------
def parse(line: String): ErrorOr[List[Term]] = {
val seed: ErrorOr[List[Term]] = Right(Nil)
tokenize(line).foldRight(seed) { (str, accum) =>
for {
terms <- accum
term <- parseTerm(str)
} yield term :: terms
}
}
def tokenize(line: String): List[String] =
line.trim.split("[ \\t]+").toList
def parseTerm(str: String): ErrorOr[Term] =
str match {
case "+" => Right(Add)
case "-" => Right(Sub)
case "*" => Right(Mul)
case "/" => Right(Div)
case str =>
try {
Right(Num(str.toInt))
} catch {
case exn: NumberFormatException =>
Left(BadTerm)
}
}
// --------------------
def evaluate(terms: List[Term]): ErrorOr[Int] = {
val result: ErrorOr[List[Int]] =
terms.foldLeft(emptyStack)(step)
result.flatMap {
case result :: Nil => Right(result)
case result :: tail => Left(TooManyResults)
case Nil => Left(NotEnoughResults)
}
}
val emptyStack: ErrorOr[List[Int]] = Right(Nil)
def step(stack: ErrorOr[List[Int]], term: Term): ErrorOr[List[Int]] =
stack.flatMap { stack =>
(term, stack) match {
case (Num(n), list) => Right(n :: list)
case (Add, b :: a :: tail) => Right((a + b) :: tail)
case (Sub, b :: a :: tail) => Right((a - b) :: tail)
case (Mul, b :: a :: tail) => Right((a * b) :: tail)
case (Div, b :: a :: tail) => Right((a / b) :: tail)
case (Add | Sub | Mul | Div, _) =>
Left(NotEnoughOperands)
}
}
}
package calc
import scala.concurrent._
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
sealed trait Term
case object Add extends Term
case object Sub extends Term
case object Mul extends Term
case object Div extends Term
case class Num(value: Int) extends Term
sealed abstract class Error(message: String) extends Exception(message)
case class BadTerm() extends Error("BadTerm")
case class EmptyStack() extends Error("EmptyStack")
case class TooManyResults() extends Error("TooManyResults")
case class NotEnoughResults() extends Error("NotEnoughResults")
case class NotEnoughOperands() extends Error("NotEnoughOperands")
object PostfixCalc {
def apply(line: String): Future[Int] =
for {
terms <- parse(line)
result <- evaluate(terms)
} yield result
// --------------------
def parse(line: String): Future[List[Term]] = {
val seed: Future[List[Term]] = Future.successful(Nil)
tokenize(line).foldRight(seed) { (str, accum) =>
for {
terms <- accum
term <- parseTerm(str)
} yield term :: terms
}
}
def tokenize(line: String): List[String] =
line.trim.split("[ \\t]+").toList
def parseTerm(str: String): Future[Term] =
str match {
case "+" => Future.successful(Add)
case "-" => Future.successful(Sub)
case "*" => Future.successful(Mul)
case "/" => Future.successful(Div)
case str =>
try {
Future.successful(Num(str.toInt))
} catch {
case exn: NumberFormatException =>
Future.failed(new BadTerm())
}
}
// --------------------
def evaluate(terms: List[Term]): Future[Int] = {
val result: Future[List[Int]] =
terms.foldLeft(emptyStack)(step)
result.flatMap {
case result :: Nil => Future.successful(result)
case result :: tail => Future.failed(new TooManyResults())
case Nil => Future.failed(new NotEnoughResults())
}
}
val emptyStack: Future[List[Int]] = Future.successful(Nil)
def step(stack: Future[List[Int]], term: Term): Future[List[Int]] =
stack.flatMap { stack =>
(term, stack) match {
case (Num(n), list) => Future.successful(n :: list)
case (Add, b :: a :: tail) => Future.successful((a + b) :: tail)
case (Sub, b :: a :: tail) => Future.successful((a - b) :: tail)
case (Mul, b :: a :: tail) => Future.successful((a * b) :: tail)
case (Div, b :: a :: tail) => Future.successful((a / b) :: tail)
case (Add | Sub | Mul | Div, _) =>
Future.failed(new NotEnoughOperands())
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment