diff --git a/scrapscript.py b/scrapscript.py index cb0466d5..b55792ee 100755 --- a/scrapscript.py +++ b/scrapscript.py @@ -938,6 +938,7 @@ def __str__(self) -> str: class Closure(Object): env: Env func: Union[Function, MatchFunction] + name: Optional[str] = None def serialize(self) -> Dict[str, object]: return self._serialize(env=serialize_env(self.env), func=self.func.serialize()) @@ -955,8 +956,9 @@ def deserialize(msg: Dict[str, object]) -> "Closure": return Closure(env, func) def __str__(self) -> str: - # TODO: Better pretty printing for Closure - return self.__repr__() + if self.name is not None: + return f"" + return f"" @dataclass(eq=True, frozen=True, unsafe_hash=True) @@ -1208,7 +1210,7 @@ def free_in(exp: Object) -> Set[str]: def improve_closure(closure: Closure) -> Closure: freevars = free_in(closure.func) env = {boundvar: value for boundvar, value in closure.env.items() if boundvar in freevars} - return Closure(env, closure.func) + return Closure(env, closure.func, name=closure.name) def eval_exp(env: Env, exp: Object) -> Object: @@ -1237,6 +1239,9 @@ def eval_exp(env: Env, exp: Object) -> Object: assert isinstance(exp.name, Var) value = eval_exp(env, exp.value) if isinstance(value, Closure): + # If creating a Closure with an Assign we can specify a name for the + # Closure, which may be useful for debugging. + value = Closure(value.env, value.func, name=exp.name.name) # We want functions to be able to call themselves without using the # Y combinator or similar, so we bind functions (and only # functions) using a letrec-like strategy. We augment their @@ -2727,7 +2732,7 @@ def test_eval_assign_function_returns_closure_without_function_in_env(self) -> N assert isinstance(result, EnvObject) closure = result.env["a"] self.assertIsInstance(closure, Closure) - self.assertEqual(closure, Closure({}, Function(Var("x"), Var("x")))) + self.assertEqual(closure, Closure({}, Function(Var("x"), Var("x")), "a")) def test_eval_assign_function_returns_closure_with_function_in_env(self) -> None: exp = Assign(Var("a"), Function(Var("x"), Var("a"))) @@ -2735,7 +2740,7 @@ def test_eval_assign_function_returns_closure_with_function_in_env(self) -> None assert isinstance(result, EnvObject) closure = result.env["a"] self.assertIsInstance(closure, Closure) - self.assertEqual(closure, Closure({"a": closure}, Function(Var("x"), Var("a")))) + self.assertEqual(closure, Closure({"a": closure}, Function(Var("x"), Var("a")), "a")) def test_eval_assign_does_not_modify_env(self) -> None: exp = Assign(Var("a"), Int(1)) @@ -3384,7 +3389,7 @@ def test_function_can_reference_itself(self) -> None: """, {}, ) - self.assertEqual(result, Closure({"f": result}, Function(Var("n"), Var("f")))) + self.assertEqual(result, Closure({"f": result}, Function(Var("n"), Var("f")), "f")) def test_function_can_call_itself(self) -> None: with self.assertRaises(RecursionError): @@ -4344,9 +4349,7 @@ def test_pretty_print_nativefunction(self) -> None: def test_pretty_print_closure(self) -> None: obj = Closure({"a": Int(123)}, Function(Var("x"), Var("x"))) - self.assertEqual( - str(obj), "Closure(env={'a': Int(value=123)}, func=Function(arg=Var(name='x'), body=Var(name='x')))" - ) + self.assertRegex(str(obj), r"") def test_pretty_print_record(self) -> None: obj = Record({"a": Int(1), "b": Int(2)})