diff --git a/src/main/scala/splatter/parsing.scala b/src/main/scala/splatter/parsing.scala index e393d18..e1c20cf 100644 --- a/src/main/scala/splatter/parsing.scala +++ b/src/main/scala/splatter/parsing.scala @@ -1,93 +1,109 @@ package splatter -package parsing -case class P[+A](parse: String => Option[(A,String)]) { +import scala.annotation.tailrec - import P._ - - def flatMap[B](f: A => P[B]): P[B] = - P(s => parse(s) match { - case Some((a,r)) => f(a).parse(r) - case None => None - }) - - def map[B](f: A => B): P[B] = - P(s => parse(s) match { - case Some((a,r)) => Some(f(a),r) - case None => None - }) - - def |[A1 >: A](that: => P[A1]): P[A1] = - P(s => parse(s) match { - case None => that.parse(s) - case res@Some(_) => res - }) - - def ~[B](that: P[B]): P[B] = - for { _ <- this ; b <- that } yield b - - private def loop[A1 >: A](s: String, acc: List[A1] = List.empty): (List[A1], String) = - parse(s) match { - case None => (acc.reverse, s) - case Some((a,ss)) => loop(ss, a :: acc) - } - - def zeroOrMore: P[List[A]] = - P(s => Some(loop(s))) - - def oneOrMore: P[List[A]] = { - P(s => parse(s).flatMap { case (a,ss) => Some(loop(ss, List(a)))}) - } - - def iff[B](p: A => Boolean)(t: P[B])(f: => P[B]): P[B] = - P(s => parse(s).flatMap { case (a,ss) => if (p(a)) t.parse(ss) else f.parse(ss)}) -} - -object P { - - def run[A](p: P[A])(s: String): A = - p.parse(s) match { - case Some((a, "")) => a - case Some((_, rs)) => sys.error(s"unconsumed: $rs") - case None => sys.error(s"did not produce a value: ${s}") - } - - def unit[A](a: A): P[A] = - P(s => Some(a, s)) +object parsing: - def take: P[Char] = - P(s => if (s.nonEmpty) Some(s.head, s.tail) else None) - - def fail[A]: P[A] = - P(_ => None) - - def satisfy(p: Char => Boolean): P[Char] = - take.flatMap(c => if (p(c)) unit(c) else fail) - - def end: P[Boolean] = - P(s => if (s.isEmpty) Some(true, "") else Some(false, s)) - - def digit: P[Char] = - satisfy(_.isDigit) + case class P[+A](parse: String => Option[(A,String)]): + + def flatMap[B](f: A => P[B]): P[B] = + P(s => + parse(s) match + case Some((a, r)) => f(a).parse(r) + case None => None + ) + + def map[B](f: A => B): P[B] = + P(s => + parse(s) match + case Some((a, r)) => Some(f(a),r) + case None => None + ) + + def |[B >: A](that: => P[B]): P[B] = + P(s => + parse(s) match + case None => that.parse(s) + case res@Some(_) => res + ) + + def ~[B](that: P[B]): P[B] = + for + _ <- this + b <- that + yield + b + + @tailrec + private def loop[B >: A](s: String, acc: List[B] = List.empty): (List[B], String) = + parse(s) match + case None => (acc.reverse, s) + case Some((a, ss)) => loop(ss, a :: acc) - def digits: P[Long] = - satisfy(_.isDigit).oneOrMore.map(_.mkString("").toLong) + def zeroOrMore: P[List[A]] = + P(s => Some(loop(s))) + + def oneOrMore: P[List[A]] = + P(s => parse(s).flatMap((a, ss) => Some(loop(ss, List(a))))) + + def iff[B](p: A => Boolean)(t: P[B])(f: => P[B]): P[B] = + P(s => parse(s).flatMap((a, ss) => if (p(a)) t.parse(ss) else f.parse(ss))) + + object P: + + def run[A](p: P[A])(s: String): A = + p.parse(s) match + case Some((a, "")) => a + case Some((_, rs)) => sys.error(s"unconsumed: $rs") + case None => sys.error(s"did not produce a value: $s") + + private def unit[A](a: A): P[A] = + P(s => Some(a, s)) - def char(c: Char): P[Char] = - satisfy(_ == c) - - def string(s: String): P[String] = - if (s.isEmpty) unit("") else for { _ <- char(s.head) ; _ <- string(s.tail) } yield s - - def oneOf(s: String): P[Char] = - satisfy(s.contains(_)) - - def spaces: P[String] = - oneOf(" \t\n\r").zeroOrMore.map(_.mkString) - - def token[A](p: P[A]): P[A] = - for { a <- p ; _ <- spaces } yield a - - def keyword(word: String): P[String] = - token(string(word)) -} \ No newline at end of file + private def take: P[Char] = + P(s => if (s.nonEmpty) Some(s.head, s.tail) else None) + + private def fail[A]: P[A] = + P(_ => None) + + def satisfy(p: Char => Boolean): P[Char] = + take.flatMap(c => if (p(c)) unit(c) else fail) + + def end: P[Boolean] = + P(s => if (s.isEmpty) Some(true, "") else Some(false, s)) + + def digit: P[Char] = + satisfy(_.isDigit) + + def digits: P[Long] = + satisfy(_.isDigit).oneOrMore.map(_.mkString("").toLong) + + private def char(c: Char): P[Char] = + satisfy(_ == c) + + def string(s: String): P[String] = + if s.isEmpty then + unit("") + else + for + _ <- char(s.head) + _ <- string(s.tail) + yield + s + + private def oneOf(s: String): P[Char] = + satisfy(s.contains(_)) + + def spaces: P[String] = + oneOf(" \t\n\r").zeroOrMore.map(_.mkString) + + private def token[A](p: P[A]): P[A] = + for + a <- p + _ <- spaces + yield + a + + def keyword(word: String): P[String] = + token(string(word)) + \ No newline at end of file diff --git a/src/main/scala/splatter/stutter.scala b/src/main/scala/splatter/stutter.scala index 661f89f..4ba0fc8 100644 --- a/src/main/scala/splatter/stutter.scala +++ b/src/main/scala/splatter/stutter.scala @@ -34,13 +34,19 @@ abstract class ExtractableOp[T](name: String) extends Extractable[T]: val Op: Atom = Atom(name) case class ExtractableOp1(name: String) extends ExtractableOp[Expr](name): - def extract = { case Lisp(Seq(Op, arg)) => arg } + def extract: PartialFunction[Expr, Expr] = { + case Lisp(Seq(Op, arg)) => arg + } case class ExtractableOp2(name: String) extends ExtractableOp[(Expr,Expr)](name): - def extract = { case Lisp(Seq(Op, a, b)) => (a, b) } + def extract: PartialFunction[Expr, (Expr, Expr)] = { + case Lisp(Seq(Op, a, b)) => (a, b) + } case class ExtractableOpN(name: String) extends ExtractableOp[Seq[Expr]](name): - def extract = { case Lisp(Op +: args) => args } + def extract: PartialFunction[Expr, Seq[Expr]] = { + case Lisp(Op +: args) => args + } lazy val QuoteLit = ExtractableOp1("quote") lazy val AtomLit = ExtractableOp1("atom") @@ -55,11 +61,15 @@ val PrimitiveOps: Seq[Atom] = // (lambda (p1 ... pn) e) object Lambda extends ExtractableOp[(Seq[Expr],Expr)]("lambda"): - def extract = { case Lisp(Seq(Op, Lisp(parms), expr)) => (parms, expr) } + def extract: PartialFunction[Expr, (Seq[Expr], Expr)] = { + case Lisp(Seq(Op, Lisp(parms), expr)) => (parms, expr) + } // (quote (lambda (p1 ... pn) e)) object QuotedLambda extends ExtractableOp[(Seq[Expr],Expr)]("quote"): - def extract = { case Lisp(Seq(Op, Lambda(parms, expr))) => (parms, expr) } + def extract: PartialFunction[Expr, (Seq[Expr], Expr)] = { + case Lisp(Seq(Op, Lambda(parms, expr))) => (parms, expr) + } // ((lambda (p1 ... pn) e) a1 ... an) object Function extends Extractable[(Seq[Expr],Expr,Seq[Expr])]: @@ -72,9 +82,8 @@ object Function extends Extractable[(Seq[Expr],Expr,Seq[Expr])]: object QuotedFunction extends Extractable[(Seq[Expr],Atom,Seq[Expr],Seq[Expr])]: def extract: PartialFunction[Expr, (Seq[Expr], Atom, Seq[Expr], Seq[Expr])] = { case Lisp(Lambda(parms, Lisp((op : Atom) +: fargs)) +: args) - if !PrimitiveOps.contains(op) && - args.nonEmpty && - QuotedLambda.is(args.head) => (parms, op, fargs, args) + if !PrimitiveOps.contains(op) && args.nonEmpty && QuotedLambda.is(args.head) => + (parms, op, fargs, args) } def eval(e: Expr): Expr = @@ -91,10 +100,9 @@ def eval(e: Expr): Expr = // - eval all args except for quoted ones // - replace the expression parms with the evaluated args case Function(parms, expr, args) => - val evaluated = args.map { + val evaluated = args.map: case q @ QuoteLit(_) => q case e : Expr => eval(e) - } val replaced = replace(expr, parms.zip(evaluated).toMap) eval(replaced) @@ -135,11 +143,11 @@ def eval(s: String): Expr = eval(Parser.parseLisp(s)) def replace(expr: Expr, parms: Map[Expr, Expr]): Expr = - Lisp(expr.subs.map({ // TODO clearly Lisp needs a `map`. + Lisp(expr.subs.map: case a: Atom if parms.keySet.contains(a) => parms(a) case a: Atom => a case r: Lisp => replace(r, parms) - })) + ) object Parser: @@ -149,22 +157,24 @@ object Parser: def atom: P[Atom] = satisfy(c => c.isLetter).oneOrMore.map(cs => Atom(cs.mkString(""))) - def list: P[Lisp] = - for { + private def list: P[Lisp] = + for _ <- keyword("(") l <- expr.zeroOrMore.map(es => Lisp(es)) _ <- keyword(")") - } yield l + yield + l def quote: P[Lisp] = (keyword("'") | keyword("’")) ~ expr.map(e => Lisp(Seq(QuoteLit.Op, e))) - def expr: P[Expr] = - for { + private def expr: P[Expr] = + for _ <- spaces - e <- (atom | list | quote) + e <- atom | list | quote _ <- spaces - } yield e + yield + e def parseLisp(s: String): Expr = run(expr)(s)