From 03b5ad2bffbaf527aa0f68fc80a359043ba3c4dc Mon Sep 17 00:00:00 2001 From: nmcb Date: Sat, 9 May 2026 01:34:25 +0000 Subject: [PATCH 1/3] 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/3] 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) From e98c2de8dddfe6538b9a665e1ae6260885b153cb Mon Sep 17 00:00:00 2001 From: nmcb Date: Sat, 20 Jun 2026 19:04:40 +0200 Subject: [PATCH 3/3] add eval extension --- src/main/scala/splatter/stutter/stutter.scala | 37 +++++++++--------- .../splatter/stutter/ExpressionAxioms.scala | 39 ++++++++++--------- .../splatter/stutter/FunctionCallSpec.scala | 35 ++++++++--------- 3 files changed, 56 insertions(+), 55 deletions(-) diff --git a/src/main/scala/splatter/stutter/stutter.scala b/src/main/scala/splatter/stutter/stutter.scala index 15080b6..5acfbb6 100644 --- a/src/main/scala/splatter/stutter/stutter.scala +++ b/src/main/scala/splatter/stutter/stutter.scala @@ -11,6 +11,10 @@ object Expr: val t = Atom("t") val f = Lisp(Nil) + extension (e: Expr) + def eval: Expr = + evalExpr(e) + case class Atom(value: String) extends Expr: def isAtom: Boolean = true def isLisp: Boolean = false @@ -84,15 +88,15 @@ object QuotedFunction extends Extractable[(Seq[Expr], Atom, Seq[Expr], Seq[Expr (parms, op, fargs, args) } -def eval(e: Expr): Expr = +def evalExpr(e: Expr): Expr = e match // quoted function calls first // - unquote the lambda present in the first argument case QuotedFunction(parms, op, fargs, args) => - eval(Lisp(args.head match + Lisp(args.head match case Lisp(Seq(QuoteLit.Op, lambda)) => lambda +: fargs - case _ => sys.error("lambda expression not quoted"))) + case _ => sys.error("lambda expression not quoted")).eval // function calls second // - eval all args except for quoted ones @@ -100,46 +104,43 @@ def eval(e: Expr): Expr = case Function(parms, expr, args) => val evaluated = args.map: case q @ QuoteLit(_) => q - case e : Expr => eval(e) + case e : Expr => e.eval val replaced = replace(expr, parms.zip(evaluated).toMap) - eval(replaced) + replaced.eval // primitive operations last case QuoteLit(arg) => arg - case AtomLit(arg) => eval(arg) match + case AtomLit(arg) => arg.eval match case a: Atom => Expr.t case l: Lisp if l.isEmpty => Expr.t case _ => Expr.f - case EqLit(a, b) => (eval(a), eval(b)) match + case EqLit(a, b) => (a.eval, b.eval) match case (a1: Atom, a2: Atom) if a1 == a2 => Expr.t case (l1: Lisp, l2: Lisp) if l1.isEmpty && l2.isEmpty => Expr.t case _ => Expr.f - case CarLit(arg) => eval(arg) match + case CarLit(arg) => arg.eval match case l: Lisp if l.isEmpty => sys.error("car on empty list") case l: Lisp => l.subs.head case a: Atom => sys.error(s"not a list: $a") - case CdrLit(arg) => eval(arg) match + case CdrLit(arg) => arg.eval match case l: Lisp if l.subs.size <= 1 => sys.error("cdr on empty or singleton list") case l: Lisp => Lisp(l.subs.tail) case a: Atom => sys.error(s"not a list: $a") - case ConsLit(a, b) => (eval(a), eval(b)) match + case ConsLit(a, b) => (a.eval, b.eval) match case (e, Lisp(es)) => Lisp(e +: es) case (_, a: Atom) => sys.error(s"not a list: $a") case CondLit(args) => args .find: - case Lisp(Seq(p, e)) => eval(p) == Expr.t + case Lisp(Seq(p, e)) => p.eval == Expr.t case e: Expr => sys.error(s"not a conditional $e") .getOrElse(sys.error("undefined")) match - case Lisp(Seq(_, expr)) => eval(expr) - case e => sys.error(s"has no argument list $e") + case Lisp(Seq(_, e)) => e.eval + case e => sys.error(s"has no argument list $e") case _ => sys.error(s"invalid expression: $e") -def eval(s: String): Expr = - eval(Parser.parseLisp(s)) - def replace(expr: Expr, parms: Map[Expr, Expr]): Expr = Lisp(expr.subs.map: case a: Atom if parms.keySet.contains(a) => parms(a) @@ -176,7 +177,7 @@ object Parser: def parseLisp(s: String): Expr = run(expr)(s) - + extension (s: String) def parse: Expr = - parseLisp(s) + parseLisp(s) diff --git a/src/test/scala/splatter/stutter/ExpressionAxioms.scala b/src/test/scala/splatter/stutter/ExpressionAxioms.scala index 2b8ade1..e36c847 100644 --- a/src/test/scala/splatter/stutter/ExpressionAxioms.scala +++ b/src/test/scala/splatter/stutter/ExpressionAxioms.scala @@ -5,41 +5,44 @@ import org.scalatest.funspec.AnyFunSpec import org.scalatest.matchers.should.Matchers._ class ExpressionAxioms extends AnyFunSpec: + + import Parser.* + import Expr.* describe("Expression axioms"): it("quote expressions should yield the quoted expression"): - eval("(quote a)").toString should be ("a") - eval("'a").toString should be ("a") - eval("(quote (a b c))").toString should be ("(a b c)") + "(quote a)".parse.eval.toString should be ("a") + "'a".parse.eval.toString should be ("a") + "(quote (a b c))".parse.eval.toString should be ("(a b c)") it("atom expressions should yield the atom `t` if the argument yields an atom or the empty list"): - eval("(atom 'a)").toString should be ("t") - eval("(atom '())").toString should be ("t") - eval("(atom (atom 'a))").toString should be ("t") + "(atom 'a)".parse.eval.toString should be ("t") + "(atom '())".parse.eval.toString should be ("t") + "(atom (atom 'a))".parse.eval.toString should be ("t") it("atom expression should yield the empty list if the argument does not yield an atom or the empty list"): - eval("(atom '(atom a))").toString should be ("()") - eval("(atom '(a b c))").toString should be ("()") + "(atom '(atom a))".parse.eval.toString should be ("()") + "(atom '(a b c))".parse.eval.toString should be ("()") it("eq expression should yield the atom `t` if both arguments yield the same atom or both the empty list"): - eval("(eq 'a 'a)").toString should be ("t") - eval("(eq '() '())").toString should be ("t") + "(eq 'a 'a)".parse.eval.toString should be ("t") + "(eq '() '())".parse.eval.toString should be ("t") it("eq expression should yield the empty list if both arguments do not yield the same atom or the empty list"): - eval("(eq 'a 'b)").toString should be ("()") + "(eq 'a 'b)".parse.eval.toString should be ("()") it("car expression should yield the first element of its argument list"): - eval("(car '(a b c))").toString should be ("a") + "(car '(a b c))".parse.eval.toString should be ("a") it("cdr expression should yield everything after the first element of its argument list"): - eval("(cdr '(a b c))").toString should be ("(b c)") + "(cdr '(a b c))".parse.eval.toString should be ("(b c)") it("cons expression should return a list containing the value of its first argument followed by the elements of the value of its second argument"): - eval("(cons 'a '(b c))").toString should be ("(a b c)") - eval("(cons 'a (cons 'b (cons 'c '())))").toString should be ("(a b c)") - eval("(car (cons 'a '(b c)))").toString should be ("a") - eval("(cdr (cons 'a '(b c)))").toString should be ("(b c)") + "(cons 'a '(b c))".parse.eval.toString should be ("(a b c)") + "(cons 'a (cons 'b (cons 'c '())))".parse.eval.toString should be ("(a b c)") + "(car (cons 'a '(b c)))".parse.eval.toString should be ("a") + "(cdr (cons 'a '(b c)))".parse.eval.toString should be ("(b c)") it("cond expression (cond (p1 e1)...(pn en)) is evaluated as follows; the p expressions are evaluated in order until one returns t; when one is found, the value of the corresponding e expression is returned as the value of the whole cond expression."): - eval("(cond ((eq 'a 'b) 'first) ((atom 'a) 'second))").toString should be ("second") + "(cond ((eq 'a 'b) 'first) ((atom 'a) 'second))".parse.eval.toString should be ("second") \ No newline at end of file diff --git a/src/test/scala/splatter/stutter/FunctionCallSpec.scala b/src/test/scala/splatter/stutter/FunctionCallSpec.scala index 199af24..6d6ff72 100644 --- a/src/test/scala/splatter/stutter/FunctionCallSpec.scala +++ b/src/test/scala/splatter/stutter/FunctionCallSpec.scala @@ -10,28 +10,25 @@ class FunctionCallSpec extends AnyFunSpec: describe("A function call"): it("works as specified in chapter 2 of roots of lisp..."): - eval( - """ - | ((lambda (x) (cons x '(b))) - | 'a) - | - """.stripMargin) should be ("(a b)".parse) + """ + | ((lambda (x) (cons x '(b))) + | 'a) + | + """.stripMargin.parse.eval should be ("(a b)".parse) - eval( - """ - | ((lambda (x y) (cons x (cdr y))) - | 'z - | '(a b c)) - | - """.stripMargin) should be ("(z b c)".parse) + """ + | ((lambda (x y) (cons x (cdr y))) + | 'z + | '(a b c)) + | + """.stripMargin.parse.eval should be ("(z b c)".parse) it("treats parameters as operators in expressions as well as arguments"): - eval( - """ - | ((lambda (f) (f '(b c))) - | ’(lambda (x) (cons 'a x))) - | - """.stripMargin) should be ("(a b c)".parse) + """ + | ((lambda (f) (f '(b c))) + | ’(lambda (x) (cons 'a x))) + | + """.stripMargin.parse.eval should be ("(a b c)".parse) describe("structural expression replacement"):