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)