From 03b5ad2bffbaf527aa0f68fc80a359043ba3c4dc Mon Sep 17 00:00:00 2001 From: nmcb Date: Sat, 9 May 2026 01:34:25 +0000 Subject: [PATCH 1/2] cleanup --- src/main/scala/splatter/stutter/parsing.scala | 2 +- src/main/scala/splatter/stutter/stutter.scala | 10 +++++----- .../scala/splatter/stutter/PrimitiveExpressions.scala | 3 +-- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/main/scala/splatter/stutter/parsing.scala b/src/main/scala/splatter/stutter/parsing.scala index 80fa5a3..e369c10 100644 --- a/src/main/scala/splatter/stutter/parsing.scala +++ b/src/main/scala/splatter/stutter/parsing.scala @@ -5,7 +5,7 @@ import scala.annotation.tailrec object parsing: - case class P[+A](parse: String => Option[(A,String)]): + case class P[+A](parse: String => Option[(A, String)]): def flatMap[B](f: A => P[B]): P[B] = P(s => diff --git a/src/main/scala/splatter/stutter/stutter.scala b/src/main/scala/splatter/stutter/stutter.scala index 69a925f..a0b94b7 100644 --- a/src/main/scala/splatter/stutter/stutter.scala +++ b/src/main/scala/splatter/stutter/stutter.scala @@ -36,7 +36,7 @@ case class ExtractableOp1(name: String) extends ExtractableOp[Expr](name): case Lisp(Seq(Op, arg)) => arg } -case class ExtractableOp2(name: String) extends ExtractableOp[(Expr,Expr)](name): +case class ExtractableOp2(name: String) extends ExtractableOp[(Expr, Expr)](name): def extract: PartialFunction[Expr, (Expr, Expr)] = { case Lisp(Seq(Op, a, b)) => (a, b) } @@ -58,26 +58,26 @@ val PrimitiveOps: Seq[Atom] = Seq(AtomLit.Op, QuoteLit.Op, EqLit.Op, CarLit.Op, CdrLit.Op, ConsLit.Op, CondLit.Op) // (lambda (p1 ... pn) e) -object Lambda extends ExtractableOp[(Seq[Expr],Expr)]("lambda"): +object Lambda extends ExtractableOp[(Seq[Expr], Expr)]("lambda"): 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"): +object QuotedLambda extends ExtractableOp[(Seq[Expr], Expr)]("quote"): 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])]: +object Function extends Extractable[(Seq[Expr], Expr, Seq[Expr])]: def extract: PartialFunction[Expr, (Seq[Expr], Expr, Seq[Expr])] = { case Lisp(Lambda(parms, expr) +: args) => (parms, expr, args) } // ((lambda (p1 ... pn) (f fa1 ... fan)) a1 ... an) -object QuotedFunction extends Extractable[(Seq[Expr],Atom,Seq[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) => diff --git a/src/test/scala/splatter/stutter/PrimitiveExpressions.scala b/src/test/scala/splatter/stutter/PrimitiveExpressions.scala index 3f4923b..dfdc0ad 100644 --- a/src/test/scala/splatter/stutter/PrimitiveExpressions.scala +++ b/src/test/scala/splatter/stutter/PrimitiveExpressions.scala @@ -20,7 +20,7 @@ class PrimitiveExpressions extends AnyFunSpec: parseLisp("(cond (eq ('a 'b) 'first) (eq ('a 'a) ('second)))") ) foreach ( e => ( - e match { + e match case Lisp(Seq(AtomLit.Op, _)) => AtomLit.Op.value case Lisp(Seq(QuoteLit.Op, _)) => QuoteLit.Op.value case Lisp(Seq(EqLit.Op, _, _)) => EqLit.Op.value @@ -29,7 +29,6 @@ class PrimitiveExpressions extends AnyFunSpec: case Lisp(Seq(ConsLit.Op, _, _)) => ConsLit.Op.value case Lisp(CondLit.Op +: _) => CondLit.Op.value case _ => sys.error(s"unmatched test expression $e") - } ) should be (e.asInstanceOf[Lisp].subs.head.asInstanceOf[Atom].value) ) From a9e1edf3af41757a47c7ffb1addafd5aa033124a Mon Sep 17 00:00:00 2001 From: nmcb Date: Sat, 20 Jun 2026 08:50:46 +0200 Subject: [PATCH 2/2] add parse extension method --- src/main/scala/splatter/stutter/stutter.scala | 4 + .../splatter/stutter/FunctionCallSpec.scala | 10 +-- .../scala/splatter/stutter/ParserSpec.scala | 27 ++++--- .../stutter/PrimitiveExpressions.scala | 75 +++++++++++-------- 4 files changed, 66 insertions(+), 50 deletions(-) diff --git a/src/main/scala/splatter/stutter/stutter.scala b/src/main/scala/splatter/stutter/stutter.scala index a0b94b7..15080b6 100644 --- a/src/main/scala/splatter/stutter/stutter.scala +++ b/src/main/scala/splatter/stutter/stutter.scala @@ -176,3 +176,7 @@ object Parser: def parseLisp(s: String): Expr = run(expr)(s) + + extension (s: String) + def parse: Expr = + parseLisp(s) diff --git a/src/test/scala/splatter/stutter/FunctionCallSpec.scala b/src/test/scala/splatter/stutter/FunctionCallSpec.scala index 3c5f43a..199af24 100644 --- a/src/test/scala/splatter/stutter/FunctionCallSpec.scala +++ b/src/test/scala/splatter/stutter/FunctionCallSpec.scala @@ -15,7 +15,7 @@ class FunctionCallSpec extends AnyFunSpec: | ((lambda (x) (cons x '(b))) | 'a) | - """.stripMargin) should be (parseLisp("(a b)")) + """.stripMargin) should be ("(a b)".parse) eval( """ @@ -23,7 +23,7 @@ class FunctionCallSpec extends AnyFunSpec: | 'z | '(a b c)) | - """.stripMargin) should be (parseLisp("(z b c)")) + """.stripMargin) should be ("(z b c)".parse) it("treats parameters as operators in expressions as well as arguments"): eval( @@ -31,12 +31,12 @@ class FunctionCallSpec extends AnyFunSpec: | ((lambda (f) (f '(b c))) | ’(lambda (x) (cons 'a x))) | - """.stripMargin) should be (parseLisp("(a b c)")) + """.stripMargin) should be ("(a b c)".parse) describe("structural expression replacement"): it("replaces expressions recursively, though maintains structural composition"): - replace(parseLisp("(a b (c d (e) f) (g) h)"), Map( + replace("(a b (c d (e) f) (g) h)".parse, Map( Atom("a") -> Atom("A"), Atom("b") -> Atom("B"), Atom("c") -> Atom("C"), @@ -45,4 +45,4 @@ class FunctionCallSpec extends AnyFunSpec: Atom("f") -> Atom("F"), Atom("g") -> Lisp(Nil), Atom("h") -> Atom("H") - )) should be (parseLisp("(A B (C (DA DB) (E) F) (()) H)")) + )) should be ("(A B (C (DA DB) (E) F) (()) H)".parse) diff --git a/src/test/scala/splatter/stutter/ParserSpec.scala b/src/test/scala/splatter/stutter/ParserSpec.scala index 4eebbc4..ce9d346 100644 --- a/src/test/scala/splatter/stutter/ParserSpec.scala +++ b/src/test/scala/splatter/stutter/ParserSpec.scala @@ -10,31 +10,31 @@ class ParserSpec extends AnyFunSpec: describe("Parser"): it("should parse atoms"): - parseLisp("foo") should be( + "foo".parse should be( Atom("foo")) it("should parse lists"): - parseLisp("()") should be( + "()".parse should be( Lisp(Nil)) - parseLisp("(foo)") should be( + "(foo)".parse should be( Lisp(Seq(Atom("foo")))) - parseLisp("(foo bar)") should be( + "(foo bar)".parse should be( Lisp(Seq(Atom("foo"), Atom("bar")))) - parseLisp("(a b (c) d)") should be( + "(a b (c) d)".parse should be( Lisp(Seq(Atom("a"), Atom("b"), Lisp(Seq(Atom("c"))), Atom("d")))) it("should parse normal single ' quotes on atoms and lists"): - parseLisp("'a") should be( + "'a".parse should be( Lisp(Seq(Atom("quote"), Atom("a")))) - parseLisp("'(a b c)") should be( + "'(a b c)".parse should be( Lisp(Seq(Atom("quote"), Lisp(Seq(Atom("a"), Atom("b"), Atom("c")))))) it("should parse ugly, copy-paste, english, burn your fingers, sharp ’ quotes"): - parseLisp("’a") should be( + "’a".parse should be( Lisp(Seq(Atom("quote"), Atom("a")))) it("should parse complex structures"): - parseLisp("((lambda (x) (cons x '(b))) 'a)") should be( + "((lambda (x) (cons x '(b))) 'a)".parse should be( Lisp(Seq( Lisp(Seq( Atom("lambda"), @@ -54,15 +54,14 @@ class ParserSpec extends AnyFunSpec: ) it("should handle complex whitespace"): - parseLisp("( a )") should be (parseLisp("(a)")) - parseLisp( - """'( + "( a )".parse should be ("(a)".parse) + """'( | a | b | c - |)""".stripMargin) should be(parseLisp("'(a b c)")) + |)""".stripMargin.parse should be("'(a b c)".parse) /* Sanity Checks */ it("should parse lists recursively"): - parseLisp("(())") should be(Lisp(Seq(Lisp(Nil)))) + "(())".parse should be(Lisp(Seq(Lisp(Nil)))) diff --git a/src/test/scala/splatter/stutter/PrimitiveExpressions.scala b/src/test/scala/splatter/stutter/PrimitiveExpressions.scala index dfdc0ad..0a6df14 100644 --- a/src/test/scala/splatter/stutter/PrimitiveExpressions.scala +++ b/src/test/scala/splatter/stutter/PrimitiveExpressions.scala @@ -6,21 +6,21 @@ import org.scalatest.matchers.should.Matchers._ class PrimitiveExpressions extends AnyFunSpec: - import Parser._ + import Parser.* describe("Primitive expressions"): it("should be differentiable"): Seq( - parseLisp("(atom foo)"), - parseLisp("(quote bar)"), - parseLisp("(eq foo bar)"), - parseLisp("(car (foo bar))"), - parseLisp("(cdr (foo bar))"), - parseLisp("(cons (foo bar) (baz))"), - parseLisp("(cond (eq ('a 'b) 'first) (eq ('a 'a) ('second)))") + "(atom foo)".parse, + "(quote bar)".parse, + "(eq foo bar)".parse, + "(car (foo bar))".parse, + "(cdr (foo bar))".parse, + "(cons (foo bar) (baz))".parse, + "(cond (eq ('a 'b) 'first) (eq ('a 'a) ('second)))".parse ) foreach ( e => ( - e match + e.runtimeChecked match case Lisp(Seq(AtomLit.Op, _)) => AtomLit.Op.value case Lisp(Seq(QuoteLit.Op, _)) => QuoteLit.Op.value case Lisp(Seq(EqLit.Op, _, _)) => EqLit.Op.value @@ -28,55 +28,68 @@ class PrimitiveExpressions extends AnyFunSpec: case Lisp(Seq(CdrLit.Op, _)) => CdrLit.Op.value case Lisp(Seq(ConsLit.Op, _, _)) => ConsLit.Op.value case Lisp(CondLit.Op +: _) => CondLit.Op.value - case _ => sys.error(s"unmatched test expression $e") ) should be (e.asInstanceOf[Lisp].subs.head.asInstanceOf[Atom].value) ) it("quote expressions should be constructable"): - Lisp(Seq(QuoteLit.Op, Atom("foo"))) should be (parseLisp("(quote foo)")) + Lisp(Seq(QuoteLit.Op, Atom("foo"))) should be ("(quote foo)".parse) it("quote expressions should be extractable"): - ((parseLisp("(quote foo)") : @unchecked) match { case Lisp(Seq(QuoteLit.Op, foo)) => foo }) should be (parseLisp("foo")) - ((parseLisp("(quote foo)") : @unchecked) match { case QuoteLit(foo) => foo }) should be (parseLisp("foo")) + "(quote foo)".parse.runtimeChecked match + case Lisp(Seq(QuoteLit.Op, foo)) => foo should be ("foo".parse) + "(quote foo)".parse.runtimeChecked match + case QuoteLit(foo) => foo should be ("foo".parse) it("atom expressions should be constructable"): - Lisp(Seq(AtomLit.Op, Atom("foo"))) should be (parseLisp("(atom foo)")) + Lisp(Seq(AtomLit.Op, Atom("foo"))) should be ("(atom foo)".parse) it("atom expressions should be extractable"): - ((parseLisp("(atom foo)") : @unchecked) match { case Lisp(Seq(AtomLit.Op, foo)) => foo }) should be (parseLisp("foo")) - ((parseLisp("(atom foo)") : @unchecked) match { case AtomLit(foo) => foo }) should be (parseLisp("foo")) + "(atom foo)".parse.runtimeChecked match + case Lisp(Seq(AtomLit.Op, foo)) => foo should be ("foo".parse) + "(atom foo)".parse.runtimeChecked match + case AtomLit(foo) => foo should be ("foo".parse) it("eq expressions should be constructable"): - Lisp(Seq(EqLit.Op, Atom("foo"), Atom("bar"))) should be (parseLisp("(eq foo bar)")) + Lisp(Seq(EqLit.Op, Atom("foo"), Atom("bar"))) should be ("(eq foo bar)".parse) it("eq expressions should be extractable"): - ((parseLisp("(eq foo bar)") : @unchecked) match { case Lisp(Seq(EqLit.Op, foo, bar)) => (foo,bar) }) should be ((parseLisp("foo"), parseLisp("bar"))) - ((parseLisp("(eq foo bar)") : @unchecked) match { case EqLit(foo, bar) => (foo,bar) }) should be ((parseLisp("foo"), parseLisp("bar"))) + "(eq foo bar)".parse.runtimeChecked match + case Lisp(Seq(EqLit.Op, foo, bar)) => (foo,bar) should be ("foo".parse, "bar".parse) + "(eq foo bar)".parse.runtimeChecked match + case EqLit(foo, bar) => (foo,bar) should be ("foo".parse, "bar".parse) it("car expressions should be constructable"): - Lisp(Seq(CarLit.Op, Lisp(Seq(Atom("foo"), Atom("bar"))))) should be (parseLisp("(car (foo bar))")) + Lisp(Seq(CarLit.Op, Lisp(Seq(Atom("foo"), Atom("bar"))))) should be ("(car (foo bar))".parse) it("car expressions should be extractable"): - ((parseLisp("(car (foo bar))") : @unchecked) match { case Lisp(Seq(CarLit.Op, arg)) => arg }) should be (parseLisp("(foo bar)")) - ((parseLisp("(car (foo bar))") : @unchecked) match { case CarLit(arg) => arg }) should be (parseLisp("(foo bar)")) + "(car (foo bar))".parse.runtimeChecked match + case Lisp(Seq(CarLit.Op, arg)) => arg should be ("(foo bar)".parse) + "(car (foo bar))".parse.runtimeChecked match + case CarLit(arg) => arg should be ("(foo bar)".parse) it("cdr expressions should be constructable"): - Lisp(Seq(CdrLit.Op, (Lisp(Seq(Atom("foo"), Atom("bar")))))) should be (parseLisp("(cdr (foo bar))")) + Lisp(Seq(CdrLit.Op, (Lisp(Seq(Atom("foo"), Atom("bar")))))) should be ("(cdr (foo bar))".parse) it("cdr expressions should be extractable"): - ((parseLisp("(cdr (foo bar))") : @unchecked) match { case Lisp(Seq(CdrLit.Op, arg)) => arg }) should be (parseLisp("(foo bar)")) - ((parseLisp("(cdr (foo bar))") : @unchecked) match { case CdrLit(arg) => arg }) should be (parseLisp("(foo bar)")) + "(cdr (foo bar))".parse.runtimeChecked match + case Lisp(Seq(CdrLit.Op, arg)) => arg should be ("(foo bar)".parse) + "(cdr (foo bar))".parse.runtimeChecked match + case CdrLit(arg) => arg should be ("(foo bar)".parse) it("cons expressions should be constructable"): - Lisp(Seq(ConsLit.Op, Lisp(Seq(Atom("foo"))), Lisp(Seq(Atom("bar"))))) should be (parseLisp("(cons (foo) (bar))")) + Lisp(Seq(ConsLit.Op, Lisp(Seq(Atom("foo"))), Lisp(Seq(Atom("bar"))))) should be ("(cons (foo) (bar))".parse) it("cons expressions should be extractable"): - ((parseLisp("(cons (foo) (bar))") : @unchecked) match { case Lisp(Seq(ConsLit.Op, l1, l2)) => (l1, l2) }) should be ((parseLisp("(foo)"), parseLisp("(bar)"))) - ((parseLisp("(cons (foo) (bar))") : @unchecked) match { case ConsLit(l1, l2) => (l1, l2) }) should be ((parseLisp("(foo)"), parseLisp("(bar)"))) + "(cons (foo) (bar))".parse.runtimeChecked match + case Lisp(Seq(ConsLit.Op, l1, l2)) => (l1, l2) should be ("(foo)".parse, "(bar)".parse) + "(cons (foo) (bar))".parse.runtimeChecked match + case ConsLit(l1, l2) => (l1, l2) should be ("(foo)".parse, "(bar)".parse) it("cond expressions should be constructable"): - Lisp(Seq(CondLit.Op, Lisp(Seq(Atom("foo"))), Lisp(Seq(Atom("bar"))))) should be (parseLisp("(cond (foo) (bar))")) + Lisp(Seq(CondLit.Op, Lisp(Seq(Atom("foo"))), Lisp(Seq(Atom("bar"))))) should be ("(cond (foo) (bar))".parse) it("cond expressions should be extractable"): - ((parseLisp("(cond (foo) (bar))") : @unchecked) match { case Lisp(Seq(CondLit.Op, l1, l2)) => (l1, l2) }) should be ((parseLisp("(foo)"), parseLisp("(bar)"))) - ((parseLisp("(cond (foo) (bar))") : @unchecked) match { case CondLit(Seq(l1, l2)) => (l1, l2) }) should be ((parseLisp("(foo)"), parseLisp("(bar)"))) + "(cond (foo) (bar))".parse.runtimeChecked match + case Lisp(Seq(CondLit.Op, l1, l2)) => (l1, l2) should be ("(foo)".parse, "(bar)".parse) + "(cond (foo) (bar))".parse.runtimeChecked match + case CondLit(Seq(l1, l2)) => (l1, l2) should be ("(foo)".parse, "(bar)".parse)