Created
July 30, 2020 05:32
-
-
Save matfournier/3caddf54e347537de0fa7b14d2eee57e to your computer and use it in GitHub Desktop.
a stack language as an ADT with compile time checking
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package stack | |
| |
import cats.Monoid | |
| |
import scala.util.Try | |
import cats.implicits._ | |
| |
import scala.reflect.macros.blackbox | |
import scala.language.experimental.macros | |
| |
/** model our stack as an ADT */ | |
sealed trait Stack extends Product with Serializable {self => | |
import Stack._ | |
def |>(op: Stack): Stack = Then(self, op) | |
} | |
| |
object Stack { | |
final case class Push(v: Int) extends Stack | |
final case class Then(op1: Stack, op2: Stack) extends Stack | |
case object Pop extends Stack | |
case object Duplicate extends Stack | |
case object Plus extends Stack | |
case object Minus extends Stack | |
case object Empty extends Stack | |
| |
implicit val stackMonoid: Monoid[Stack] = new Monoid[Stack] { | |
override def empty: Stack = Empty | |
override def combine(x: Stack, y: Stack): Stack = x |> y | |
} | |
| |
/** convenience function create an arbitrary existing stack */ | |
def of(values: List[Int]): Stack = values.foldMap(v => Push(v): Stack) | |
| |
/** an interpreter for our ADT */ | |
def eval(stack: Stack): Int = { | |
val init: List[Int] = Nil | |
| |
/** Not stack safe */ | |
def go(op: Stack, stack: List[Int]): List[Int] = | |
op match { | |
case Empty => stack | |
case Push(v) => v +: stack | |
case Pop => stack.tail | |
case Duplicate => stack.head +: stack | |
case Plus => | |
val v: Int = scala.math.addExact(stack.head,stack.tail.head) | |
v +: stack.drop(2) | |
case Minus => | |
val v = stack.head - stack.tail.head | |
if (v < 0) throw new ArithmeticException("negitive not allowed") else v +: stack | |
case Then(op1, op2) => | |
go(op2, go(op1, stack)) | |
} | |
| |
// can't remember if they just wanted top, or entire stack | |
go(stack, init) match { | |
case Nil => throw new Exception("Stack cannot be empty") | |
case head::_ => head | |
} | |
} | |
| |
/** interpret a string into a stack and evaluate it*/ | |
def from(s: String): Int = { | |
val stack = s.split(" ").toList.map { | |
case v if v.forall(_.isDigit) => Push(v.toInt) | |
case "DUP" => Duplicate | |
case "POP" => Pop | |
case "+" => Plus | |
case "-" => Minus | |
case _ => throw new Exception("invalid symbol") | |
} | |
Stack.eval(stack.combineAll) | |
} | |
| |
/** | |
* EVERYTHING BENEATH THIS IS FOR JOKES BUT WAS FUN LEARNING ABOUT MACROS | |
* boot up a repl: | |
* import stack.Stack | |
* import stack.Stack._ | |
* then try stack"13 13 +" should return 26 | |
* Compile time checking of stack"13 13 sjdhfs" fails with a compile error | |
* Compile time checking of stack"13 DUP 4 POP 5 DUP + DUP + -" passes | |
* compile time checking of stack"5 6 + -" fails with a compiler error | |
* if you had a stack"..." defined in another file, would be an inteliJ compiler error if an invalid | |
* stack language is evaluated at compile time. | |
*/ | |
implicit class LiteralOps(val sc: StringContext) extends AnyVal { | |
def stack(args: Any*): Int = macro LiteralSyntaxMacros.stackInterpolator | |
} | |
| |
object LiteralSyntaxMacros { | |
def stackInterpolator(c: blackbox.Context)(args: c.Expr[Any]*): c.Expr[Int] = | |
singlePartInterpolator(c)( | |
args, | |
"stack", | |
input => Try(Stack.from(input)).toOption.isDefined, | |
s => c.universe.reify(Stack.from(s.splice)) | |
) | |
private def singlePartInterpolator[A](c: blackbox.Context)( | |
args: Seq[c.Expr[Any]], | |
typeName: String, | |
validate: String => Boolean, | |
construct: c.Expr[String] => c.Expr[A]): c.Expr[A] = { | |
import c.universe._ | |
identity(args) | |
c.prefix.tree match { | |
case Apply(_, List(Apply(_, (lcp @ Literal(Constant(p: String))) :: Nil))) => | |
val valid = validate(p) | |
if (valid) construct(c.Expr(lcp)) | |
else c.abort(c.enclosingPosition, s"invalid $typeName") | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment