this repo has no description

Use precedence-aware pretty printing (#191)

Also remove Compose because it doesn't really represent anything other
than a new function

authored by bernsteinbear.com and committed by

GitHub efefb7ff 67b74f2d

+209 -168
+209 -168
scrapscript.py
··· 4 4 import code 5 5 import dataclasses 6 6 import enum 7 + import functools 7 8 import json 8 9 import logging 9 10 import os ··· 324 325 "||": rp(7), 325 326 "|>": rp(6), 326 327 "<|": lp(6), 328 + "#": lp(5.5), 327 329 "->": lp(5), 328 330 "|": rp(4.5), 329 331 ":": lp(4.5), ··· 360 362 if not isinstance(assign, Assign): 361 363 raise ParseError("failed to parse variable assignment in record constructor") 362 364 return assign 365 + 366 + 367 + def gensym() -> str: 368 + gensym.counter += 1 # type: ignore 369 + return f"$v{gensym.counter}" # type: ignore 370 + 371 + 372 + def gensym_reset() -> None: 373 + gensym.counter = -1 # type: ignore 374 + 375 + 376 + gensym_reset() 363 377 364 378 365 379 def parse(tokens: typing.List[Token], p: float = 0) -> "Object": ··· 487 501 elif op == Operator("<|"): 488 502 l = Apply(l, parse(tokens, pr)) 489 503 elif op == Operator(">>"): 490 - l = Compose(l, parse(tokens, pr)) 504 + r = parse(tokens, pr) 505 + varname = gensym() 506 + l = Function(Var(varname), Apply(r, Apply(l, Var(varname)))) 491 507 elif op == Operator("<<"): 492 - l = Compose(parse(tokens, pr), l) 508 + r = parse(tokens, pr) 509 + varname = gensym() 510 + l = Function(Var(varname), Apply(l, Apply(r, Var(varname)))) 493 511 elif op == Operator("."): 494 512 l = Where(l, parse(tokens, pr)) 495 513 elif op == Operator("?"): ··· 507 525 @dataclass(eq=True, frozen=True, unsafe_hash=True) 508 526 class Object: 509 527 def __str__(self) -> str: 510 - raise NotImplementedError("__str__ not implemented for superclass Object") 528 + return pretty(self) 511 529 512 530 513 531 @dataclass(eq=True, frozen=True, unsafe_hash=True) 514 532 class Int(Object): 515 533 value: int 516 534 517 - def __str__(self) -> str: 518 - return str(self.value) 519 - 520 535 521 536 @dataclass(eq=True, frozen=True, unsafe_hash=True) 522 537 class Float(Object): 523 538 value: float 524 - 525 - def __str__(self) -> str: 526 - return str(self.value) 527 539 528 540 529 541 @dataclass(eq=True, frozen=True, unsafe_hash=True) 530 542 class String(Object): 531 543 value: str 532 544 533 - def __str__(self) -> str: 534 - # TODO: handle nested quotes 535 - return f'"{self.value}"' 536 - 537 545 538 546 @dataclass(eq=True, frozen=True, unsafe_hash=True) 539 547 class Bytes(Object): 540 548 value: bytes 541 549 542 - def __str__(self) -> str: 543 - return f"~~{base64.b64encode(self.value).decode()}" 544 - 545 550 546 551 @dataclass(eq=True, frozen=True, unsafe_hash=True) 547 552 class Var(Object): 548 553 name: str 549 554 550 - def __str__(self) -> str: 551 - return self.name 552 - 553 555 554 556 @dataclass(eq=True, frozen=True, unsafe_hash=True) 555 557 class Hole(Object): 556 - def __str__(self) -> str: 557 - return "()" 558 + pass 558 559 559 560 560 561 @dataclass(eq=True, frozen=True, unsafe_hash=True) 561 562 class Spread(Object): 562 563 name: Optional[str] = None 563 - 564 - def __str__(self) -> str: 565 - return "..." if self.name is None else f"...{self.name}" 566 564 567 565 568 566 Env = Mapping[str, Object] ··· 652 650 left: Object 653 651 right: Object 654 652 655 - def __str__(self) -> str: 656 - return f"{self.left} {BinopKind.to_str(self.op)} {self.right}" 657 - 658 653 659 654 @dataclass(eq=True, frozen=True, unsafe_hash=True) 660 655 class List(Object): 661 656 items: typing.List[Object] 662 657 663 - def __str__(self) -> str: 664 - inner = ", ".join(str(item) for item in self.items) 665 - return f"[{inner}]" 666 - 667 658 668 659 @dataclass(eq=True, frozen=True, unsafe_hash=True) 669 660 class Assign(Object): 670 661 name: Var 671 662 value: Object 672 663 673 - def __str__(self) -> str: 674 - return f"{self.name} = {self.value}" 675 - 676 664 677 665 @dataclass(eq=True, frozen=True, unsafe_hash=True) 678 666 class Function(Object): 679 667 arg: Object 680 668 body: Object 681 - 682 - def __str__(self) -> str: 683 - # TODO: Better pretty printing for Function 684 - return self.__repr__() 685 669 686 670 687 671 @dataclass(eq=True, frozen=True, unsafe_hash=True) ··· 689 673 func: Object 690 674 arg: Object 691 675 692 - def __str__(self) -> str: 693 - # TODO: Better pretty printing for Apply 694 - return self.__repr__() 695 - 696 - 697 - @dataclass(eq=True, frozen=True, unsafe_hash=True) 698 - class Compose(Object): 699 - inner: Object 700 - outer: Object 701 - 702 - def __str__(self) -> str: 703 - # TODO: Better pretty printing for Compose 704 - return self.__repr__() 705 - 706 676 707 677 @dataclass(eq=True, frozen=True, unsafe_hash=True) 708 678 class Where(Object): 709 679 body: Object 710 680 binding: Object 711 - 712 - def __str__(self) -> str: 713 - # TODO: Better pretty printing for Where 714 - return self.__repr__() 715 681 716 682 717 683 @dataclass(eq=True, frozen=True, unsafe_hash=True) ··· 719 685 value: Object 720 686 cond: Object 721 687 722 - def __str__(self) -> str: 723 - # TODO: Better pretty printing for Assert 724 - return self.__repr__() 725 - 726 688 727 689 @dataclass(eq=True, frozen=True, unsafe_hash=True) 728 690 class EnvObject(Object): ··· 737 699 pattern: Object 738 700 body: Object 739 701 740 - def __str__(self) -> str: 741 - # TODO: Better pretty printing for MatchCase 742 - return self.__repr__() 743 - 744 702 745 703 @dataclass(eq=True, frozen=True, unsafe_hash=True) 746 704 class MatchFunction(Object): 747 705 cases: typing.List[MatchCase] 748 - 749 - def __str__(self) -> str: 750 - # TODO: Better pretty printing for MatchFunction 751 - return self.__repr__() 752 706 753 707 754 708 @dataclass(eq=True, frozen=True, unsafe_hash=True) 755 709 class Relocation(Object): 756 710 name: str 757 711 758 - def __str__(self) -> str: 759 - # TODO: Better pretty printing for Relocation 760 - return self.__repr__() 761 - 762 712 763 713 @dataclass(eq=True, frozen=True, unsafe_hash=True) 764 714 class NativeFunctionRelocation(Relocation): 765 - def __str__(self) -> str: 766 - # TODO: Better pretty printing for NativeFunctionRelocation 767 - return self.__repr__() 715 + pass 768 716 769 717 770 718 @dataclass(eq=True, frozen=True, unsafe_hash=True) ··· 772 720 name: str 773 721 func: Callable[[Object], Object] 774 722 775 - def __str__(self) -> str: 776 - # TODO: Better pretty printing for NativeFunction 777 - return f"NativeFunction(name={self.name})" 778 - 779 723 780 724 @dataclass(eq=True, frozen=True, unsafe_hash=True) 781 725 class Closure(Object): 782 726 env: Env 783 727 func: Union[Function, MatchFunction] 784 - 785 - def __str__(self) -> str: 786 - # TODO: Better pretty printing for Closure 787 - return self.__repr__() 788 728 789 729 790 730 @dataclass(eq=True, frozen=True, unsafe_hash=True) 791 731 class Record(Object): 792 732 data: Dict[str, Object] 793 733 794 - def __str__(self) -> str: 795 - inner = ", ".join(f"{k} = {self.data[k]}" for k in self.data) 796 - return f"{{{inner}}}" 797 - 798 734 799 735 @dataclass(eq=True, frozen=True, unsafe_hash=True) 800 736 class Access(Object): 801 737 obj: Object 802 738 at: Object 803 739 804 - def __str__(self) -> str: 805 - # TODO: Better pretty printing for Access 806 - return self.__repr__() 807 - 808 740 809 741 @dataclass(eq=True, frozen=True, unsafe_hash=True) 810 742 class Variant(Object): 811 743 tag: str 812 744 value: Object 813 - 814 - def __str__(self) -> str: 815 - return f"#{self.tag} {self.value}" 816 745 817 746 818 747 tags = [ ··· 1487 1416 raise ValueError(f"index {access_at.value} out of bounds for list") 1488 1417 return obj.items[access_at.value] 1489 1418 raise TypeError(f"attempted to access from type {type(obj).__name__}") 1490 - if isinstance(exp, Compose): 1491 - clo_inner = eval_exp(env, exp.inner) 1492 - clo_outer = eval_exp(env, exp.outer) 1493 - return Closure({}, Function(Var("x"), Apply(clo_outer, Apply(clo_inner, Var("x"))))) 1494 1419 elif isinstance(exp, Spread): 1495 1420 raise RuntimeError("cannot evaluate a spread") 1496 1421 raise NotImplementedError(f"eval_exp not implemented for {exp}") ··· 2261 2186 ) 2262 2187 2263 2188 def test_parse_compose(self) -> None: 2264 - self.assertEqual(parse([Name("f"), Operator(">>"), Name("g")]), Compose(Var("f"), Var("g"))) 2189 + gensym_reset() 2190 + self.assertEqual( 2191 + parse([Name("f"), Operator(">>"), Name("g")]), 2192 + Function(Var("$v0"), Apply(Var("g"), Apply(Var("f"), Var("$v0")))), 2193 + ) 2265 2194 2266 2195 def test_parse_compose_reverse(self) -> None: 2267 - self.assertEqual(parse([Name("f"), Operator("<<"), Name("g")]), Compose(Var("g"), Var("f"))) 2196 + gensym_reset() 2197 + self.assertEqual( 2198 + parse([Name("f"), Operator("<<"), Name("g")]), 2199 + Function(Var("$v0"), Apply(Var("f"), Apply(Var("g"), Var("$v0")))), 2200 + ) 2268 2201 2269 2202 def test_parse_double_compose(self) -> None: 2203 + gensym_reset() 2270 2204 self.assertEqual( 2271 2205 parse([Name("f"), Operator("<<"), Name("g"), Operator("<<"), Name("h")]), 2272 - Compose(Compose(Var("h"), Var("g")), Var("f")), 2206 + Function( 2207 + Var("$v1"), 2208 + Apply(Var("f"), Apply(Function(Var("$v0"), Apply(Var("g"), Apply(Var("h"), Var("$v0")))), Var("$v1"))), 2209 + ), 2273 2210 ) 2274 2211 2275 2212 def test_boolean_and_binds_tighter_than_or(self) -> None: ··· 2967 2904 self.assertEqual(eval_exp({}, exp), Int(2)) 2968 2905 2969 2906 def test_eval_compose(self) -> None: 2970 - exp = Compose( 2971 - Function(Var("x"), Binop(BinopKind.ADD, Var("x"), Int(3))), 2972 - Function(Var("x"), Binop(BinopKind.MUL, Var("x"), Int(2))), 2973 - ) 2907 + gensym_reset() 2908 + exp = parse(tokenize("(x -> x + 3) << (x -> x * 2)")) 2974 2909 env = {"a": Int(1)} 2975 2910 expected = Closure( 2976 2911 {}, 2977 2912 Function( 2978 - Var("x"), 2913 + Var("$v0"), 2979 2914 Apply( 2980 - Closure({}, Function(Var("x"), Binop(BinopKind.MUL, Var("x"), Int(2)))), 2981 - Apply(Closure({}, Function(Var("x"), Binop(BinopKind.ADD, Var("x"), Int(3)))), Var("x")), 2915 + Function(Var("x"), Binop(BinopKind.ADD, Var("x"), Int(3))), 2916 + Apply(Function(Var("x"), Binop(BinopKind.MUL, Var("x"), Int(2))), Var("$v0")), 2982 2917 ), 2983 2918 ), 2984 2919 ) 2985 2920 self.assertEqual(eval_exp(env, exp), expected) 2986 - 2987 - def test_eval_compose_apply(self) -> None: 2988 - exp = Apply( 2989 - Compose( 2990 - Function(Var("x"), Binop(BinopKind.ADD, Var("x"), Int(3))), 2991 - Function(Var("x"), Binop(BinopKind.MUL, Var("x"), Int(2))), 2992 - ), 2993 - Int(4), 2994 - ) 2995 - self.assertEqual( 2996 - eval_exp({}, exp), 2997 - Int(14), 2998 - ) 2999 2921 3000 2922 def test_eval_native_function_returns_function(self) -> None: 3001 2923 exp = NativeFunction("times2", lambda x: Int(x.value * 2)) # type: ignore [attr-defined] ··· 4284 4206 self.assertEqual(next_monad.env, {"a": Int(123), "b": Int(456)}) 4285 4207 4286 4208 4209 + Number = typing.Union[int, float] 4210 + 4211 + 4212 + class Repr(typing.Protocol): 4213 + def __call__(self, obj: Object, prec: Number = 0) -> str: ... 4214 + 4215 + 4216 + # Can't use reprlib.recursive_repr because it doesn't work if the print 4217 + # function has more than one argument (for example, prec) 4218 + def handle_recursion(func: Repr) -> Repr: 4219 + cache: typing.List[Object] = [] 4220 + 4221 + @functools.wraps(func) 4222 + def wrapper(obj: Object, prec: Number = 0) -> str: 4223 + for cached in cache: 4224 + if obj is cached: 4225 + return "..." 4226 + cache.append(obj) 4227 + result = func(obj, prec) 4228 + cache.remove(obj) 4229 + return result 4230 + 4231 + return wrapper 4232 + 4233 + 4234 + @handle_recursion 4235 + def pretty(obj: Object, prec: Number = 0) -> str: 4236 + if isinstance(obj, Int): 4237 + return str(obj.value) 4238 + if isinstance(obj, Float): 4239 + return str(obj.value) 4240 + if isinstance(obj, String): 4241 + return json.dumps(obj.value) 4242 + if isinstance(obj, Bytes): 4243 + return f"~~{base64.b64encode(obj.value).decode()}" 4244 + if isinstance(obj, Var): 4245 + return obj.name 4246 + if isinstance(obj, Hole): 4247 + return "()" 4248 + if isinstance(obj, Spread): 4249 + return f"...{obj.name}" if obj.name else "..." 4250 + if isinstance(obj, List): 4251 + return f"[{', '.join(pretty(item) for item in obj.items)}]" 4252 + if isinstance(obj, Record): 4253 + return f"{{{', '.join(f'{key} = {pretty(value)}' for key, value in obj.data.items())}}}" 4254 + if isinstance(obj, Closure): 4255 + keys = list(obj.env.keys()) 4256 + return f"Closure({keys}, {pretty(obj.func)})" 4257 + if isinstance(obj, EnvObject): 4258 + return f"EnvObject({repr(obj.env)})" 4259 + if isinstance(obj, NativeFunction): 4260 + return f"NativeFunction(name={obj.name})" 4261 + if isinstance(obj, Relocation): 4262 + return f"Relocation(name={repr(obj.name)})" 4263 + if isinstance(obj, Variant): 4264 + op_prec = PS["#"] 4265 + left_prec, right_prec = op_prec.pl, op_prec.pr 4266 + result = f"#{obj.tag} {pretty(obj.value, right_prec)}" 4267 + if isinstance(obj, Assign): 4268 + op_prec = PS["="] 4269 + left_prec, right_prec = op_prec.pl, op_prec.pr 4270 + result = f"{pretty(obj.name, left_prec)} = {pretty(obj.value, right_prec)}" 4271 + if isinstance(obj, Binop): 4272 + op_prec = PS[BinopKind.to_str(obj.op)] 4273 + left_prec, right_prec = op_prec.pl, op_prec.pr 4274 + result = f"{pretty(obj.left, left_prec)} {BinopKind.to_str(obj.op)} {pretty(obj.right, right_prec)}" 4275 + if isinstance(obj, Function): 4276 + op_prec = PS["->"] 4277 + left_prec, right_prec = op_prec.pl, op_prec.pr 4278 + assert isinstance(obj.arg, Var) 4279 + result = f"{obj.arg.name} -> {pretty(obj.body, right_prec)}" 4280 + if isinstance(obj, MatchFunction): 4281 + op_prec = PS["|"] 4282 + left_prec, right_prec = op_prec.pl, op_prec.pr 4283 + result = "\n".join( 4284 + f"| {pretty(case.pattern, left_prec)} -> {pretty(case.body, right_prec)}" for case in obj.cases 4285 + ) 4286 + if isinstance(obj, Where): 4287 + op_prec = PS["."] 4288 + left_prec, right_prec = op_prec.pl, op_prec.pr 4289 + result = f"{pretty(obj.body, left_prec)} . {pretty(obj.binding, right_prec)}" 4290 + if isinstance(obj, Assert): 4291 + op_prec = PS["!"] 4292 + left_prec, right_prec = op_prec.pl, op_prec.pr 4293 + result = f"{pretty(obj.value, left_prec)} ! {pretty(obj.cond, right_prec)}" 4294 + if isinstance(obj, Apply): 4295 + op_prec = PS[""] 4296 + left_prec, right_prec = op_prec.pl, op_prec.pr 4297 + result = f"{pretty(obj.func, left_prec)} {pretty(obj.arg, right_prec)}" 4298 + if isinstance(obj, Access): 4299 + op_prec = PS["@"] 4300 + left_prec, right_prec = op_prec.pl, op_prec.pr 4301 + result = f"{pretty(obj.obj, left_prec)} @ {pretty(obj.at, right_prec)}" 4302 + if prec >= op_prec.pl: 4303 + return f"({result})" 4304 + return result 4305 + 4306 + 4287 4307 class PrettyPrintTests(unittest.TestCase): 4288 4308 def test_pretty_print_int(self) -> None: 4289 4309 obj = Int(1) 4290 - self.assertEqual(str(obj), "1") 4310 + self.assertEqual(pretty(obj), "1") 4291 4311 4292 4312 def test_pretty_print_float(self) -> None: 4293 4313 obj = Float(3.14) 4294 - self.assertEqual(str(obj), "3.14") 4314 + self.assertEqual(pretty(obj), "3.14") 4295 4315 4296 4316 def test_pretty_print_string(self) -> None: 4297 4317 obj = String("hello") 4298 - self.assertEqual(str(obj), '"hello"') 4318 + self.assertEqual(pretty(obj), '"hello"') 4299 4319 4300 4320 def test_pretty_print_bytes(self) -> None: 4301 4321 obj = Bytes(b"abc") 4302 - self.assertEqual(str(obj), "~~YWJj") 4322 + self.assertEqual(pretty(obj), "~~YWJj") 4303 4323 4304 4324 def test_pretty_print_var(self) -> None: 4305 4325 obj = Var("ref") 4306 - self.assertEqual(str(obj), "ref") 4326 + self.assertEqual(pretty(obj), "ref") 4307 4327 4308 4328 def test_pretty_print_hole(self) -> None: 4309 4329 obj = Hole() 4310 - self.assertEqual(str(obj), "()") 4330 + self.assertEqual(pretty(obj), "()") 4311 4331 4312 4332 def test_pretty_print_spread(self) -> None: 4313 4333 obj = Spread() 4314 - self.assertEqual(str(obj), "...") 4334 + self.assertEqual(pretty(obj), "...") 4315 4335 4316 4336 def test_pretty_print_named_spread(self) -> None: 4317 4337 obj = Spread("rest") 4318 - self.assertEqual(str(obj), "...rest") 4338 + self.assertEqual(pretty(obj), "...rest") 4319 4339 4320 4340 def test_pretty_print_binop(self) -> None: 4321 4341 obj = Binop(BinopKind.ADD, Int(1), Int(2)) 4322 - self.assertEqual(str(obj), "1 + 2") 4342 + self.assertEqual(pretty(obj), "1 + 2") 4343 + 4344 + def test_pretty_print_binop_precedence(self) -> None: 4345 + obj = Binop(BinopKind.ADD, Int(1), Binop(BinopKind.MUL, Int(2), Int(3))) 4346 + self.assertEqual(pretty(obj), "1 + 2 * 3") 4347 + obj = Binop(BinopKind.MUL, Binop(BinopKind.ADD, Int(1), Int(2)), Int(3)) 4348 + self.assertEqual(pretty(obj), "(1 + 2) * 3") 4323 4349 4324 4350 def test_pretty_print_int_list(self) -> None: 4325 4351 obj = List([Int(1), Int(2), Int(3)]) 4326 - self.assertEqual(str(obj), "[1, 2, 3]") 4352 + self.assertEqual(pretty(obj), "[1, 2, 3]") 4327 4353 4328 4354 def test_pretty_print_str_list(self) -> None: 4329 4355 obj = List([String("1"), String("2"), String("3")]) 4330 - self.assertEqual(str(obj), '["1", "2", "3"]') 4356 + self.assertEqual(pretty(obj), '["1", "2", "3"]') 4357 + 4358 + def test_pretty_print_recursion(self) -> None: 4359 + obj = List([]) 4360 + obj.items.append(obj) 4361 + self.assertEqual(pretty(obj), "[...]") 4331 4362 4332 4363 def test_pretty_print_assign(self) -> None: 4333 4364 obj = Assign(Var("x"), Int(3)) 4334 - self.assertEqual(str(obj), "x = 3") 4365 + self.assertEqual(pretty(obj), "x = 3") 4335 4366 4336 4367 def test_pretty_print_function(self) -> None: 4337 4368 obj = Function(Var("x"), Binop(BinopKind.ADD, Int(1), Var("x"))) 4338 - self.assertEqual( 4339 - str(obj), 4340 - "Function(arg=Var(name='x'), body=Binop(op=<BinopKind.ADD: 1>, left=Int(value=1), right=Var(name='x')))", 4341 - ) 4369 + self.assertEqual(pretty(obj), "x -> 1 + x") 4370 + 4371 + def test_pretty_print_nested_function(self) -> None: 4372 + obj = Function(Var("x"), Function(Var("y"), Binop(BinopKind.ADD, Var("x"), Var("y")))) 4373 + self.assertEqual(pretty(obj), "x -> y -> x + y") 4342 4374 4343 4375 def test_pretty_print_apply(self) -> None: 4344 4376 obj = Apply(Var("x"), Var("y")) 4345 - self.assertEqual(str(obj), "Apply(func=Var(name='x'), arg=Var(name='y'))") 4377 + self.assertEqual(pretty(obj), "x y") 4346 4378 4347 4379 def test_pretty_print_compose(self) -> None: 4348 - obj = Compose( 4349 - Function(Var("x"), Binop(BinopKind.ADD, Var("x"), Int(3))), 4350 - Function(Var("x"), Binop(BinopKind.MUL, Var("x"), Int(2))), 4380 + gensym_reset() 4381 + obj = parse(tokenize("(x -> x + 3) << (x -> x * 2)")) 4382 + self.assertEqual( 4383 + pretty(obj), 4384 + "$v0 -> (x -> x + 3) ((x -> x * 2) $v0)", 4351 4385 ) 4386 + gensym_reset() 4387 + obj = parse(tokenize("(x -> x + 3) >> (x -> x * 2)")) 4352 4388 self.assertEqual( 4353 - str(obj), 4354 - "Compose(inner=Function(arg=Var(name='x'), body=Binop(op=<BinopKind.ADD: 1>, " 4355 - "left=Var(name='x'), right=Int(value=3))), outer=Function(arg=Var(name='x'), " 4356 - "body=Binop(op=<BinopKind.MUL: 3>, left=Var(name='x'), right=Int(value=2))))", 4389 + pretty(obj), 4390 + "$v0 -> (x -> x * 2) ((x -> x + 3) $v0)", 4357 4391 ) 4358 4392 4359 4393 def test_pretty_print_where(self) -> None: ··· 4361 4395 Binop(BinopKind.ADD, Var("a"), Var("b")), 4362 4396 Assign(Var("a"), Int(1)), 4363 4397 ) 4364 - self.assertEqual( 4365 - str(obj), 4366 - "Where(body=Binop(op=<BinopKind.ADD: 1>, left=Var(name='a'), " 4367 - "right=Var(name='b')), binding=Assign(name=Var(name='a'), value=Int(value=1)))", 4368 - ) 4398 + self.assertEqual(pretty(obj), "a + b . a = 1") 4369 4399 4370 4400 def test_pretty_print_assert(self) -> None: 4371 4401 obj = Assert(Int(123), Variant("true", String("foo"))) 4372 - self.assertEqual(str(obj), "Assert(value=Int(value=123), cond=Variant(tag='true', value=String(value='foo')))") 4402 + self.assertEqual(pretty(obj), '123 ! #true "foo"') 4373 4403 4374 4404 def test_pretty_print_envobject(self) -> None: 4375 4405 obj = EnvObject({"x": Int(1)}) 4376 - self.assertEqual(str(obj), "EnvObject(keys=dict_keys(['x']))") 4377 - 4378 - def test_pretty_print_matchcase(self) -> None: 4379 - obj = MatchCase(pattern=Int(1), body=Int(2)) 4380 - self.assertEqual(str(obj), "MatchCase(pattern=Int(value=1), body=Int(value=2))") 4406 + self.assertEqual(pretty(obj), "EnvObject({'x': Int(value=1)})") 4381 4407 4382 4408 def test_pretty_print_matchfunction(self) -> None: 4383 4409 obj = MatchFunction([MatchCase(Var("y"), Var("x"))]) 4384 - self.assertEqual(str(obj), "MatchFunction(cases=[MatchCase(pattern=Var(name='y'), body=Var(name='x'))])") 4410 + self.assertEqual(pretty(obj), "| y -> x") 4411 + 4412 + def test_pretty_print_matchfunction_precedence(self) -> None: 4413 + obj = MatchFunction( 4414 + [ 4415 + MatchCase(Var("a"), MatchFunction([MatchCase(Var("b"), Var("c"))])), 4416 + MatchCase(Var("x"), MatchFunction([MatchCase(Var("y"), Var("z"))])), 4417 + ] 4418 + ) 4419 + self.assertEqual( 4420 + pretty(obj), 4421 + """\ 4422 + | a -> (| b -> c) 4423 + | x -> (| y -> z)""", 4424 + ) 4385 4425 4386 4426 def test_pretty_print_relocation(self) -> None: 4387 4427 obj = Relocation("relocate") 4388 - self.assertEqual(str(obj), "Relocation(name='relocate')") 4428 + self.assertEqual(pretty(obj), "Relocation(name='relocate')") 4389 4429 4390 4430 def test_pretty_print_nativefunction(self) -> None: 4391 4431 obj = NativeFunction("times2", lambda x: Int(x.value * 2)) # type: ignore [attr-defined] 4392 - self.assertEqual(str(obj), "NativeFunction(name=times2)") 4432 + self.assertEqual(pretty(obj), "NativeFunction(name=times2)") 4393 4433 4394 4434 def test_pretty_print_closure(self) -> None: 4395 4435 obj = Closure({"a": Int(123)}, Function(Var("x"), Var("x"))) 4396 - self.assertEqual( 4397 - str(obj), "Closure(env={'a': Int(value=123)}, func=Function(arg=Var(name='x'), body=Var(name='x')))" 4398 - ) 4436 + self.assertEqual(pretty(obj), "Closure(['a'], x -> x)") 4399 4437 4400 4438 def test_pretty_print_record(self) -> None: 4401 4439 obj = Record({"a": Int(1), "b": Int(2)}) 4402 - self.assertEqual(str(obj), "{a = 1, b = 2}") 4440 + self.assertEqual(pretty(obj), "{a = 1, b = 2}") 4403 4441 4404 4442 def test_pretty_print_access(self) -> None: 4405 4443 obj = Access(Record({"a": Int(4)}), Var("a")) 4406 - self.assertEqual(str(obj), "Access(obj=Record(data={'a': Int(value=4)}), at=Var(name='a'))") 4444 + self.assertEqual(pretty(obj), "{a = 4} @ a") 4407 4445 4408 4446 def test_pretty_print_variant(self) -> None: 4409 4447 obj = Variant("x", Int(123)) 4410 - self.assertEqual(str(obj), "#x 123") 4448 + self.assertEqual(pretty(obj), "#x 123") 4449 + 4450 + obj = Variant("x", Function(Var("a"), Var("b"))) 4451 + self.assertEqual(pretty(obj), "#x (a -> b)") 4411 4452 4412 4453 4413 4454 def fetch(url: Object) -> Object: ··· 4585 4626 self.env.update(result.env) 4586 4627 else: 4587 4628 self.env["_"] = result 4588 - print(result) 4629 + print(pretty(result)) 4589 4630 except UnexpectedEOFError: 4590 4631 # Need to read more text 4591 4632 return True ··· 4606 4647 ast = parse(tokens) 4607 4648 logger.debug("AST: %s", ast) 4608 4649 result = eval_exp(boot_env(), ast) 4609 - print(result) 4650 + print(pretty(result)) 4610 4651 4611 4652 4612 4653 def apply_command(args: argparse.Namespace) -> None: ··· 4618 4659 ast = parse(tokens) 4619 4660 logger.debug("AST: %s", ast) 4620 4661 result = eval_exp(boot_env(), ast) 4621 - print(result) 4662 + print(pretty(result)) 4622 4663 4623 4664 4624 4665 def repl_command(args: argparse.Namespace) -> None: