From af60461e5536c1f1ba544226d1b9c677b94d829d Mon Sep 17 00:00:00 2001 From: gf712 Date: Sat, 27 Jun 2026 18:32:19 +0100 Subject: [PATCH] runtime: use PyArgsParser rather than manual parsing --- integration/tests/attributes.py | 23 +- integration/tests/bytes.py | 43 ++- integration/tests/classes.py | 57 ++++ integration/tests/dict.py | 44 +++ integration/tests/eval.py | 16 ++ integration/tests/function.py | 19 +- integration/tests/imp.py | 26 ++ integration/tests/inheritance.py | 30 ++ integration/tests/integer.py | 29 ++ integration/tests/list.py | 28 ++ integration/tests/logical.py | 22 ++ integration/tests/module.py | 28 ++ integration/tests/number.py | 36 +++ integration/tests/posix.py | 35 +++ integration/tests/range.py | 26 +- integration/tests/reversed.py | 8 + integration/tests/set.py | 30 +- integration/tests/slice.py | 23 ++ integration/tests/string.py | 134 +++++++++ src/runtime/PyBool.cpp | 18 +- src/runtime/PyByteArray.cpp | 35 +-- src/runtime/PyBytes.cpp | 50 ++-- src/runtime/PyClassMethod.cpp | 14 +- src/runtime/PyDict.cpp | 84 +++--- src/runtime/PyFloat.cpp | 18 +- src/runtime/PyFrozenSet.cpp | 71 +++-- src/runtime/PyInteger.cpp | 85 +++--- src/runtime/PyList.cpp | 15 +- src/runtime/PyModule.cpp | 35 ++- src/runtime/PyProperty.cpp | 48 ++-- src/runtime/PyRange.cpp | 98 +++---- src/runtime/PyReversed.cpp | 13 +- src/runtime/PySet.cpp | 24 +- src/runtime/PySlice.cpp | 41 ++- src/runtime/PyStaticMethod.cpp | 14 +- src/runtime/PyString.cpp | 302 ++++++++------------ src/runtime/PyType.cpp | 16 +- src/runtime/modules/BuiltinsModule.cpp | 364 +++++++++++-------------- src/runtime/modules/IOModule.cpp | 31 ++- src/runtime/modules/ImpModule.cpp | 122 +++++---- src/runtime/modules/PosixModule.cpp | 83 +++--- 41 files changed, 1374 insertions(+), 864 deletions(-) create mode 100644 integration/tests/imp.py create mode 100644 integration/tests/module.py create mode 100644 integration/tests/posix.py create mode 100644 integration/tests/slice.py diff --git a/integration/tests/attributes.py b/integration/tests/attributes.py index c2ce74ff..9ac1e655 100644 --- a/integration/tests/attributes.py +++ b/integration/tests/attributes.py @@ -7,4 +7,25 @@ def __init__(self): a = A() print("a.a", a.a) a.a += 1 -assert a.a == 2, "Failed to store attribute after inplace addition" \ No newline at end of file +assert a.a == 2, "Failed to store attribute after inplace addition" + +def attribute_builtins(): + class B: + pass + + b = B() + setattr(b, "x", 5) + assert getattr(b, "x") == 5, "setattr/getattr round-trip failed" + assert hasattr(b, "x"), "hasattr should find a set attribute" + assert not hasattr(b, "missing"), "hasattr should be False for a missing attribute" + assert getattr(b, "missing", 42) == 42, "getattr should return the default for a missing attribute" + + for builtin, arg_count in [(hasattr, 2), (setattr, 3)]: + try: + builtin(b) + except TypeError: + assert True + else: + assert False, "Expected attribute builtin to raise TypeError on wrong arity" + +attribute_builtins() \ No newline at end of file diff --git a/integration/tests/bytes.py b/integration/tests/bytes.py index 327bbce8..260de993 100644 --- a/integration/tests/bytes.py +++ b/integration/tests/bytes.py @@ -16,4 +16,45 @@ def bytes_translate(): result = bytearray(b'read this short text').translate(None, b'aeiou') assert result == bytearray(b'rd ths shrt txt') -bytes_translate() \ No newline at end of file +bytes_translate() + +def bytearray_find(): + a = bytearray(b'hello') + assert a.find(ord('l')) == 2, "bytearray.find should return the first matching index" + assert a.find(ord('l'), 3) == 3, "bytearray.find should honour the start argument" + + try: + a.find(b'l') + except TypeError: + assert True + else: + assert False, "Expected bytearray.find to raise TypeError when the pattern is not an int" + + try: + a.find() + except TypeError: + assert True + else: + assert False, "Expected bytearray.find to raise TypeError when called with no arguments" + +bytearray_find() + +def bytes_decode(): + assert b'hello'.decode() == 'hello', "bytes.decode() should default to utf-8" + assert b'hello'.decode('utf-8') == 'hello', "bytes.decode('utf-8') failed" + + try: + b'hello'.decode(1) + except TypeError: + assert True + else: + assert False, "Expected bytes.decode to raise TypeError when encoding is not a string" + + try: + b'hello'.decode('utf-8', 'strict', 'extra') + except TypeError: + assert True + else: + assert False, "Expected bytes.decode to raise TypeError when given too many arguments" + +bytes_decode() \ No newline at end of file diff --git a/integration/tests/classes.py b/integration/tests/classes.py index 60e71cac..48a7e610 100644 --- a/integration/tests/classes.py +++ b/integration/tests/classes.py @@ -37,6 +37,16 @@ class C: c = C() assert c.a() == foo() +def staticmethod_arity(): + try: + staticmethod() + except TypeError: + assert True + else: + assert False, "Expected staticmethod() with no arguments to raise TypeError" + +staticmethod_arity() + class A: def __init__(self, a): self._a = a @@ -52,6 +62,16 @@ def a(self): assert A(10).a == 20 assert A.new(10).a == 20 +def classmethod_arity(): + try: + classmethod() + except TypeError: + assert True + else: + assert False, "Expected classmethod() with no arguments to raise TypeError" + +classmethod_arity() + class D: def test(self): return __class__ == D @@ -78,3 +98,40 @@ def value(self): assert False class_closure() + +def property_accessors(): + class C: + @property + def x(self): + return self._x + + @x.setter + def x(self, value): + self._x = value + + c = C() + c.x = 42 + assert c.x == 42, "property getter/setter round-trip failed" + + try: + C.x.getter() + except TypeError: + assert True + else: + assert False, "Expected property.getter() with no arguments to raise TypeError" + +property_accessors() + +def type_three_arg(): + Foo = type("Foo", (), {}) + assert Foo.__name__ == "Foo", "type() should set the class name" + assert isinstance(Foo(), Foo), "type()-created class should be instantiable" + + try: + type("Bad", "notatuple", {}) + except TypeError: + assert True + else: + assert False, "Expected type() with non-tuple bases to raise TypeError" + +type_three_arg() diff --git a/integration/tests/dict.py b/integration/tests/dict.py index 5704fdd6..a8a36431 100644 --- a/integration/tests/dict.py +++ b/integration/tests/dict.py @@ -5,6 +5,16 @@ assert a.get("3") == None assert a.get("3", 3) == 3 +def dict_get_arity(): + try: + a.get() + except TypeError: + assert True + else: + assert False, "Expected dict.get to raise TypeError when called with no arguments" + +dict_get_arity() + assert a["1"] == 1 a["10"] = 10 assert a["10"] == 10 @@ -23,6 +33,13 @@ def dict_from_keys(): a = dict.fromkeys([1, 2, 3], "a") assert a == {1: "a", 2: "a", 3: "a"} + try: + dict.fromkeys() + except TypeError: + assert True + else: + assert False, "Expected dict.fromkeys to raise TypeError when called with no arguments" + dict_from_keys() def dict_from_map(): @@ -68,3 +85,30 @@ def __repr__(self): assert raised is not None, "dict.pop on missing key with failing __repr__ must not abort" dict_pop_missing_key_with_failing_repr() + +def dict_pop_arity(): + d = {"a": 1} + assert d.pop("a", 99) == 1, "dict.pop should return the value for an existing key" + assert d.pop("a", 99) == 99, "dict.pop should return the default for a missing key" + try: + d.pop() + except TypeError: + assert True + else: + assert False, "Expected dict.pop to raise TypeError when called with no arguments" + +dict_pop_arity() + +def dict_update_method(): + d = {"a": 1} + d.update({"b": 2}) + assert d["a"] == 1, "dict.update should keep existing keys" + assert d["b"] == 2, "dict.update should add new keys" + try: + d.update() + except TypeError: + assert True + else: + assert False, "Expected dict.update to raise TypeError when called with no arguments" + +dict_update_method() diff --git a/integration/tests/eval.py b/integration/tests/eval.py index 20bda328..06630062 100644 --- a/integration/tests/eval.py +++ b/integration/tests/eval.py @@ -17,3 +17,19 @@ def invalid_eval(): else: assert False, "Wrong exception" invalid_eval() + +def exec_arity(): + try: + exec() + except TypeError: + assert True + else: + assert False, "Expected exec() with no arguments to raise TypeError" + + try: + exec(1, 2, 3, 4) + except TypeError: + assert True + else: + assert False, "Expected exec() with too many arguments to raise TypeError" +exec_arity() diff --git a/integration/tests/function.py b/integration/tests/function.py index 5676a499..8a2c7a70 100644 --- a/integration/tests/function.py +++ b/integration/tests/function.py @@ -56,4 +56,21 @@ def bar(): return 42 assert bar() == 21 -assert bar == new_bar \ No newline at end of file +assert bar == new_bar + +def misc_builtin_arity(): + it = iter([1, 2, 3]) + assert next(it) == 1, "iter/next should yield the first element" + assert hash(1) == hash(1), "hash should be stable" + assert callable(len), "len should be callable" + assert not callable(5), "an int should not be callable" + + for fn in [iter, hash, next, callable]: + try: + fn() + except TypeError: + assert True + else: + assert False, "Expected a 1-argument builtin to raise TypeError with no arguments" + +misc_builtin_arity() \ No newline at end of file diff --git a/integration/tests/imp.py b/integration/tests/imp.py new file mode 100644 index 00000000..d281c29c --- /dev/null +++ b/integration/tests/imp.py @@ -0,0 +1,26 @@ +import _imp + +def imp_query_tests(): + assert _imp.is_builtin("sys") == True, "sys should be reported as a builtin module" + assert _imp.is_builtin("definitely_not_a_module") == False, "unknown module is not builtin" + assert _imp.is_frozen("definitely_not_a_module") == False, "unknown module is not frozen" + +imp_query_tests() + +def imp_arity_tests(): + for fn in [_imp.is_builtin, _imp.is_frozen, _imp.create_builtin, _imp.exec_builtin]: + try: + fn() + except TypeError: + assert True + else: + assert False, "Expected _imp function to raise TypeError with no arguments" + + try: + _imp.is_frozen(123) + except TypeError: + assert True + else: + assert False, "Expected _imp.is_frozen with a non-string name to raise TypeError" + +imp_arity_tests() diff --git a/integration/tests/inheritance.py b/integration/tests/inheritance.py index aa82c403..5bc88d67 100644 --- a/integration/tests/inheritance.py +++ b/integration/tests/inheritance.py @@ -43,3 +43,33 @@ def bar(self): assert Base.mro() == [Base, object] assert Derived.mro() == [Derived, Base, Base1, object] assert Derived.__bases__ == (Base, Base1) + +def predicate_builtin_arity(): + assert all([True, True]) == True, "all of all-true should be True" + assert all([True, False]) == False, "all with a falsey element should be False" + assert any([False, True]) == True, "any with a truthy element should be True" + assert any([False, False]) == False, "any of all-false should be False" + + try: + isinstance(1) + except TypeError: + assert True + else: + assert False, "Expected isinstance with one argument to raise TypeError" + + try: + issubclass(Derived) + except TypeError: + assert True + else: + assert False, "Expected issubclass with one argument to raise TypeError" + + for fn in [all, any]: + try: + fn([], []) + except TypeError: + assert True + else: + assert False, "Expected a 1-argument builtin to raise TypeError with too many arguments" + +predicate_builtin_arity() diff --git a/integration/tests/integer.py b/integration/tests/integer.py index 86d581f6..2e64e9a1 100644 --- a/integration/tests/integer.py +++ b/integration/tests/integer.py @@ -10,12 +10,26 @@ def to_bytes_test(): finally: assert raise_error, "should raise an error when converting an int that is too large for the given bytes" + try: + (5).to_bytes(2) + except TypeError: + assert True + else: + assert False, "Expected to_bytes with too few arguments to raise TypeError" + to_bytes_test() def from_bytes_test(): assert int.from_bytes(b"10", "little") == 12337 assert int.from_bytes(b"10", "big") == 12592 + try: + int.from_bytes(b"10") + except TypeError: + assert True + else: + assert False, "Expected from_bytes with too few arguments to raise TypeError" + from_bytes_test() def big_int_addition(): @@ -25,3 +39,18 @@ def big_int_addition(): assert c == 80235802358023580235 big_int_addition() + +def int_constructor(): + assert int() == 0, "int() should be 0" + assert int(3.7) == 3, "int(3.7) should truncate to 3" + assert int("10") == 10, "int('10') should be 10" + assert int("ff", 16) == 255, "int('ff', 16) should be 255" + + try: + int(1, 2, 3) + except TypeError: + assert True + else: + assert False, "Expected int() with too many arguments to raise TypeError" + +int_constructor() diff --git a/integration/tests/list.py b/integration/tests/list.py index bb52bdfd..dcbeb4ab 100644 --- a/integration/tests/list.py +++ b/integration/tests/list.py @@ -33,3 +33,31 @@ def list_recursive_repr(): assert repr(a) == "[[...]]", "recursive list repr should be idempotent" list_recursive_repr() + +def len_arity(): + assert len([1, 2, 3]) == 3, "len of a list failed" + try: + len() + except TypeError: + assert True + else: + assert False, "Expected len() with no arguments to raise TypeError" + try: + len([], []) + except TypeError: + assert True + else: + assert False, "Expected len() with too many arguments to raise TypeError" + +len_arity() + +def list_class_getitem(): + assert str(list[int]) == "list[int]", "list[int] generic alias failed" + try: + list.__class_getitem__() + except TypeError: + assert True + else: + assert False, "Expected list.__class_getitem__ to raise TypeError with no arguments" + +list_class_getitem() diff --git a/integration/tests/logical.py b/integration/tests/logical.py index 79b43941..72439809 100644 --- a/integration/tests/logical.py +++ b/integration/tests/logical.py @@ -14,3 +14,25 @@ assert a is not b, "True should not be False" assert (a is b) is False, "True is False should be false" + +assert bool(1) is True, "bool(1) should be True" +assert bool(0) is False, "bool(0) should be False" +assert bool([]) is False, "bool of an empty list should be False" +assert bool([1]) is True, "bool of a non-empty list should be True" + +def bool_arity(): + try: + bool() + except TypeError: + assert True + else: + assert False, "Expected bool() with no arguments to raise TypeError" + + try: + bool(1, 2) + except TypeError: + assert True + else: + assert False, "Expected bool() with too many arguments to raise TypeError" + +bool_arity() diff --git a/integration/tests/module.py b/integration/tests/module.py new file mode 100644 index 00000000..c2e6270b --- /dev/null +++ b/integration/tests/module.py @@ -0,0 +1,28 @@ +ModuleType = type(__import__("sys")) + +def module_construction(): + m = ModuleType("mymod", "docs") + assert m.__name__ == "mymod", "module name should be set" + assert m.__doc__ == "docs", "module doc should be set" + + m2 = ModuleType("noname") + assert m2.__name__ == "noname", "module should be constructible without a doc" + +module_construction() + +def module_errors(): + try: + ModuleType() + except TypeError: + assert True + else: + assert False, "Expected module() with no arguments to raise TypeError" + + try: + ModuleType(123) + except TypeError: + assert True + else: + assert False, "Expected module() with a non-string name to raise TypeError" + +module_errors() diff --git a/integration/tests/number.py b/integration/tests/number.py index d5b04989..5e194cf0 100644 --- a/integration/tests/number.py +++ b/integration/tests/number.py @@ -27,3 +27,39 @@ def add(a, b): assert 0xDEADBEEF == 3735928559, "Failed to create a number from hex" assert 0o125 == 85, "Failed to create a number from octal" assert 0b01110001 == 113, "Failed to create a number from binary" + +assert float() == 0.0, "float() with no arguments should be 0.0" +assert float(3) == 3.0, "float(3) should be 3.0" +assert float(2.5) == 2.5, "float(2.5) should be 2.5" + +def float_arity(): + try: + float(1, 2) + except TypeError: + assert True + else: + assert False, "Expected float() with too many arguments to raise TypeError" + +float_arity() + +def conversion_builtin_arity(): + assert ord("a") == 97, "ord('a') should be 97" + assert chr(97) == "a", "chr(97) should be 'a'" + assert repr(5) == "5", "repr(5) should be '5'" + assert abs(5) == 5, "abs(5) should be 5" + + for fn in [ord, chr, hex, repr, abs]: + try: + fn() + except TypeError: + assert True + else: + assert False, "Expected a 1-argument builtin to raise TypeError with no arguments" + try: + fn(1, 2) + except TypeError: + assert True + else: + assert False, "Expected a 1-argument builtin to raise TypeError with too many arguments" + +conversion_builtin_arity() diff --git a/integration/tests/posix.py b/integration/tests/posix.py new file mode 100644 index 00000000..25900685 --- /dev/null +++ b/integration/tests/posix.py @@ -0,0 +1,35 @@ +import posix + +def fspath_tests(): + assert posix.fspath("/tmp/foo") == "/tmp/foo", "fspath should return the string path unchanged" + + try: + posix.fspath() + except TypeError: + assert True + else: + assert False, "Expected posix.fspath() with no arguments to raise TypeError" + + try: + posix.fspath(123) + except TypeError: + assert True + else: + assert False, "Expected posix.fspath() with a non-path argument to raise TypeError" + +fspath_tests() + +def listdir_tests(): + # listdir() defaults to the current directory and must not raise. + entries = posix.listdir() + assert len(entries) >= 0, "listdir() should return a list" + assert posix.listdir(".") == entries, "listdir('.') should match listdir()" + + try: + posix.listdir(".", ".") + except TypeError: + assert True + else: + assert False, "Expected posix.listdir() with too many arguments to raise TypeError" + +listdir_tests() diff --git a/integration/tests/range.py b/integration/tests/range.py index af95640c..3e4b8fd8 100644 --- a/integration/tests/range.py +++ b/integration/tests/range.py @@ -34,4 +34,28 @@ def test_subscript(): r = range(0, 20, 2) assert r[5] == 10 -test_subscript() \ No newline at end of file +test_subscript() + +def test_range_errors(): + try: + range() + except TypeError: + assert True + else: + assert False, "Expected range() with no arguments to raise TypeError" + + try: + range("a") + except TypeError: + assert True + else: + assert False, "Expected range with a non-integer argument to raise TypeError" + + try: + range(1, 2, 3, 4) + except TypeError: + assert True + else: + assert False, "Expected range with too many arguments to raise TypeError" + +test_range_errors() \ No newline at end of file diff --git a/integration/tests/reversed.py b/integration/tests/reversed.py index b4b4f271..d8ea7b84 100644 --- a/integration/tests/reversed.py +++ b/integration/tests/reversed.py @@ -9,3 +9,11 @@ raises_stop_iteration = True finally: assert raises_stop_iteration + +try: + reversed() +except TypeError: + raised_type_error = True +else: + raised_type_error = False +assert raised_type_error, "Expected reversed() with no arguments to raise TypeError" diff --git a/integration/tests/set.py b/integration/tests/set.py index 9037cd1a..ab026e77 100644 --- a/integration/tests/set.py +++ b/integration/tests/set.py @@ -74,4 +74,32 @@ def set_union(): else: assert False -set_union() \ No newline at end of file +set_union() + +def frozenset_construction(): + assert len(frozenset()) == 0, "frozenset() should be empty" + assert len(frozenset([1, 2, 3, 2])) == 3, "frozenset should drop duplicates" + assert 2 in frozenset([1, 2, 3]), "frozenset should contain its elements" + + try: + frozenset([1], [2]) + except TypeError: + assert True + else: + assert False, "Expected frozenset with too many arguments to raise TypeError" + +frozenset_construction() + +def set_construction(): + assert len(set()) == 0, "set() should be empty" + assert len(set([1, 2, 3, 2])) == 3, "set should drop duplicates" + assert 2 in set([1, 2, 3]), "set should contain its elements" + + try: + set([1], [2]) + except TypeError: + assert True + else: + assert False, "Expected set with too many arguments to raise TypeError" + +set_construction() \ No newline at end of file diff --git a/integration/tests/slice.py b/integration/tests/slice.py new file mode 100644 index 00000000..c9cbd48f --- /dev/null +++ b/integration/tests/slice.py @@ -0,0 +1,23 @@ +def slice_construction(): + assert str(slice(5)) == "slice(None, 5, None)", "slice(stop) failed" + assert str(slice(1, 10)) == "slice(1, 10, None)", "slice(start, stop) failed" + assert str(slice(1, 10, 2)) == "slice(1, 10, 2)", "slice(start, stop, step) failed" + +slice_construction() + +def slice_errors(): + try: + slice() + except TypeError: + assert True + else: + assert False, "Expected slice() with no arguments to raise TypeError" + + try: + slice(1, 2, 3, 4) + except TypeError: + assert True + else: + assert False, "Expected slice with too many arguments to raise TypeError" + +slice_errors() diff --git a/integration/tests/string.py b/integration/tests/string.py index 3af7efdd..56306fff 100644 --- a/integration/tests/string.py +++ b/integration/tests/string.py @@ -54,8 +54,44 @@ def string_find_tests(): assert str.find("foo123", "23", 2) == 4, "Failed to find '123' pattern in 'foo123' substring (start)" assert str.find("foo123", "23", 4) == 4, "Failed to find '123' pattern in 'foo123' substring (start and end)" + try: + str.find("foo", 1) + except TypeError: + assert True + else: + assert False, "Expected find to raise TypeError when the pattern is not a string" + + try: + str.find("foo") + except TypeError: + assert True + else: + assert False, "Expected find to raise TypeError when called with too few arguments" + string_find_tests() +def string_rfind_tests(): + assert str.rfind("foofoo", "foo") == 3, "Failed to rfind 'foo' in 'foofoo'" + assert str.rfind("foo", "o") == 2, "Failed to rfind 'o' in 'foo'" + assert str.rfind("abcabc", "bc") == 4, "Failed to rfind 'bc' in 'abcabc'" + assert str.rfind("foofoo", "foo", 1) == 3, "Failed to rfind 'foo' in 'foofoo' substring (start)" + + try: + str.rfind("foo", 1) + except TypeError: + assert True + else: + assert False, "Expected rfind to raise TypeError when the pattern is not a string" + + try: + str.rfind("foo") + except TypeError: + assert True + else: + assert False, "Expected rfind to raise TypeError when called with too few arguments" + +string_rfind_tests() + def string_count_tests(): assert str.count("aaa", "aa") == 1, "Failed to find pattern 'aa' once in 'aaa'" assert str.count("aaaa", "aa") == 2, "Failed to find pattern 'aa' twice in 'aaaa'" @@ -67,6 +103,20 @@ def string_count_tests(): assert str.count("foo123", "4") == 0, "Failed to find no occurences of pattern '4' in 'foo123'" + try: + str.count("foo", 1) + except TypeError: + assert True + else: + assert False, "Expected count to raise TypeError when the pattern is not a string" + + try: + str.count("foo") + except TypeError: + assert True + else: + assert False, "Expected count to raise TypeError when called with too few arguments" + string_count_tests() def string_endswith_tests(): @@ -75,6 +125,20 @@ def string_endswith_tests(): assert str.endswith("foo123", "123", 0, 3) == False, "'foo123' substring 'foo' should not end with '123'" assert str.endswith("foo123", "123", 3, 6), "'foo123' substring '123' should end with '123'" + try: + str.endswith("foo", 1) + except TypeError: + assert True + else: + assert False, "Expected endswith to raise TypeError when the suffix is not str or tuple" + + try: + str.endswith("foo") + except TypeError: + assert True + else: + assert False, "Expected endswith to raise TypeError when called with too few arguments" + string_endswith_tests() def string_startswith_tests(): @@ -87,12 +151,33 @@ def string_startswith_tests(): a = ("foo", "bar", "baz") assert "bazzzz".startswith(a), "bazzzz starts with foo, bar or baz" + try: + str.startswith("foo", 1) + except TypeError: + assert True + else: + assert False, "Expected startswith to raise TypeError when the prefix is not str or tuple" + + try: + str.startswith("foo") + except TypeError: + assert True + else: + assert False, "Expected startswith to raise TypeError when called with too few arguments" + string_startswith_tests() def string_join_tests(): assert str.join(".", ["www", "python", "org"]) == "www.python.org", "Failed to create string 'www.python.org' from join" assert str.join("", []) == "", "Failed to create an empty string from join with empty list" + try: + str.join(".") + except TypeError: + assert True + else: + assert False, "Expected join to raise TypeError when called with too few arguments" + string_join_tests() assert str.lower("AbCDeF \o/") == "abcdef \o/", "Failed to create lowercase version of 'AbCDeF \o/'" @@ -114,6 +199,20 @@ def string_rpartition_tests(): assert a_bar_partition[1] == "" assert a_bar_partition[2] == "foo.bar.baz" + try: + "foo".rpartition(1) + except TypeError: + assert True + else: + assert False, "Expected rpartition to raise TypeError when the separator is not a string" + + try: + "foo".rpartition() + except TypeError: + assert True + else: + assert False, "Expected rpartition to raise TypeError when called with too few arguments" + string_rpartition_tests() def test_string_truthyness_behaviour(): @@ -140,6 +239,13 @@ def test_rstrip(): b = a.rstrip("ipz") assert b == "mississ" + try: + "foo".rstrip(1) + except TypeError: + assert True + else: + assert False, "Expected rstrip to raise TypeError when chars is not a string" + test_rstrip() def test_strip(): @@ -149,6 +255,20 @@ def test_strip(): comment_string = '#....... Section 3.2.1 Issue #32 .......' assert comment_string.strip('.#! ') == 'Section 3.2.1 Issue #32' + try: + "foo".strip(1) + except TypeError: + assert True + else: + assert False, "Expected strip to raise TypeError when chars is not a string" + + try: + "foo".strip("a", "b") + except TypeError: + assert True + else: + assert False, "Expected strip to raise TypeError when called with too many arguments" + test_strip() def test_split(): @@ -160,6 +280,20 @@ def test_split(): assert '1 2 3'.split(None, 1) == ['1', '2 3'] assert ' 1 2 3 '.split() == ['1', '2', '3'] + try: + "1,2,3".split(1) + except TypeError: + assert True + else: + assert False, "Expected split to raise TypeError when the separator is not a string" + + try: + "1,2,3".split(",", "x") + except TypeError: + assert True + else: + assert False, "Expected split to raise TypeError when maxsplit is not an integer" + test_split() def test_literal_hex_string(): diff --git a/src/runtime/PyBool.cpp b/src/runtime/PyBool.cpp index 8ba0c37a..aa735520 100644 --- a/src/runtime/PyBool.cpp +++ b/src/runtime/PyBool.cpp @@ -1,4 +1,5 @@ #include "PyBool.hpp" +#include "PyArgParser.hpp" #include "PyString.hpp" #include "types/api.hpp" #include "types/builtin.hpp" @@ -32,18 +33,19 @@ bool PyBool::value() const PyResult PyBool::__new__(const PyType *type, PyTuple *args, PyDict *kwargs) { - ASSERT(!kwargs || kwargs->map().size() == 0); - ASSERT(args && args->size() == 1); ASSERT(type == types::bool_()); - const auto &value = PyObject::from(args->elements()[0]); + auto result = PyArgsParser::unpack_tuple(args, + kwargs, + "bool", + std::integral_constant{}, + std::integral_constant{}); + if (result.is_err()) return Err(result.unwrap_err()); + auto *value = std::get<0>(result.unwrap()); - if (value.is_err()) return value; + if (value->type() == types::bool_()) return Ok(value); - if (value.unwrap()->type() == types::bool_()) return value; - - return value.unwrap()->true_().and_then( - [](const auto &v) { return Ok(v ? py_true() : py_false()); }); + return value->true_().and_then([](const auto &v) { return Ok(v ? py_true() : py_false()); }); } PyResult PyBool::__repr__() const { return PyString::create(to_string()); } diff --git a/src/runtime/PyByteArray.cpp b/src/runtime/PyByteArray.cpp index abf56e7c..e404cf3c 100644 --- a/src/runtime/PyByteArray.cpp +++ b/src/runtime/PyByteArray.cpp @@ -1,5 +1,6 @@ #include "PyByteArray.hpp" #include "MemoryError.hpp" +#include "PyArgParser.hpp" #include "PyBytes.hpp" #include "StopIteration.hpp" #include "runtime/IndexError.hpp" @@ -289,33 +290,19 @@ PyResult PyByteArray::__add__(const PyObject *other) const PyResult PyByteArray::find(PyTuple *args, PyDict *kwargs) const { - ASSERT(args && args->size() <= 3 && args->size() > 0); - ASSERT(!kwargs); - - auto pattern_ = PyObject::from(args->elements()[0]); - if (pattern_.is_err()) return pattern_; - if (!pattern_.unwrap()->type()->issubclass(types::integer())) { TODO(); } - const auto &pattern_int = static_cast(*pattern_.unwrap()); + auto parse_result = PyArgsParser::unpack_tuple(args, + kwargs, + "find", + std::integral_constant{}, + std::integral_constant{}, + nullptr /* start */, + nullptr /* end */); + if (parse_result.is_err()) return Err(parse_result.unwrap_err()); + auto [pattern, start, end] = parse_result.unwrap(); + const auto &pattern_int = *pattern; if (pattern_int.as_big_int() < 0 && pattern_int.as_big_int() > 255) { TODO(); } - PyInteger *start = nullptr; - PyInteger *end = nullptr; int64_t result = -1; - if (args->size() >= 2) { - auto start_ = PyObject::from(args->elements()[1]); - if (start_.is_err()) return start_; - start = as(start_.unwrap()); - // TODO: raise exception when start in not a number - ASSERT(start); - } - if (args->size() == 3) { - auto end_ = PyObject::from(args->elements()[2]); - if (end_.is_err()) return end_; - end = as(end_.unwrap()); - // TODO: raise exception when end in not a number - ASSERT(end); - } - auto get_position_from_slice = [this](int64_t pos) -> PyResult { if (pos < 0) { pos += m_value.b.size(); diff --git a/src/runtime/PyBytes.cpp b/src/runtime/PyBytes.cpp index b18b4d2e..edb14aa3 100644 --- a/src/runtime/PyBytes.cpp +++ b/src/runtime/PyBytes.cpp @@ -1,5 +1,6 @@ #include "PyBytes.hpp" #include "MemoryError.hpp" +#include "PyArgParser.hpp" #include "PyBool.hpp" #include "StopIteration.hpp" #include "runtime/IndexError.hpp" @@ -260,35 +261,36 @@ namespace { return std::move(klass("bytes") .def("decode", [](PyBytes *obj, PyTuple *args, PyDict *kwargs) -> PyResult { + auto result = PyArgsParser::unpack_tuple(args, + kwargs, + "decode", + std::integral_constant{}, + std::integral_constant{}, + nullptr /* encoding */, + nullptr /* errors */); + if (result.is_err()) return Err(result.unwrap_err()); + auto [encoding_obj, errors_obj] = result.unwrap(); + std::optional encoding; std::optional errors; - if (args) { - if (args->size() > 2) { - return Err(type_error( - "decode() takes at most 2 arguments ({} given)", args->size())); - } - if (args->size() > 0) { - auto arg0 = PyObject::from(args->elements()[0]).unwrap(); - if (auto enc = as(arg0)) { - encoding = enc->value(); - } else { - return Err(type_error( - "decode() argument 'encoding' must be str, not {}", - arg0->type()->to_string())); - } + if (encoding_obj) { + if (auto enc = as(encoding_obj)) { + encoding = enc->value(); + } else { + return Err( + type_error("decode() argument 'encoding' must be str, not {}", + encoding_obj->type()->to_string())); } - if (args->size() > 1) { - auto arg1 = PyObject::from(args->elements()[1]).unwrap(); - if (auto err = as(arg1)) { - errors = err->value(); - } else { - return Err( - type_error("decode() argument 'errors' must be str, not {}", - arg1->type()->to_string())); - } + } + if (errors_obj) { + if (auto err = as(errors_obj)) { + errors = err->value(); + } else { + return Err( + type_error("decode() argument 'errors' must be str, not {}", + errors_obj->type()->to_string())); } } - if (kwargs) { TODO(); } return obj->decode(encoding.value_or("utf-8"), errors.value_or("strict")); }) .type); diff --git a/src/runtime/PyClassMethod.cpp b/src/runtime/PyClassMethod.cpp index 34cbb8f1..22a21e0f 100644 --- a/src/runtime/PyClassMethod.cpp +++ b/src/runtime/PyClassMethod.cpp @@ -1,5 +1,6 @@ #include "PyClassMethod.hpp" #include "MemoryError.hpp" +#include "PyArgParser.hpp" #include "PyDict.hpp" #include "PyString.hpp" #include "PyTuple.hpp" @@ -32,12 +33,13 @@ PyResult PyClassMethod::__new__(const PyType *type, PyTuple *, PyDic PyResult PyClassMethod::__init__(PyTuple *args, PyDict *kwargs) { - ASSERT(args && args->size() == 1); - ASSERT(!kwargs || kwargs->map().empty()); - - auto callable = PyObject::from(args->elements()[0]); - if (callable.is_err()) return Err(callable.unwrap_err()); - m_callable = callable.unwrap(); + auto result = PyArgsParser::unpack_tuple(args, + kwargs, + "classmethod", + std::integral_constant{}, + std::integral_constant{}); + if (result.is_err()) return Err(result.unwrap_err()); + m_callable = std::get<0>(result.unwrap()); return Ok(0); } diff --git a/src/runtime/PyDict.cpp b/src/runtime/PyDict.cpp index 293fe619..2675e202 100644 --- a/src/runtime/PyDict.cpp +++ b/src/runtime/PyDict.cpp @@ -1,6 +1,7 @@ #include "PyDict.hpp" #include "KeyError.hpp" #include "MemoryError.hpp" +#include "PyArgParser.hpp" #include "PyBool.hpp" #include "PyNone.hpp" #include "PyString.hpp" @@ -427,45 +428,40 @@ namespace { return std::move(klass("dict") .def( "get", - +[](PyDict *self, PyTuple *args, PyDict *kwargs) { - ASSERT(args); - ASSERT(!kwargs || kwargs->size() == 0); - auto key_ = PyObject::from(args->elements()[0]); - if (key_.is_err()) return key_; - PyObject *key = key_.unwrap(); - PyObject *default_value = nullptr; - if (args->elements().size() == 2) { - auto default_value_ = PyObject::from(args->elements()[1]); - if (default_value_.is_err()) return default_value_; - default_value = default_value_.unwrap(); - } + +[](PyDict *self, PyTuple *args, PyDict *kwargs) -> PyResult { + auto result = PyArgsParser::unpack_tuple(args, + kwargs, + "get", + std::integral_constant{}, + std::integral_constant{}, + nullptr /* default */); + if (result.is_err()) return Err(result.unwrap_err()); + auto [key, default_value] = result.unwrap(); return self->get(key, default_value); }) .def( "pop", - +[](PyDict *self, PyTuple *args, PyDict *kwargs) { - ASSERT(args); - ASSERT(!kwargs || kwargs->size() == 0); - auto key_ = PyObject::from(args->elements()[0]); - if (key_.is_err()) return key_; - PyObject *key = key_.unwrap(); - PyObject *default_value = nullptr; - if (args->elements().size() == 2) { - auto default_value_ = PyObject::from(args->elements()[1]); - if (default_value_.is_err()) return default_value_; - default_value = default_value_.unwrap(); - } + +[](PyDict *self, PyTuple *args, PyDict *kwargs) -> PyResult { + auto result = PyArgsParser::unpack_tuple(args, + kwargs, + "pop", + std::integral_constant{}, + std::integral_constant{}, + nullptr /* default */); + if (result.is_err()) return Err(result.unwrap_err()); + auto [key, default_value] = result.unwrap(); return self->pop(key, default_value); }) .def( "update", +[](PyDict *self, PyTuple *args, PyDict *kwargs) -> PyResult { - ASSERT(args); - ASSERT(!kwargs || kwargs->size() == 0); - auto other_ = PyObject::from(args->elements()[0]); - if (other_.is_err()) return other_; - auto *other = other_.unwrap(); - return self->update(other); + auto result = PyArgsParser::unpack_tuple(args, + kwargs, + "update", + std::integral_constant{}, + std::integral_constant{}); + if (result.is_err()) return Err(result.unwrap_err()); + return self->update(std::get<0>(result.unwrap())); }) .def( "items", @@ -479,26 +475,14 @@ namespace { "fromkeys", +[](PyType *cls, PyTuple *args, PyDict *kwargs) -> PyResult { ASSERT(cls == types::dict()); - ASSERT(args && args->elements().size() > 0); - ASSERT(!kwargs || kwargs->map().size()); - - auto iterable_ = PyObject::from(args->elements()[0]); - if (iterable_.is_err()) return iterable_; - auto *iterable = iterable_.unwrap(); - - auto value_ = [args]() -> PyResult { - if (args->elements().size() == 2) { - return PyObject::from(args->elements()[1]); - } else if (args->elements().size() == 3) { - TODO(); - } else { - return Ok(nullptr); - } - }(); - - if (value_.is_err()) return value_; - auto *value = value_.unwrap(); - + auto result = PyArgsParser::unpack_tuple(args, + kwargs, + "fromkeys", + std::integral_constant{}, + std::integral_constant{}, + nullptr /* value */); + if (result.is_err()) return Err(result.unwrap_err()); + auto [iterable, value] = result.unwrap(); return PyDict::fromkeys(iterable, value); }) .type); diff --git a/src/runtime/PyFloat.cpp b/src/runtime/PyFloat.cpp index 8163395a..c8f925a5 100644 --- a/src/runtime/PyFloat.cpp +++ b/src/runtime/PyFloat.cpp @@ -1,5 +1,6 @@ #include "PyFloat.hpp" #include "MemoryError.hpp" +#include "runtime/PyArgParser.hpp" #include "runtime/PyNumber.hpp" #include "runtime/Value.hpp" #include "runtime/forward.hpp" @@ -32,15 +33,14 @@ PyResult PyFloat::__new__(const PyType *type, PyTuple *args, PyDict // TODO: support inheriting from float ASSERT(type == types::float_()); - ASSERT(!kwargs || kwargs->map().empty()); - PyObject *value = nullptr; - if (args->elements().size() > 0) { - if (auto obj = PyObject::from(args->elements()[0]); obj.is_ok()) { - value = obj.unwrap(); - } else { - return obj; - } - } + auto result = PyArgsParser::unpack_tuple(args, + kwargs, + "float", + std::integral_constant{}, + std::integral_constant{}, + nullptr); + if (result.is_err()) return Err(result.unwrap_err()); + auto *value = std::get<0>(result.unwrap()); if (!value) { return PyFloat::create(0.0); diff --git a/src/runtime/PyFrozenSet.cpp b/src/runtime/PyFrozenSet.cpp index 96b5f930..99cfad65 100644 --- a/src/runtime/PyFrozenSet.cpp +++ b/src/runtime/PyFrozenSet.cpp @@ -1,5 +1,6 @@ #include "PyFrozenSet.hpp" #include "MemoryError.hpp" +#include "PyArgParser.hpp" #include "PySet.hpp" #include "StopIteration.hpp" #include "types/api.hpp" @@ -73,54 +74,52 @@ std::string PyFrozenSet::to_string() const return os.str(); } -PyResult PyFrozenSet::__new__(const PyType *type, PyTuple *args, PyDict *) +PyResult PyFrozenSet::__new__(const PyType *type, PyTuple *args, PyDict *kwargs) { ASSERT(type == types::frozenset()); - ASSERT(args); - if (args->size() == 0) { - return PyFrozenSet::create(); - } else if (args->size() == 1) { - auto iterable_ = PyObject::from(args->elements()[0]); - if (iterable_.is_err()) return iterable_; - auto *iterable = iterable_.unwrap(); + auto result = PyArgsParser::unpack_tuple(args, + kwargs, + "frozenset", + std::integral_constant{}, + std::integral_constant{}, + nullptr); + if (result.is_err()) return Err(result.unwrap_err()); + auto *iterable = std::get<0>(result.unwrap()); - auto iterator_ = iterable->iter(); - if (iterator_.is_err()) return iterator_; - auto *iterator = iterator_.unwrap(); + if (!iterable) { return PyFrozenSet::create(); } - auto value_ = iterator->next(); + auto iterator_ = iterable->iter(); + if (iterator_.is_err()) return iterator_; + auto *iterator = iterator_.unwrap(); - SetType set; - while (!value_.is_err()) { - set.insert(value_.unwrap()); - value_ = iterator->next(); - } - - if (value_.unwrap_err()->type() != stop_iteration()->type()) { return value_; } + auto value_ = iterator->next(); - return PyFrozenSet::create(set); - } else { - return Err(type_error("frozenset expected at most 1 argument, got {}", args->size())); + SetType set; + while (!value_.is_err()) { + set.insert(value_.unwrap()); + value_ = iterator->next(); } + + if (value_.unwrap_err()->type() != stop_iteration()->type()) { return value_; } + + return PyFrozenSet::create(set); } PyResult PyFrozenSet::__init__(PyTuple *args, PyDict *kwargs) { - if (kwargs && !kwargs->map().empty()) { - return Err(type_error("frozenset() takes no keyword arguments")); - } - if (!args || args->elements().size() == 0) { return Ok(0); } - - if (args->elements().size() != 1) { - return Err( - type_error("frozenset expected at most 1 argument, got {}", args->elements().size())); - } - - auto iterable = PyObject::from(args->elements()[0]); - if (iterable.is_err()) return Err(iterable.unwrap_err()); - - auto iterator = iterable.unwrap()->iter(); + auto result = PyArgsParser::unpack_tuple(args, + kwargs, + "frozenset", + std::integral_constant{}, + std::integral_constant{}, + nullptr); + if (result.is_err()) return Err(result.unwrap_err()); + auto *iterable_obj = std::get<0>(result.unwrap()); + + if (!iterable_obj) { return Ok(0); } + + auto iterator = iterable_obj->iter(); if (iterator.is_err()) return Err(iterator.unwrap_err()); auto value = iterator.unwrap()->next(); diff --git a/src/runtime/PyInteger.cpp b/src/runtime/PyInteger.cpp index 0475652c..d1f3ec63 100644 --- a/src/runtime/PyInteger.cpp +++ b/src/runtime/PyInteger.cpp @@ -1,5 +1,6 @@ #include "PyInteger.hpp" #include "MemoryError.hpp" +#include "PyArgParser.hpp" #include "PyBytes.hpp" #include "PyFloat.hpp" #include "TypeError.hpp" @@ -70,19 +71,15 @@ PyResult PyInteger::create(PyType *type, BigIntType value) PyResult PyInteger::__new__(const PyType *type, PyTuple *args, PyDict *kwargs) { - ASSERT(!kwargs || kwargs->map().empty()); - PyObject *value = nullptr; - PyObject *base = nullptr; - if (!args) { return PyInteger::create(0); } - if (args->elements().size() > 0) { - if (auto obj = PyObject::from(args->elements()[0]); obj.is_ok()) { - value = obj.unwrap(); - } else { - return obj; - } - } - - if (args->elements().size() > 1) { base = PyObject::from(args->elements()[1]).unwrap(); } + auto result = PyArgsParser::unpack_tuple(args, + kwargs, + "int", + std::integral_constant{}, + std::integral_constant{}, + nullptr /* value */, + nullptr /* base */); + if (result.is_err()) return Err(result.unwrap_err()); + auto [value, base] = result.unwrap(); if (!value) { return PyInteger::create(const_cast(type), 0); @@ -214,29 +211,28 @@ PyResult PyInteger::bit_count() const PyResult PyInteger::to_bytes(PyTuple *args, PyDict *kwargs) const { // FIXME: fix signature to to_bytes(length, byteorder, *, signed=False) - ASSERT(!kwargs || kwargs->map().empty()); - - if (args->size() != 2) { return Err(type_error("to_bytes expected two arguments")); } - - auto length_ = PyObject::from(args->elements()[0]); - if (length_.is_err()) return length_; - auto byteorder_ = PyObject::from(args->elements()[1]); - if (byteorder_.is_err()) return byteorder_; - - if (!as(length_.unwrap())) { + auto parsed = PyArgsParser::unpack_tuple(args, + kwargs, + "to_bytes", + std::integral_constant{}, + std::integral_constant{}); + if (parsed.is_err()) return Err(parsed.unwrap_err()); + auto [length_obj, byteorder_obj] = parsed.unwrap(); + + if (!as(length_obj)) { return Err(type_error( - "'{}' object cannot be interpreted as an integer", length_.unwrap()->type()->name())); + "'{}' object cannot be interpreted as an integer", length_obj->type()->name())); } - if (!as(byteorder_.unwrap())) { - return Err(type_error("to_bytes() argument 'byteorder' must be str, not {}", - byteorder_.unwrap()->type()->name())); + if (!as(byteorder_obj)) { + return Err(type_error( + "to_bytes() argument 'byteorder' must be str, not {}", byteorder_obj->type()->name())); } - if (as(length_.unwrap())->as_i64() < 0) { + if (as(length_obj)->as_i64() < 0) { return Err(type_error("length argument must be non-negative")); } - const auto length = as(length_.unwrap())->as_size_t(); - const auto byteorder = as(byteorder_.unwrap())->value(); + const auto length = as(length_obj)->as_size_t(); + const auto byteorder = as(byteorder_obj)->value(); if (byteorder != "little" && byteorder != "big") { return Err(value_error("byteorder must be either 'little' or 'big'")); } @@ -267,28 +263,27 @@ PyResult PyInteger::to_bytes(PyTuple *args, PyDict *kwargs) const PyResult PyInteger::from_bytes(PyType *type, PyTuple *args, PyDict *kwargs) { // FIXME: fix signature to from_bytes(bytes, byteorder, *, signed=False) - ASSERT(!kwargs || kwargs->map().empty()); - - if (args->size() != 2) { return Err(type_error("from_bytes expected two arguments")); } - ASSERT(type == types::integer()); - auto bytes_ = PyObject::from(args->elements()[0]); - if (bytes_.is_err()) return bytes_; - auto byteorder_ = PyObject::from(args->elements()[1]); - if (byteorder_.is_err()) return byteorder_; + auto parsed = PyArgsParser::unpack_tuple(args, + kwargs, + "from_bytes", + std::integral_constant{}, + std::integral_constant{}); + if (parsed.is_err()) return Err(parsed.unwrap_err()); + auto [bytes_obj, byteorder_obj] = parsed.unwrap(); - if (!as(bytes_.unwrap())) { + if (!as(bytes_obj)) { return Err(type_error( - "'{}' object cannot be interpreted as a bytes array", bytes_.unwrap()->type()->name())); + "'{}' object cannot be interpreted as a bytes array", bytes_obj->type()->name())); } - if (!as(byteorder_.unwrap())) { - return Err(type_error("to_bytes() argument 'byteorder' must be str, not {}", - byteorder_.unwrap()->type()->name())); + if (!as(byteorder_obj)) { + return Err(type_error( + "to_bytes() argument 'byteorder' must be str, not {}", byteorder_obj->type()->name())); } - const auto bytes = as(bytes_.unwrap()); - const auto byteorder = as(byteorder_.unwrap())->value(); + const auto bytes = as(bytes_obj); + const auto byteorder = as(byteorder_obj)->value(); if (byteorder != "little" && byteorder != "big") { return Err(value_error("byteorder must be either 'little' or 'big'")); } diff --git a/src/runtime/PyList.cpp b/src/runtime/PyList.cpp index cf896630..8f776609 100644 --- a/src/runtime/PyList.cpp +++ b/src/runtime/PyList.cpp @@ -1,6 +1,7 @@ #include "PyList.hpp" #include "IndexError.hpp" #include "MemoryError.hpp" +#include "PyArgParser.hpp" #include "PyBool.hpp" #include "PyDict.hpp" #include "PyFunction.hpp" @@ -672,12 +673,14 @@ namespace { .def("sort", &PyList::sort) .classmethod( "__class_getitem__", - +[](PyType *type, PyTuple *args, PyDict *kwargs) { - ASSERT(args && args->elements().size() == 1); - ASSERT(!kwargs || kwargs->map().empty()); - return PyObject::from(args->elements()[0]).and_then([type](PyObject *arg) { - return PyGenericAlias::create(type, arg); - }); + +[](PyType *type, PyTuple *args, PyDict *kwargs) -> PyResult { + auto result = PyArgsParser::unpack_tuple(args, + kwargs, + "__class_getitem__", + std::integral_constant{}, + std::integral_constant{}); + if (result.is_err()) return Err(result.unwrap_err()); + return PyGenericAlias::create(type, std::get<0>(result.unwrap())); }) .def("__reversed__", &PyList::__reversed__) .def("insert", &PyList::insert) diff --git a/src/runtime/PyModule.cpp b/src/runtime/PyModule.cpp index 665cea98..3b291c00 100644 --- a/src/runtime/PyModule.cpp +++ b/src/runtime/PyModule.cpp @@ -1,5 +1,6 @@ #include "PyModule.hpp" #include "MemoryError.hpp" +#include "PyArgParser.hpp" #include "PyDict.hpp" #include "PyList.hpp" #include "PyString.hpp" @@ -52,30 +53,34 @@ PyResult PyModule::__repr__() const PyResult PyModule::__new__(const PyType *type, PyTuple *args, PyDict *kwargs) { ASSERT(type == types::module()); - ASSERT(!kwargs || kwargs->map().empty()); auto symbol_table = PyDict::create(); if (symbol_table.is_err()) return symbol_table; - auto *name = args->size() > 0 ? PyObject::from(args->elements()[0]).unwrap() : nullptr; - auto *doc = args->size() > 1 ? PyObject::from(args->elements()[1]).unwrap() : py_none(); - if (!name) { TODO(); } - if (!as(name)) { TODO(); } + auto result = PyArgsParser::unpack_tuple(args, + kwargs, + "module", + std::integral_constant{}, + std::integral_constant{}, + py_none() /* doc */); + if (result.is_err()) return Err(result.unwrap_err()); + auto [name, doc] = result.unwrap(); - return PyModule::create(symbol_table.unwrap(), as(name), doc); + return PyModule::create(symbol_table.unwrap(), name, doc); } PyResult PyModule::__init__(PyTuple *args, PyDict *kwargs) { - ASSERT(args); - ASSERT(!kwargs || kwargs->map().empty()); - - auto *name = args->size() > 0 ? PyObject::from(args->elements()[0]).unwrap() : nullptr; - auto *doc = args->size() > 1 ? PyObject::from(args->elements()[1]).unwrap() : py_none(); - if (!name) { TODO(); } - if (!as(name)) { TODO(); } - - m_module_name = as(name); + auto result = PyArgsParser::unpack_tuple(args, + kwargs, + "module", + std::integral_constant{}, + std::integral_constant{}, + py_none() /* doc */); + if (result.is_err()) return Err(result.unwrap_err()); + auto [name, doc] = result.unwrap(); + + m_module_name = name; m_doc = doc; auto attr = PyDict::create(); diff --git a/src/runtime/PyProperty.cpp b/src/runtime/PyProperty.cpp index b6c84ebf..78e0088f 100644 --- a/src/runtime/PyProperty.cpp +++ b/src/runtime/PyProperty.cpp @@ -1,5 +1,6 @@ #include "PyProperty.hpp" #include "AttributeError.hpp" +#include "PyArgParser.hpp" #include "PyDict.hpp" #include "PyFunction.hpp" #include "PyNone.hpp" @@ -89,15 +90,14 @@ PyResult PyResult PyProperty::getter(PyTuple *args, PyDict *kwargs) const { - ASSERT(!kwargs || kwargs->map().empty()); - ASSERT(args); - ASSERT(args->size() == 1); - - auto getter_ = PyObject::from(args->elements()[0]); - - if (getter_.is_err()) return getter_; - - return PyProperty::create(getter_.unwrap(), + auto result = PyArgsParser::unpack_tuple(args, + kwargs, + "getter", + std::integral_constant{}, + std::integral_constant{}); + if (result.is_err()) return Err(result.unwrap_err()); + + return PyProperty::create(std::get<0>(result.unwrap()), m_setter == py_none() ? py_none() : m_setter, m_deleter == py_none() ? py_none() : m_deleter, m_property_name); @@ -105,33 +105,31 @@ PyResult PyProperty::getter(PyTuple *args, PyDict *kwargs) const PyResult PyProperty::setter(PyTuple *args, PyDict *kwargs) const { - ASSERT(!kwargs || kwargs->map().empty()); - ASSERT(args); - ASSERT(args->size() == 1); - - auto setter_ = PyObject::from(args->elements()[0]); - - if (setter_.is_err()) return setter_; + auto result = PyArgsParser::unpack_tuple(args, + kwargs, + "setter", + std::integral_constant{}, + std::integral_constant{}); + if (result.is_err()) return Err(result.unwrap_err()); return PyProperty::create(m_getter == py_none() ? py_none() : m_getter, - setter_.unwrap(), + std::get<0>(result.unwrap()), m_deleter == py_none() ? py_none() : m_deleter, m_property_name); } PyResult PyProperty::deleter(PyTuple *args, PyDict *kwargs) const { - ASSERT(!kwargs || kwargs->map().empty()); - ASSERT(args); - ASSERT(args->size() == 1); - - auto deleter_ = PyObject::from(args->elements()[0]); - - if (deleter_.is_err()) return deleter_; + auto result = PyArgsParser::unpack_tuple(args, + kwargs, + "deleter", + std::integral_constant{}, + std::integral_constant{}); + if (result.is_err()) return Err(result.unwrap_err()); return PyProperty::create(m_setter == py_none() ? py_none() : m_setter, m_setter == py_none() ? py_none() : m_setter, - deleter_.unwrap(), + std::get<0>(result.unwrap()), m_property_name); } diff --git a/src/runtime/PyRange.cpp b/src/runtime/PyRange.cpp index bca836bf..986aa221 100644 --- a/src/runtime/PyRange.cpp +++ b/src/runtime/PyRange.cpp @@ -1,5 +1,6 @@ #include "PyRange.hpp" #include "MemoryError.hpp" +#include "PyArgParser.hpp" #include "PyDict.hpp" #include "PyInteger.hpp" #include "PyString.hpp" @@ -20,68 +21,47 @@ PyRange::PyRange(PyType *type) : PyBaseObject(type) {} PyResult PyRange::__new__(const PyType *type, PyTuple *args, PyDict *kwargs) { - ASSERT(!kwargs || kwargs->map().size() == 0); - ASSERT(args && args->size() > 0 && args->size() < 4); ASSERT(type == types::range()); - auto obj = [&]() -> std::variant> { - if (args->size() == 1) { - if (auto arg1 = PyObject::from(args->elements()[0]); arg1.is_ok()) { - auto stop = as(arg1.unwrap()); - if (!stop) { - return Err(type_error("'{}' object cannot be interpreted as an integer", - arg1.unwrap()->type()->name())); - } - return VirtualMachine::the().heap().allocate(stop); - } else { - return Err(arg1.unwrap_err()); - } - } else if (args->size() == 2) { - auto start_ = PyObject::from(args->elements()[0]); - if (start_.is_err()) return Err(start_.unwrap_err()); - auto *start = as(start_.unwrap()); - if (!start) { - return Err(type_error("'{}' object cannot be interpreted as an integer", - start_.unwrap()->type()->name())); - } - auto stop_ = PyObject::from(args->elements()[1]); - if (stop_.is_err()) return Err(stop_.unwrap_err()); - auto *stop = as(stop_.unwrap()); - if (!stop) { - return Err(type_error("'{}' object cannot be interpreted as an integer", - stop_.unwrap()->type()->name())); - } - return VirtualMachine::the().heap().allocate(start, stop); - } else if (args->size() == 3) { - auto start_ = PyObject::from(args->elements()[0]); - if (start_.is_err()) return Err(start_.unwrap_err()); - auto *start = as(start_.unwrap()); - if (!start) { - return Err(type_error("'{}' object cannot be interpreted as an integer", - start_.unwrap()->type()->name())); - } - auto stop_ = PyObject::from(args->elements()[1]); - if (stop_.is_err()) return Err(stop_.unwrap_err()); - auto *stop = as(stop_.unwrap()); - if (!stop) { - return Err(type_error("'{}' object cannot be interpreted as an integer", - stop_.unwrap()->type()->name())); - } - auto step_ = PyObject::from(args->elements()[2]); - if (step_.is_err()) return Err(step_.unwrap_err()); - auto *step = as(step_.unwrap()); - if (!step) { - return Err(type_error("'{}' object cannot be interpreted as an integer", - step_.unwrap()->type()->name())); - } - return VirtualMachine::the().heap().allocate(start, stop, step); - } - ASSERT_NOT_REACHED(); - }(); + auto result = PyArgsParser::unpack_tuple(args, + kwargs, + "range", + std::integral_constant{}, + std::integral_constant{}, + nullptr /* stop / start */, + nullptr /* step */); + if (result.is_err()) return Err(result.unwrap_err()); + auto [arg0, arg1, arg2] = result.unwrap(); + + auto as_index = [](PyObject *obj) -> PyResult { + if (auto *value = as(obj)) { return Ok(value); } + return Err( + type_error("'{}' object cannot be interpreted as an integer", obj->type()->name())); + }; - if (std::holds_alternative>(obj)) return std::get>(obj); - if (!std::get(obj)) { return Err(memory_error(sizeof(PyRange))); } - return Ok(std::get(obj)); + PyRange *range = nullptr; + if (!arg1) { + auto stop = as_index(arg0); + if (stop.is_err()) return Err(stop.unwrap_err()); + range = VirtualMachine::the().heap().allocate(stop.unwrap()); + } else if (!arg2) { + auto start = as_index(arg0); + if (start.is_err()) return Err(start.unwrap_err()); + auto stop = as_index(arg1); + if (stop.is_err()) return Err(stop.unwrap_err()); + range = VirtualMachine::the().heap().allocate(start.unwrap(), stop.unwrap()); + } else { + auto start = as_index(arg0); + if (start.is_err()) return Err(start.unwrap_err()); + auto stop = as_index(arg1); + if (stop.is_err()) return Err(stop.unwrap_err()); + auto step = as_index(arg2); + if (step.is_err()) return Err(step.unwrap_err()); + range = VirtualMachine::the().heap().allocate( + start.unwrap(), stop.unwrap(), step.unwrap()); + } + if (!range) { return Err(memory_error(sizeof(PyRange))); } + return Ok(range); } PyRange::PyRange(BigIntType start, BigIntType stop, BigIntType step) diff --git a/src/runtime/PyReversed.cpp b/src/runtime/PyReversed.cpp index fd902262..f6218e90 100644 --- a/src/runtime/PyReversed.cpp +++ b/src/runtime/PyReversed.cpp @@ -1,4 +1,5 @@ #include "PyReversed.hpp" +#include "PyArgParser.hpp" #include "types/api.hpp" #include "types/builtin.hpp" @@ -26,11 +27,13 @@ PyResult PyReversed::create(PyObject *sequence) PyResult PyReversed::__new__(const PyType *type, PyTuple *args, PyDict *kwargs) { ASSERT(type == types::reversed()); - ASSERT(!kwargs || kwargs->map().empty()); - ASSERT(args && args->size() == 1); - auto sequence = PyObject::from(args->elements()[0]); - if (sequence.is_err()) { return sequence; } - return PyReversed::create(sequence.unwrap()); + auto result = PyArgsParser::unpack_tuple(args, + kwargs, + "reversed", + std::integral_constant{}, + std::integral_constant{}); + if (result.is_err()) return Err(result.unwrap_err()); + return PyReversed::create(std::get<0>(result.unwrap())); } PyResult PyReversed::__iter__() const { TODO(); } diff --git a/src/runtime/PySet.cpp b/src/runtime/PySet.cpp index 048e4689..3a2a6ce3 100644 --- a/src/runtime/PySet.cpp +++ b/src/runtime/PySet.cpp @@ -1,6 +1,7 @@ #include "PySet.hpp" #include "KeyError.hpp" #include "MemoryError.hpp" +#include "PyArgParser.hpp" #include "PyBool.hpp" #include "PyDict.hpp" #include "PyFrozenSet.hpp" @@ -235,18 +236,17 @@ PyResult PySet::__new__(const PyType *type, PyTuple *, PyDict *) PyResult PySet::__init__(PyTuple *args, PyDict *kwargs) { - if (kwargs && !kwargs->map().empty()) { - return Err(type_error("set() takes no keyword arguments")); - } - if (!args || args->elements().size() == 0) { return Ok(0); } - - if (args->elements().size() != 1) { - return Err(type_error("set expected at most 1 argument, got {}", args->elements().size())); - } - - auto iterable = PyObject::from(args->elements()[0]); - if (iterable.is_err()) return Err(iterable.unwrap_err()); - return from_iterable(iterable.unwrap(), std::inserter(m_elements, m_elements.begin())) + auto result = PyArgsParser::unpack_tuple(args, + kwargs, + "set", + std::integral_constant{}, + std::integral_constant{}, + nullptr); + if (result.is_err()) return Err(result.unwrap_err()); + auto *iterable = std::get<0>(result.unwrap()); + + if (!iterable) { return Ok(0); } + return from_iterable(iterable, std::inserter(m_elements, m_elements.begin())) .and_then([](auto) { return Ok(0); }); } diff --git a/src/runtime/PySlice.cpp b/src/runtime/PySlice.cpp index 41f44f13..23bbaaff 100644 --- a/src/runtime/PySlice.cpp +++ b/src/runtime/PySlice.cpp @@ -1,5 +1,6 @@ #include "PySlice.hpp" #include "MemoryError.hpp" +#include "PyArgParser.hpp" #include "PyBool.hpp" #include "PyInteger.hpp" #include "PyNone.hpp" @@ -82,32 +83,24 @@ PyResult PySlice::__new__(const PyType *type, PyTuple *, PyDict *) return Err(memory_error(sizeof(PySlice))); } -PyResult PySlice::__init__(PyTuple *args, PyDict *) +PyResult PySlice::__init__(PyTuple *args, PyDict *kwargs) { - if (args->size() == 0) { return Err(type_error("slice expected at least 1 argument, got 0")); } - if (args->size() > 3) { - return Err(type_error("slice expected at most 3 arguments, got {}", args->size())); - } - - if (args->size() == 1) { - auto stop = PyObject::from(args->elements()[0]); - if (stop.is_err()) return Err(stop.unwrap_err()); - m_stop = stop.unwrap(); + auto result = PyArgsParser::unpack_tuple(args, + kwargs, + "slice", + std::integral_constant{}, + std::integral_constant{}, + nullptr /* stop / start */, + nullptr /* step */); + if (result.is_err()) return Err(result.unwrap_err()); + auto [arg0, arg1, arg2] = result.unwrap(); + + if (!arg1) { + m_stop = arg0; } else { - auto start = PyObject::from(args->elements()[0]); - if (start.is_err()) return Err(start.unwrap_err()); - auto stop = PyObject::from(args->elements()[1]); - if (stop.is_err()) return Err(stop.unwrap_err()); - auto step = [args]() -> PyResult { - if (args->size() > 2) { return PyObject::from(args->elements()[2]); } - return Ok(py_none()); - }(); - - if (step.is_err()) return Err(step.unwrap_err()); - - m_start = start.unwrap(); - m_stop = stop.unwrap(); - m_step = step.unwrap(); + m_start = arg0; + m_stop = arg1; + m_step = arg2 ? arg2 : py_none(); } return Ok(0); diff --git a/src/runtime/PyStaticMethod.cpp b/src/runtime/PyStaticMethod.cpp index 4fec175d..303dad0e 100644 --- a/src/runtime/PyStaticMethod.cpp +++ b/src/runtime/PyStaticMethod.cpp @@ -1,4 +1,5 @@ #include "PyStaticMethod.hpp" +#include "PyArgParser.hpp" #include "PyFunction.hpp" #include "PyString.hpp" #include "PyType.hpp" @@ -14,12 +15,13 @@ PyStaticMethod::PyStaticMethod(PyType *type) : PyBaseObject(type) {} PyResult PyStaticMethod::__new__(const PyType *, PyTuple *args, PyDict *kwargs) { - ASSERT(!kwargs || kwargs->map().empty()); - ASSERT(args && args->elements().size() == 1); - - return PyObject::from(args->elements()[0]).and_then([](PyObject *function) { - return PyStaticMethod::create(function); - }); + auto result = PyArgsParser::unpack_tuple(args, + kwargs, + "staticmethod", + std::integral_constant{}, + std::integral_constant{}); + if (result.is_err()) return Err(result.unwrap_err()); + return PyStaticMethod::create(std::get<0>(result.unwrap())); } void PyStaticMethod::visit_graph(Visitor &visitor) diff --git a/src/runtime/PyString.cpp b/src/runtime/PyString.cpp index 348e72d7..e932ae87 100644 --- a/src/runtime/PyString.cpp +++ b/src/runtime/PyString.cpp @@ -3,6 +3,7 @@ #include "KeyError.hpp" #include "MemoryError.hpp" #include "NotImplementedError.hpp" +#include "PyArgParser.hpp" #include "PyBool.hpp" #include "PyDict.hpp" #include "PyFloat.hpp" @@ -440,31 +441,17 @@ size_t PyString::get_position_from_slice(int64_t pos) const // FIXME: assumes string only has ASCII characters PyResult PyString::find(PyTuple *args, PyDict *kwargs) const { - ASSERT(args && args->size() <= 3 && args->size() > 0); - ASSERT(!kwargs); - - auto pattern_ = PyObject::from(args->elements()[0]); - if (pattern_.is_err()) return pattern_; - PyString *pattern = as(pattern_.unwrap()); - PyInteger *start = nullptr; - PyInteger *end = nullptr; + auto parse_result = PyArgsParser::unpack_tuple(args, + kwargs, + "find", + std::integral_constant{}, + std::integral_constant{}, + nullptr /* start */, + nullptr /* end */); + if (parse_result.is_err()) return Err(parse_result.unwrap_err()); + auto [pattern, start, end] = parse_result.unwrap(); size_t result{ std::string::npos }; - if (args->size() >= 2) { - auto start_ = PyObject::from(args->elements()[1]); - if (start_.is_err()) return start_; - start = as(start_.unwrap()); - // TODO: raise exception when start in not a number - ASSERT(start); - } - if (args->size() == 3) { - auto end_ = PyObject::from(args->elements()[2]); - if (end_.is_err()) return end_; - end = as(end_.unwrap()); - // TODO: raise exception when end in not a number - ASSERT(end); - } - if (!start && !end) { result = m_value.find(pattern->value().c_str()); } else if (!end) { @@ -515,29 +502,15 @@ PyResult PyString::find(PyTuple *args, PyDict *kwargs) const // FIXME: assumes string only has ASCII characters PyResult PyString::rfind(PyTuple *args, PyDict *kwargs) const { - ASSERT(args && args->size() <= 3 && args->size() > 0); - ASSERT(!kwargs); - - auto pattern_ = PyObject::from(args->elements()[0]); - if (pattern_.is_err()) return pattern_; - PyString *pattern = as(pattern_.unwrap()); - PyInteger *start = nullptr; - PyInteger *end = nullptr; - - if (args->size() >= 2) { - auto start_ = PyObject::from(args->elements()[1]); - if (start_.is_err()) return start_; - start = as(start_.unwrap()); - // TODO: raise exception when start in not a number - ASSERT(start); - } - if (args->size() == 3) { - auto end_ = PyObject::from(args->elements()[2]); - if (end_.is_err()) return end_; - end = as(end_.unwrap()); - // TODO: raise exception when end in not a number - ASSERT(end); - } + auto parse_result = PyArgsParser::unpack_tuple(args, + kwargs, + "rfind", + std::integral_constant{}, + std::integral_constant{}, + nullptr /* start */, + nullptr /* end */); + if (parse_result.is_err()) return Err(parse_result.unwrap_err()); + auto [pattern, start, end] = parse_result.unwrap(); size_t start_idx = 0; size_t end_idx = 0; @@ -597,31 +570,17 @@ PyResult PyString::rfind(PyTuple *args, PyDict *kwargs) const // FIXME: assumes string only has ASCII characters PyResult PyString::count(PyTuple *args, PyDict *kwargs) const { - ASSERT(args && args->size() <= 3 && args->size() > 0); - ASSERT(!kwargs); - - auto pattern_ = PyObject::from(args->elements()[0]); - if (pattern_.is_err()) return pattern_; - PyString *pattern = as(pattern_.unwrap()); - PyInteger *start = nullptr; - PyInteger *end = nullptr; + auto parse_result = PyArgsParser::unpack_tuple(args, + kwargs, + "count", + std::integral_constant{}, + std::integral_constant{}, + nullptr /* start */, + nullptr /* end */); + if (parse_result.is_err()) return Err(parse_result.unwrap_err()); + auto [pattern, start, end] = parse_result.unwrap(); size_t result{ 0 }; - if (args->size() >= 2) { - auto start_ = PyObject::from(args->elements()[1]); - if (start_.is_err()) return start_; - start = as(start_.unwrap()); - // TODO: raise exception when start in not a number - ASSERT(start); - } - if (args->size() == 3) { - auto end_ = PyObject::from(args->elements()[2]); - if (end_.is_err()) return end_; - end = as(end_.unwrap()); - // TODO: raise exception when end in not a number - ASSERT(end); - } - const size_t start_ = [start, this]() { if (start) { return std::visit(overloaded{ @@ -682,50 +641,36 @@ PyResult PyString::count(PyTuple *args, PyDict *kwargs) const // FIXME: assumes string only has ASCII characters PyResult PyString::startswith(PyTuple *args, PyDict *kwargs) const { - ASSERT(args && args->size() <= 3 && args->size() > 0); - ASSERT(!kwargs); - - auto prefix_ = PyObject::from(args->elements()[0]); - if (prefix_.is_err()) return prefix_; - - auto prefixes_ = [&prefix_]() -> PyResult> { - PyString *prefix = as(prefix_.unwrap()); - if (prefix) { return Ok(std::vector{ prefix->value() }); } - PyTuple *prefixes = as(prefix_.unwrap()); - if (prefixes) { + auto parse_result = PyArgsParser::unpack_tuple(args, + kwargs, + "startswith", + std::integral_constant{}, + std::integral_constant{}, + nullptr /* start */, + nullptr /* end */); + if (parse_result.is_err()) return Err(parse_result.unwrap_err()); + auto [prefix, start, end] = parse_result.unwrap(); + + auto prefixes_ = [prefix]() -> PyResult> { + if (auto *prefix_str = as(prefix)) { + return Ok(std::vector{ prefix_str->value() }); + } + if (auto *prefixes = as(prefix)) { std::vector els; els.reserve(prefixes->size()); for (const auto &el : prefixes->elements()) { auto obj = PyObject::from(el); if (obj.is_err()) return Err(obj.unwrap_err()); - PyString *prefix = as(obj.unwrap()); - if (!prefix) { return Err(type_error("expected tuple of objects of type str")); } - els.push_back(prefix->value()); + PyString *el_str = as(obj.unwrap()); + if (!el_str) { return Err(type_error("expected tuple of objects of type str")); } + els.push_back(el_str->value()); } return Ok(els); } return Err(type_error("startswith first arg must be str or a tuple of str, not '{}'", - prefix_.unwrap()->type()->name())); + prefix->type()->name())); }(); - PyInteger *start = nullptr; - PyInteger *end = nullptr; - - if (args->size() >= 2) { - auto start_ = PyObject::from(args->elements()[1]); - if (start_.is_err()) return start_; - start = as(start_.unwrap()); - // TODO: raise exception when start in not a number - ASSERT(start); - } - if (args->size() == 3) { - auto end_ = PyObject::from(args->elements()[2]); - if (end_.is_err()) return end_; - end = as(end_.unwrap()); - // TODO: raise exception when end in not a number - ASSERT(end); - } - if (prefixes_.is_err()) return Err(prefixes_.unwrap_err()); const auto &prefixes = prefixes_.unwrap(); @@ -782,26 +727,15 @@ PyResult PyString::startswith(PyTuple *args, PyDict *kwargs) const // FIXME: assumes string only has ASCII characters PyResult PyString::endswith(PyTuple *args, PyDict *kwargs) const { - ASSERT(args && args->size() <= 3 && args->size() > 0); - ASSERT(!kwargs); - - PyInteger *start = nullptr; - PyInteger *end = nullptr; - - if (args->size() >= 2) { - auto start_ = PyObject::from(args->elements()[1]); - if (start_.is_err()) return start_; - start = as(start_.unwrap()); - // TODO: raise exception when start in not a number - ASSERT(start); - } - if (args->size() == 3) { - auto end_ = PyObject::from(args->elements()[2]); - if (end_.is_err()) return end_; - end = as(end_.unwrap()); - // TODO: raise exception when end in not a number - ASSERT(end); - } + auto parse_result = PyArgsParser::unpack_tuple(args, + kwargs, + "endswith", + std::integral_constant{}, + std::integral_constant{}, + nullptr /* start */, + nullptr /* end */); + if (parse_result.is_err()) return Err(parse_result.unwrap_err()); + auto [suffix_obj, start, end] = parse_result.unwrap(); std::optional start_; std::optional end_; @@ -843,11 +777,9 @@ PyResult PyString::endswith(PyTuple *args, PyDict *kwargs) const } }; - auto suffix_ = PyObject::from(args->elements()[0]); - if (suffix_.is_err()) return suffix_; - if (auto *suffix = as(suffix_.unwrap())) { + if (auto *suffix = as(suffix_obj)) { return Ok(endswith_impl(suffix->value()) ? py_true() : py_false()); - } else if (auto *suffix_tuple = as(suffix_.unwrap())) { + } else if (auto *suffix_tuple = as(suffix_obj)) { for (const auto &el : suffix_tuple->elements()) { auto obj = PyObject::from(el); if (obj.is_err()) { return obj; } @@ -860,7 +792,7 @@ PyResult PyString::endswith(PyTuple *args, PyDict *kwargs) const } } else { return Err(type_error("endswith first arg must be str or a tuple of str, not '{}'", - suffix_.unwrap()->type()->name())); + suffix_obj->type()->name())); } return Ok(py_false()); @@ -868,13 +800,15 @@ PyResult PyString::endswith(PyTuple *args, PyDict *kwargs) const PyResult PyString::join(PyTuple *args, PyDict *kwargs) const { - ASSERT(args && args->size() == 1); - ASSERT(!kwargs); - - auto iterable = PyObject::from(args->elements()[0]); - if (iterable.is_err()) return iterable; + auto parse_result = PyArgsParser::unpack_tuple(args, + kwargs, + "join", + std::integral_constant{}, + std::integral_constant{}); + if (parse_result.is_err()) return Err(parse_result.unwrap_err()); + auto [iterable_obj] = parse_result.unwrap(); - auto iterator_ = iterable.unwrap()->iter(); + auto iterator_ = iterable_obj->iter(); if (iterator_.is_err()) return iterator_; auto *iterator = iterator_.unwrap(); @@ -946,13 +880,13 @@ PyResult PyString::partition(PyObject *sep) const PyResult PyString::rpartition(PyTuple *args, PyDict *kwargs) const { - ASSERT(args && args->size() == 1); - ASSERT(!kwargs || kwargs->size() == 0); - - auto sep_ = PyObject::from(args->elements()[0]); - if (sep_.is_err()) return sep_; - auto *sep_obj = as(sep_.unwrap()); - ASSERT(sep_obj); + auto parse_result = PyArgsParser::unpack_tuple(args, + kwargs, + "rpartition", + std::integral_constant{}, + std::integral_constant{}); + if (parse_result.is_err()) return Err(parse_result.unwrap_err()); + auto [sep_obj] = parse_result.unwrap(); const auto &sep = sep_obj->value(); @@ -971,20 +905,18 @@ PyResult PyString::rpartition(PyTuple *args, PyDict *kwargs) const PyResult PyString::strip(PyTuple *args, PyDict *kwargs) const { - ASSERT(!kwargs || kwargs->size() == 0); - - const auto chars = [args]() -> PyResult> { - if (!args || args->size() == 0) { return Ok(std::vector{}); } - auto args0 = PyObject::from(args->elements()[0]); - - auto str = as(args0.unwrap()); - if (!str) { return Err(type_error("")); } - return Ok(str->codepoints()); - }(); - - if (chars.is_err()) return Err(chars.unwrap_err()); + auto parse_result = PyArgsParser::unpack_tuple(args, + kwargs, + "strip", + std::integral_constant{}, + std::integral_constant{}, + nullptr /* chars */); + if (parse_result.is_err()) return Err(parse_result.unwrap_err()); + auto [chars_str] = parse_result.unwrap(); + const std::vector chars = + chars_str ? chars_str->codepoints() : std::vector{}; - if (chars.unwrap().empty()) { + if (chars.empty()) { const auto it_start = std::find_if( m_value.begin(), m_value.end(), [](const auto &el) { return !std::isspace(el); }); const auto it_end = std::find_if( @@ -997,7 +929,7 @@ PyResult PyString::strip(PyTuple *args, PyDict *kwargs) const return PyString::create(result); } else { const auto codepoints = this->codepoints(); - const auto patterns = chars.unwrap(); + const auto patterns = chars; auto contains = [patterns](const int32_t &cp) { return std::find(patterns.begin(), patterns.end(), cp) != patterns.end(); @@ -1030,20 +962,18 @@ PyResult PyString::strip(PyTuple *args, PyDict *kwargs) const PyResult PyString::rstrip(PyTuple *args, PyDict *kwargs) const { - ASSERT(!kwargs || kwargs->size() == 0); - - const auto chars = [args]() -> PyResult> { - if (!args || args->size() == 0) { return Ok(std::vector{}); } - auto args0 = PyObject::from(args->elements()[0]); - - auto str = as(args0.unwrap()); - if (!str) { return Err(type_error("")); } - return Ok(str->codepoints()); - }(); - - if (chars.is_err()) return Err(chars.unwrap_err()); + auto parse_result = PyArgsParser::unpack_tuple(args, + kwargs, + "rstrip", + std::integral_constant{}, + std::integral_constant{}, + nullptr /* chars */); + if (parse_result.is_err()) return Err(parse_result.unwrap_err()); + auto [chars_str] = parse_result.unwrap(); + const std::vector chars = + chars_str ? chars_str->codepoints() : std::vector{}; - if (chars.unwrap().empty()) { + if (chars.empty()) { const auto it = std::find_if( m_value.rbegin(), m_value.rend(), [](const auto &el) { return !std::isspace(el); }); if (it != m_value.rend()) { @@ -1057,7 +987,7 @@ PyResult PyString::rstrip(PyTuple *args, PyDict *kwargs) const const auto codepoints = this->codepoints(); auto codepoints_it = codepoints.rbegin(); size_t string_index = 0; - const auto patterns = chars.unwrap(); + const auto patterns = chars; auto contains = [patterns](const int32_t &cp) { return std::find(patterns.begin(), patterns.end(), cp) != patterns.end(); @@ -1079,14 +1009,19 @@ PyResult PyString::rstrip(PyTuple *args, PyDict *kwargs) const PyResult PyString::split(PyTuple *args, PyDict *kwargs) const { - ASSERT(!kwargs || kwargs->map().empty()); - - const auto sep_ = [args]() -> PyResult> { - if (!args || args->size() == 0) { return Ok(std::vector{}); } - auto args0 = PyObject::from(args->elements()[0]); - - if (args0.unwrap() == py_none()) { return Ok(std::vector{}); } - auto str = as(args0.unwrap()); + auto parse_result = PyArgsParser::unpack_tuple(args, + kwargs, + "split", + std::integral_constant{}, + std::integral_constant{}, + py_none() /* sep */, + nullptr /* maxsplit */); + if (parse_result.is_err()) return Err(parse_result.unwrap_err()); + auto [sep_obj, maxsplit_int] = parse_result.unwrap(); + + const auto sep_ = [sep_obj]() -> PyResult> { + if (sep_obj == py_none()) { return Ok(std::vector{}); } + auto str = as(sep_obj); if (!str) { return Err(type_error("")); } if (str->value().empty()) { return Err(value_error("empty separator")); } return Ok(str->codepoints()); @@ -1099,19 +1034,12 @@ PyResult PyString::split(PyTuple *args, PyDict *kwargs) const return PyList::create(std::vector{ PyString::create("").unwrap() }); } - const auto maxsplit_ = [this, args]() -> PyResult { - if (!args || args->size() < 2) { return Ok(BigIntType{ m_value.size() }); } - auto args1 = PyObject::from(args->elements()[1]); - - auto maxsplit = as(args1.unwrap()); - if (!maxsplit) { return Err(type_error("")); } - if (maxsplit->as_big_int() == -1) { return Ok(BigIntType{ m_value.size() }); } - return Ok(maxsplit->as_big_int()); + const BigIntType maxsplit = [this, maxsplit_int]() -> BigIntType { + if (!maxsplit_int) { return BigIntType{ m_value.size() }; } + if (maxsplit_int->as_big_int() == -1) { return BigIntType{ m_value.size() }; } + return maxsplit_int->as_big_int(); }(); - if (maxsplit_.is_err()) { return Err(maxsplit_.unwrap_err()); } - const auto &maxsplit = maxsplit_.unwrap(); - auto result_ = PyList::create(); if (result_.is_err()) { return result_; } auto *result = result_.unwrap(); diff --git a/src/runtime/PyType.cpp b/src/runtime/PyType.cpp index 36f7d6ba..f9782d70 100644 --- a/src/runtime/PyType.cpp +++ b/src/runtime/PyType.cpp @@ -1,5 +1,6 @@ #include "PyType.hpp" #include "AttributeError.hpp" +#include "PyArgParser.hpp" #include "PyBool.hpp" #include "PyBoundMethod.hpp" #include "PyBuiltInMethod.hpp" @@ -1424,14 +1425,13 @@ void PyType::fixup_slots() PyResult PyType::__new__(const PyType *type_, PyTuple *args, PyDict *kwargs) { - ASSERT(args && args->size() == 3); - ASSERT(!kwargs || kwargs->map().empty()); - - auto *name = as(PyObject::from(args->elements()[0]).unwrap()); - ASSERT(name); - auto *bases = as(PyObject::from(args->elements()[1]).unwrap()); - ASSERT(bases); - auto *ns = PyObject::from(args->elements()[2]).unwrap(); + auto result = PyArgsParser::unpack_tuple(args, + kwargs, + "type", + std::integral_constant{}, + std::integral_constant{}); + if (result.is_err()) return Err(result.unwrap_err()); + auto [name, bases, ns] = result.unwrap(); std::vector bases_vector; for (const auto &base : bases->elements()) { diff --git a/src/runtime/modules/BuiltinsModule.cpp b/src/runtime/modules/BuiltinsModule.cpp index 97511291..4a2d8793 100644 --- a/src/runtime/modules/BuiltinsModule.cpp +++ b/src/runtime/modules/BuiltinsModule.cpp @@ -132,30 +132,39 @@ PyResult print(const PyTuple *args, const PyDict *kwargs, Interprete } -PyResult iter(const PyTuple *args, const PyDict *kwargs, Interpreter &) +PyResult iter(PyTuple *args, PyDict *kwargs, Interpreter &) { - ASSERT(args->size() == 1); - const auto &arg = args->operator[](0); - if (kwargs) { return Err(type_error("iter() takes no keyword arguments")); } - return arg.and_then([](auto *obj) { return obj->iter(); }); + auto result = PyArgsParser::unpack_tuple(args, + kwargs, + "iter", + std::integral_constant{}, + std::integral_constant{}); + if (result.is_err()) return Err(result.unwrap_err()); + return std::get<0>(result.unwrap())->iter(); } -PyResult hash(const PyTuple *args, const PyDict *kwargs, Interpreter &) +PyResult hash(PyTuple *args, PyDict *kwargs, Interpreter &) { - ASSERT(args->size() == 1); - const auto &arg = args->operator[](0); - if (kwargs) { return Err(type_error("hash() takes no keyword arguments")); } - return arg.and_then([](auto *obj) { return obj->hash(); }).and_then([](const size_t h) { + auto result = PyArgsParser::unpack_tuple(args, + kwargs, + "hash", + std::integral_constant{}, + std::integral_constant{}); + if (result.is_err()) return Err(result.unwrap_err()); + return std::get<0>(result.unwrap())->hash().and_then([](const size_t h) { return PyInteger::create(h); }); } -PyResult next(const PyTuple *args, const PyDict *kwargs, Interpreter &) +PyResult next(PyTuple *args, PyDict *kwargs, Interpreter &) { - ASSERT(args->size() == 1); - if (kwargs) { return Err(type_error("next() takes no keyword arguments")); } - const auto &arg = args->operator[](0); - return arg.and_then([](auto *obj) { return obj->next(); }); + auto result = PyArgsParser::unpack_tuple(args, + kwargs, + "next", + std::integral_constant{}, + std::integral_constant{}); + if (result.is_err()) return Err(result.unwrap_err()); + return std::get<0>(result.unwrap())->next(); } @@ -356,24 +365,23 @@ PyResult locals(const PyTuple *, const PyDict *, Interpreter &interp } -PyResult len(const PyTuple *args, const PyDict *kwargs, Interpreter &) +PyResult len(PyTuple *args, PyDict *kwargs, Interpreter &) { - if (args->size() != 1) { - return Err(type_error("len() takes exactly one argument ({} given)", args->size())); - } - if (kwargs && !kwargs->map().empty()) { - return Err(type_error("len() takes no keyword arguments")); - } + auto result = PyArgsParser::unpack_tuple(args, + kwargs, + "len", + std::integral_constant{}, + std::integral_constant{}); + if (result.is_err()) return Err(result.unwrap_err()); - return PyObject::from(args->elements()[0]).and_then([](PyObject *o) -> PyResult { - auto mapping = o->as_mapping(); - if (mapping.is_err()) { return Err(mapping.unwrap_err()); } - if (auto r = mapping.unwrap().len(); r.is_ok()) { - return PyInteger::create(r.unwrap()); - } else { - return Err(r.unwrap_err()); - } - }); + auto *o = std::get<0>(result.unwrap()); + auto mapping = o->as_mapping(); + if (mapping.is_err()) { return Err(mapping.unwrap_err()); } + if (auto r = mapping.unwrap().len(); r.is_ok()) { + return PyInteger::create(r.unwrap()); + } else { + return Err(r.unwrap_err()); + } } PyResult id(const PyTuple *args, const PyDict *, Interpreter &) @@ -467,51 +475,44 @@ PyResult import(const PyTuple *args, const PyDict *, Interpreter &) static_cast(as(level)->as_size_t())); } -PyResult hasattr(const PyTuple *args, const PyDict *, Interpreter &) +PyResult hasattr(PyTuple *args, PyDict *kwargs, Interpreter &) { - if (args->size() != 2) { - return Err(type_error("hasattr expected 2 arguments, got {}", args->size())); - } - auto obj_ = PyObject::from(args->elements()[0]); - if (obj_.is_err()) return obj_; - auto *obj = obj_.unwrap(); - auto name_ = PyObject::from(args->elements()[1]); - if (name_.is_err()) return name_; - auto *name = name_.unwrap(); + auto result = PyArgsParser::unpack_tuple(args, + kwargs, + "hasattr", + std::integral_constant{}, + std::integral_constant{}); + if (result.is_err()) return Err(result.unwrap_err()); + auto [obj, name] = result.unwrap(); if (!as(name)) { return Err(type_error("hasattr(): attribute name must be string")); } - auto [result, found_status] = obj->lookup_attribute(name); + auto [lookup_result, found_status] = obj->lookup_attribute(name); if (found_status == LookupAttrResult::FOUND) { return Ok(py_true()); } else if (found_status == LookupAttrResult::NOT_FOUND) { return Ok(py_false()); } else { - return result; + return lookup_result; } } -PyResult getattr(const PyTuple *args, const PyDict *, Interpreter &) +PyResult getattr(PyTuple *args, PyDict *kwargs, Interpreter &) { - if (args->size() != 2 && args->size() != 3) { - return Err(type_error("getattr expected 2 or 3 arguments, got {}", args->size())); - } - auto obj_ = PyObject::from(args->elements()[0]); - if (obj_.is_err()) return obj_; - auto *obj = obj_.unwrap(); - auto name_ = PyObject::from(args->elements()[1]); - if (name_.is_err()) return name_; - auto *name = name_.unwrap(); + auto result = PyArgsParser::unpack_tuple(args, + kwargs, + "getattr", + std::integral_constant{}, + std::integral_constant{}, + nullptr /* default */); + if (result.is_err()) return Err(result.unwrap_err()); + auto [obj, name, default_value] = result.unwrap(); if (!as(name)) { return Err(type_error("getattr(): attribute name must be string")); } - if (args->size() == 2) { - auto result = obj->getattribute(name); - if (result.is_ok()) { ASSERT(result.unwrap()); } - return result; + if (!default_value) { + auto attr = obj->getattribute(name); + if (attr.is_ok()) { ASSERT(attr.unwrap()); } + return attr; } else { - auto default_value_ = PyObject::from(args->elements()[2]); - if (default_value_.is_err()) return default_value_; - auto *default_value = default_value_.unwrap(); - auto [attr_value, found_status] = obj->lookup_attribute(name); if (attr_value.is_err()) { return attr_value; } @@ -526,36 +527,34 @@ PyResult getattr(const PyTuple *args, const PyDict *, Interpreter &) } } -PyResult setattr(const PyTuple *args, const PyDict *, Interpreter &) +PyResult setattr(PyTuple *args, PyDict *kwargs, Interpreter &) { - if (args->size() != 3) { - return Err(type_error("setattr expected 3 arguments, got {}", args->size())); - } - auto obj_ = PyObject::from(args->elements()[0]); - if (obj_.is_err()) return obj_; - auto *obj = obj_.unwrap(); - auto name_ = PyObject::from(args->elements()[1]); - if (name_.is_err()) return name_; - auto *name = name_.unwrap(); - auto value_ = PyObject::from(args->elements()[2]); - if (value_.is_err()) return value_; - auto *value = value_.unwrap(); + auto result = PyArgsParser::unpack_tuple(args, + kwargs, + "setattr", + std::integral_constant{}, + std::integral_constant{}); + if (result.is_err()) return Err(result.unwrap_err()); + auto [obj, name, value] = result.unwrap(); if (!as(name)) { return Err(type_error("setattr(): attribute name must be string")); } - if (auto result = obj->setattribute(name, value); result.is_ok()) { + if (auto set_result = obj->setattribute(name, value); set_result.is_ok()) { return Ok(py_none()); } else { - return Err(result.unwrap_err()); + return Err(set_result.unwrap_err()); } } -PyResult hex(const PyTuple *args, const PyDict *, Interpreter &) +PyResult hex(PyTuple *args, PyDict *kwargs, Interpreter &) { - ASSERT(args->size() == 1); - auto obj_ = args->operator[](0); - if (obj_.is_err()) return obj_; - auto *obj = obj_.unwrap(); + auto result = PyArgsParser::unpack_tuple(args, + kwargs, + "hex", + std::integral_constant{}, + std::integral_constant{}); + if (result.is_err()) return Err(result.unwrap_err()); + auto *obj = std::get<0>(result.unwrap()); if (auto pynumber = PyNumber::as_number(obj)) { if (std::holds_alternative(pynumber->value().value)) { std::ostringstream os; @@ -572,12 +571,15 @@ PyResult hex(const PyTuple *args, const PyDict *, Interpreter &) } } -PyResult ord(const PyTuple *args, const PyDict *, Interpreter &) +PyResult ord(PyTuple *args, PyDict *kwargs, Interpreter &) { - ASSERT(args->size() == 1); - auto obj_ = args->operator[](0); - if (obj_.is_err()) return obj_; - auto *obj = obj_.unwrap(); + auto result = PyArgsParser::unpack_tuple(args, + kwargs, + "ord", + std::integral_constant{}, + std::integral_constant{}); + if (result.is_err()) return Err(result.unwrap_err()); + auto *obj = std::get<0>(result.unwrap()); if (auto pystr = as(obj)) { if (auto codepoint = pystr->codepoint()) { return PyObject::from(Number{ static_cast(*codepoint) }); @@ -595,12 +597,15 @@ PyResult ord(const PyTuple *args, const PyDict *, Interpreter &) } } -PyResult chr(const PyTuple *args, const PyDict *, Interpreter &) +PyResult chr(PyTuple *args, PyDict *kwargs, Interpreter &) { - ASSERT(args->size() == 1); - auto obj_ = args->operator[](0); - if (obj_.is_err()) return obj_; - auto *obj = obj_.unwrap(); + auto result = PyArgsParser::unpack_tuple(args, + kwargs, + "chr", + std::integral_constant{}, + std::integral_constant{}); + if (result.is_err()) return Err(result.unwrap_err()); + auto *obj = std::get<0>(result.unwrap()); if (auto cp = PyNumber::as_number(obj)) { if (std::holds_alternative(cp->value().value)) { @@ -657,23 +662,26 @@ PyResult dir(const PyTuple *args, const PyDict *, Interpreter &inter return Ok(static_cast(dir_list_.unwrap())); } -PyResult repr(const PyTuple *args, const PyDict *, Interpreter &) +PyResult repr(PyTuple *args, PyDict *kwargs, Interpreter &) { - if (args->size() != 1) { - return Err(type_error("repr() takes exactly one argument ({} given)", args->size())); - } - return PyObject::from(args->elements()[0]).and_then([](auto *obj) { return obj->repr(); }); + auto result = PyArgsParser::unpack_tuple(args, + kwargs, + "repr", + std::integral_constant{}, + std::integral_constant{}); + if (result.is_err()) return Err(result.unwrap_err()); + return std::get<0>(result.unwrap())->repr(); } -PyResult abs(const PyTuple *args, const PyDict *kwargs, Interpreter &) +PyResult abs(PyTuple *args, PyDict *kwargs, Interpreter &) { - if (args->size() != 1) { - return Err(type_error("abs() takes exactly one argument ({} given)", args->size())); - } - if (kwargs && !kwargs->map().empty()) { - return Err(type_error("abs() takes no keyword arguments")); - } - return PyObject::from(args->elements()[0]).and_then([](auto *obj) { return obj->abs(); }); + auto result = PyArgsParser::unpack_tuple(args, + kwargs, + "abs", + std::integral_constant{}, + std::integral_constant{}); + if (result.is_err()) return Err(result.unwrap_err()); + return std::get<0>(result.unwrap())->abs(); } PyResult max(const PyTuple *args, const PyDict *kwargs, Interpreter &interpreter) @@ -774,21 +782,15 @@ PyResult min(const PyTuple *args, const PyDict *kwargs, Interpreter } } -PyResult isinstance(const PyTuple *args, const PyDict *kwargs, Interpreter &) +PyResult isinstance(PyTuple *args, PyDict *kwargs, Interpreter &) { - if (args->size() != 2) { - return Err(type_error("isinstance expected 2 arguments, got {}", args->size())); - } - - if (kwargs && !kwargs->map().empty()) { - return Err(type_error("isinstance() takes no keyword arguments")); - } - auto object_ = PyObject::from(args->elements()[0]); - if (object_.is_err()) return object_; - auto *object = object_.unwrap(); - auto classinfo_ = PyObject::from(args->elements()[1]); - if (classinfo_.is_err()) return classinfo_; - auto *classinfo = classinfo_.unwrap(); + auto result = PyArgsParser::unpack_tuple(args, + kwargs, + "isinstance", + std::integral_constant{}, + std::integral_constant{}); + if (result.is_err()) return Err(result.unwrap_err()); + auto [object, classinfo] = result.unwrap(); std::vector types; if (auto *class_info_tuple = as(classinfo)) { @@ -807,28 +809,22 @@ PyResult isinstance(const PyTuple *args, const PyDict *kwargs, Inter return Err(type_error("isinstance() arg 2 must be a type or tuple of types")); } - const auto result = std::any_of(types.begin(), types.end(), [object](PyType *const &t) { + const auto matches = std::any_of(types.begin(), types.end(), [object](PyType *const &t) { return object->type()->issubclass(t); }); - return Ok(result ? py_true() : py_false()); + return Ok(matches ? py_true() : py_false()); } -PyResult issubclass(const PyTuple *args, const PyDict *kwargs, Interpreter &) +PyResult issubclass(PyTuple *args, PyDict *kwargs, Interpreter &) { - if (args->size() != 2) { - return Err(type_error("issubclass expected 2 arguments, got {}", args->size())); - } - - if (kwargs && !kwargs->map().empty()) { - return Err(type_error("issubclass() takes no keyword arguments")); - } - auto c = PyObject::from(args->elements()[0]); - if (c.is_err()) return c; - auto *class_ = c.unwrap(); - auto classinfo_ = PyObject::from(args->elements()[1]); - if (classinfo_.is_err()) return classinfo_; - auto *classinfo = classinfo_.unwrap(); + auto result = PyArgsParser::unpack_tuple(args, + kwargs, + "issubclass", + std::integral_constant{}, + std::integral_constant{}); + if (result.is_err()) return Err(result.unwrap_err()); + auto [class_, classinfo] = result.unwrap(); auto *class_as_type = as(class_); if (!class_as_type) { return Err(type_error("issubclass() arg 1 must be a class")); } @@ -843,18 +839,15 @@ PyResult issubclass(const PyTuple *args, const PyDict *kwargs, Inter } } -PyResult all(const PyTuple *args, const PyDict *kwargs, Interpreter &) +PyResult all(PyTuple *args, PyDict *kwargs, Interpreter &) { - if (args->size() != 1) { - return Err(type_error("all expected 1 arguments, got {}", args->size())); - } - - if (kwargs && !kwargs->map().empty()) { - return Err(type_error("all() takes no keyword arguments")); - } - auto iterable_ = PyObject::from(args->elements()[0]); - if (iterable_.is_err()) return iterable_; - auto *iterable = iterable_.unwrap(); + auto result = PyArgsParser::unpack_tuple(args, + kwargs, + "all", + std::integral_constant{}, + std::integral_constant{}); + if (result.is_err()) return Err(result.unwrap_err()); + auto *iterable = std::get<0>(result.unwrap()); const auto &iterator = iterable->iter(); if (iterator.is_err()) return iterator; @@ -874,18 +867,15 @@ PyResult all(const PyTuple *args, const PyDict *kwargs, Interpreter } -PyResult any(const PyTuple *args, const PyDict *kwargs, Interpreter &) +PyResult any(PyTuple *args, PyDict *kwargs, Interpreter &) { - if (args->size() != 1) { - return Err(type_error("any expected 1 arguments, got {}", args->size())); - } - - if (kwargs && !kwargs->map().empty()) { - return Err(type_error("any() takes no keyword arguments")); - } - auto iterable_ = PyObject::from(args->elements()[0]); - if (iterable_.is_err()) return iterable_; - auto *iterable = iterable_.unwrap(); + auto result = PyArgsParser::unpack_tuple(args, + kwargs, + "any", + std::integral_constant{}, + std::integral_constant{}); + if (result.is_err()) return Err(result.unwrap_err()); + auto *iterable = std::get<0>(result.unwrap()); const auto &iterator = iterable->iter(); if (iterator.is_err()) return iterator; @@ -904,31 +894,17 @@ PyResult any(const PyTuple *args, const PyDict *kwargs, Interpreter } } -PyResult exec(const PyTuple *args, const PyDict *, Interpreter &interpreter) +PyResult exec(PyTuple *args, PyDict *kwargs, Interpreter &interpreter) { - ASSERT(args); - if (args->size() < 1) { - return Err(type_error("exec expected at least 1 argument, got {}", args->size())); - } - if (args->size() > 3) { - return Err(type_error("exec expected at most 3 arguments, got {}", args->size())); - } - - auto source_ = PyObject::from(args->elements()[0]); - auto globals_ = args->size() >= 2 ? PyObject::from(args->elements()[1]) : Ok(py_none()); - auto locals_ = args->size() == 3 ? PyObject::from(args->elements()[2]) : Ok(py_none()); - - if (source_.is_err()) return source_; - if (globals_.is_err()) return globals_; - if (locals_.is_err()) return locals_; - - auto *source = source_.unwrap(); - auto *globals = globals_.unwrap(); - auto *locals = locals_.unwrap(); - - ASSERT(source); - ASSERT(globals); - ASSERT(locals); + auto result = PyArgsParser::unpack_tuple(args, + kwargs, + "exec", + std::integral_constant{}, + std::integral_constant{}, + py_none() /* globals */, + py_none() /* locals */); + if (result.is_err()) return Err(result.unwrap_err()); + auto [source, globals, locals] = result.unwrap(); if (globals == py_none()) { globals = interpreter.execution_frame()->globals(); @@ -1123,26 +1099,16 @@ PyResult compile(const PyTuple *args, const PyDict *, Interpreter &) } } -PyResult callable(const PyTuple *args, const PyDict *kwargs, Interpreter &) +PyResult callable(PyTuple *args, PyDict *kwargs, Interpreter &) { - if (!args) { - return Err(type_error("callable() takes exactly one argument (0 given)")); - } else if (args->size() != 1) { - return Err(type_error("callable() takes exactly one argument ({} given)", args->size())); - } - - if (kwargs && kwargs->size() != 0) { - return Err(type_error("callable() takes no keyword arguments", args->size())); - } - - auto obj = args->elements()[0]; - return std::visit(overloaded{ - [](auto) { return false; }, - [](PyObject *obj) { return obj->type_prototype().__call__.has_value(); }, - }, - obj) - ? Ok(py_true()) - : Ok(py_false()); + auto result = PyArgsParser::unpack_tuple(args, + kwargs, + "callable", + std::integral_constant{}, + std::integral_constant{}); + if (result.is_err()) return Err(result.unwrap_err()); + auto *obj = std::get<0>(result.unwrap()); + return obj->type_prototype().__call__.has_value() ? Ok(py_true()) : Ok(py_false()); } PyResult ascii(PyTuple *args, PyDict *kwargs, Interpreter &) diff --git a/src/runtime/modules/IOModule.cpp b/src/runtime/modules/IOModule.cpp index 337ff8fd..a926a616 100644 --- a/src/runtime/modules/IOModule.cpp +++ b/src/runtime/modules/IOModule.cpp @@ -3042,26 +3042,29 @@ PyModule *io_module() s_io_module->add_symbol(PyString::create("open").unwrap(), VirtualMachine::the().heap().allocate( - "open", [](PyTuple *args, PyDict *kwargs) { - ASSERT(!kwargs || kwargs->map().empty()); - ASSERT(args && args->elements().size() == 2); - auto arg0 = PyObject::from(args->elements()[0]).unwrap(); - auto arg1 = PyObject::from(args->elements()[1]).unwrap(); - - ASSERT(as(arg1)); - const std::string rawmode = as(arg1)->value(); + "open", [](PyTuple *args, PyDict *kwargs) -> PyResult { + auto result = PyArgsParser::unpack_tuple(args, + kwargs, + "open", + std::integral_constant{}, + std::integral_constant{}); + if (result.is_err()) return Err(result.unwrap_err()); + auto [file, mode] = result.unwrap(); - return open(arg0, rawmode); + return open(file, mode->value()); })); s_io_module->add_symbol(PyString::create("open_code").unwrap(), VirtualMachine::the().heap().allocate( - "open_code", [](PyTuple *args, PyDict *kwargs) { - ASSERT(!kwargs || kwargs->map().empty()); - ASSERT(args && args->elements().size() == 1); - auto arg0 = PyObject::from(args->elements()[0]).unwrap(); + "open_code", [](PyTuple *args, PyDict *kwargs) -> PyResult { + auto result = PyArgsParser::unpack_tuple(args, + kwargs, + "open_code", + std::integral_constant{}, + std::integral_constant{}); + if (result.is_err()) return Err(result.unwrap_err()); - return open(arg0, "rb"); + return open(std::get<0>(result.unwrap()), "rb"); })); // C++ standard streams currently do not provide an API to get default buffer size, and C's diff --git a/src/runtime/modules/ImpModule.cpp b/src/runtime/modules/ImpModule.cpp index 7cf854fb..861c4104 100644 --- a/src/runtime/modules/ImpModule.cpp +++ b/src/runtime/modules/ImpModule.cpp @@ -4,6 +4,7 @@ #include "interpreter/Interpreter.hpp" #include "runtime/Import.hpp" #include "runtime/ImportError.hpp" +#include "runtime/PyArgParser.hpp" #include "runtime/PyBool.hpp" #include "runtime/PyCode.hpp" #include "runtime/PyDict.hpp" @@ -32,41 +33,44 @@ PyModule *imp_module() s_imp_module->add_symbol(PyString::create("is_frozen").unwrap(), PyNativeFunction::create("is_frozen", [](PyTuple *args, PyDict *kwargs) -> py::PyResult { - ASSERT(args->size() == 1); - ASSERT(!kwargs || kwargs->map().size() == 0); - - auto arg0 = PyObject::from(args->elements()[0]); - if (arg0.is_err()) { return Err(arg0.unwrap_err()); } - - if (!as(arg0.unwrap())) { - return Err(type_error("expected name to be a string, but got {}", - arg0.unwrap()->type()->to_string())); + auto result = PyArgsParser::unpack_tuple(args, + kwargs, + "is_frozen", + std::integral_constant{}, + std::integral_constant{}); + if (result.is_err()) return Err(result.unwrap_err()); + auto *name = std::get<0>(result.unwrap()); + + if (!as(name)) { + return Err(type_error( + "expected name to be a string, but got {}", name->type()->to_string())); } - return Ok( - find_frozen(as(arg0.unwrap())).has_value() ? py_true() : py_false()); + return Ok(find_frozen(as(name)).has_value() ? py_true() : py_false()); }) .unwrap()); s_imp_module->add_symbol(PyString::create("is_frozen_package").unwrap(), PyNativeFunction::create("is_frozen_package", [](PyTuple *args, PyDict *kwargs) -> py::PyResult { - ASSERT(args->size() == 1); - ASSERT(!kwargs || kwargs->map().size() == 0); - - auto arg0 = PyObject::from(args->elements()[0]); - if (arg0.is_err()) { return Err(arg0.unwrap_err()); } - - if (!as(arg0.unwrap())) { - return Err(type_error("expected name to be a string, but got {}", - arg0.unwrap()->type()->to_string())); + auto result = PyArgsParser::unpack_tuple(args, + kwargs, + "is_frozen_package", + std::integral_constant{}, + std::integral_constant{}); + if (result.is_err()) return Err(result.unwrap_err()); + auto *name = std::get<0>(result.unwrap()); + + if (!as(name)) { + return Err(type_error( + "expected name to be a string, but got {}", name->type()->to_string())); } - if (auto frozen_module = find_frozen(as(arg0.unwrap()))) { + if (auto frozen_module = find_frozen(as(name))) { return Ok(frozen_module->get().is_package ? py_true() : py_false()); } else { return Err(import_error( - "No such frozen object named {}", as(arg0.unwrap())->value())); + "No such frozen object named {}", as(name)->value())); } }) .unwrap()); @@ -74,24 +78,26 @@ PyModule *imp_module() s_imp_module->add_symbol(PyString::create("get_frozen_object").unwrap(), PyNativeFunction::create("get_frozen_object", [](PyTuple *args, PyDict *kwargs) -> py::PyResult { - ASSERT(args->size() == 1); - ASSERT(!kwargs || kwargs->map().size() == 0); - - auto arg0 = PyObject::from(args->elements()[0]); - if (arg0.is_err()) { return Err(arg0.unwrap_err()); } - - if (!as(arg0.unwrap())) { - return Err(type_error("expected name to be a string, but got {}", - arg0.unwrap()->type()->to_string())); + auto result = PyArgsParser::unpack_tuple(args, + kwargs, + "get_frozen_object", + std::integral_constant{}, + std::integral_constant{}); + if (result.is_err()) return Err(result.unwrap_err()); + auto *name = std::get<0>(result.unwrap()); + + if (!as(name)) { + return Err(type_error( + "expected name to be a string, but got {}", name->type()->to_string())); } - if (auto frozen_module = find_frozen(as(arg0.unwrap()))) { + if (auto frozen_module = find_frozen(as(name))) { std::shared_ptr program = BytecodeProgram::deserialize(frozen_module->get().code); return PyCode::create(program); } else { return Err(import_error( - "No such frozen object named {}", as(arg0.unwrap())->value())); + "No such frozen object named {}", as(name)->value())); } }) .unwrap()); @@ -117,43 +123,55 @@ PyModule *imp_module() s_imp_module->add_symbol(PyString::create("is_builtin").unwrap(), PyNativeFunction::create("is_builtin", [](PyTuple *args, PyDict *kwargs) -> py::PyResult { - ASSERT(!args || args->size() == 1); - ASSERT(!kwargs || kwargs->map().size() == 0); - auto name = PyObject::from(args->elements()[0]).unwrap(); - ASSERT(as(name)); - - return is_builtin(as(name)->value()) ? Ok(py_true()) : Ok(py_false()); + auto result = PyArgsParser::unpack_tuple(args, + kwargs, + "is_builtin", + std::integral_constant{}, + std::integral_constant{}); + if (result.is_err()) return Err(result.unwrap_err()); + auto *name = std::get<0>(result.unwrap()); + + return is_builtin(name->value()) ? Ok(py_true()) : Ok(py_false()); }) .unwrap()); s_imp_module->add_symbol(PyString::create("create_builtin").unwrap(), PyNativeFunction::create("create_builtin", [](PyTuple *args, PyDict *kwargs) -> py::PyResult { - ASSERT(!args || args->size() == 1); - ASSERT(!kwargs || kwargs->map().size() == 0); - auto spec = PyObject::from(args->elements()[0]).unwrap(); - - return create_builtin(spec); + auto result = PyArgsParser::unpack_tuple(args, + kwargs, + "create_builtin", + std::integral_constant{}, + std::integral_constant{}); + if (result.is_err()) return Err(result.unwrap_err()); + + return create_builtin(std::get<0>(result.unwrap())); }) .unwrap()); s_imp_module->add_symbol(PyString::create("exec_builtin").unwrap(), PyNativeFunction::create("exec_builtin", [](PyTuple *args, PyDict *kwargs) -> py::PyResult { - ASSERT(!args || args->size() == 1); - ASSERT(!kwargs || kwargs->map().size() == 0); - auto mod = PyObject::from(args->elements()[0]).unwrap(); - return exec_builtin(mod); + auto result = PyArgsParser::unpack_tuple(args, + kwargs, + "exec_builtin", + std::integral_constant{}, + std::integral_constant{}); + if (result.is_err()) return Err(result.unwrap_err()); + return exec_builtin(std::get<0>(result.unwrap())); }) .unwrap()); s_imp_module->add_symbol(PyString::create("exec_dynamic").unwrap(), PyNativeFunction::create("exec_dynamic", [](PyTuple *args, PyDict *kwargs) -> py::PyResult { - ASSERT(!args || args->size() == 1); - ASSERT(!kwargs || kwargs->map().size() == 0); - auto mod = PyObject::from(args->elements()[0]).unwrap(); - return exec_builtin(mod); + auto result = PyArgsParser::unpack_tuple(args, + kwargs, + "exec_dynamic", + std::integral_constant{}, + std::integral_constant{}); + if (result.is_err()) return Err(result.unwrap_err()); + return exec_builtin(std::get<0>(result.unwrap())); }) .unwrap()); diff --git a/src/runtime/modules/PosixModule.cpp b/src/runtime/modules/PosixModule.cpp index 314adb64..8645cbf5 100644 --- a/src/runtime/modules/PosixModule.cpp +++ b/src/runtime/modules/PosixModule.cpp @@ -1,4 +1,5 @@ #include "Modules.hpp" +#include "runtime/PyArgParser.hpp" #include "runtime/PyBytes.hpp" #include "runtime/PyDict.hpp" #include "runtime/PyFunction.hpp" @@ -185,22 +186,18 @@ PyModule *posix_module() s_posix_module->add_symbol(PyString::create("stat").unwrap(), PyNativeFunction::create("stat", [](PyTuple *args, PyDict *kwargs) -> py::PyResult { - ASSERT(!kwargs || kwargs->map().empty()); - - if (!args) { return Err(type_error("posix.stat() takes one argument (0 given)")); } - - if (args->size() != 1) { + auto result = PyArgsParser::unpack_tuple(args, + kwargs, + "posix.stat", + std::integral_constant{}, + std::integral_constant{}); + if (result.is_err()) return Err(result.unwrap_err()); + auto *path_obj = std::get<0>(result.unwrap()); + if (!as(path_obj)) { return Err( - type_error("posix.stat() takes one argument ({} given)", args->size())); - } - - const auto path = PyObject::from(args->elements()[0]); - if (path.is_err()) return path; - if (!as(path.unwrap())) { - return Err(type_error( - "expected to be string but got '{}'", path.unwrap()->type()->name())); + type_error("expected to be string but got '{}'", path_obj->type()->name())); } - const auto *path_cstr = as(path.unwrap())->value().c_str(); + const auto *path_cstr = as(path_obj)->value().c_str(); auto stat_ = std::make_unique(); stat(path_cstr, stat_.get()); // FIXME: handle errors @@ -212,22 +209,24 @@ PyModule *posix_module() s_posix_module->add_symbol(PyString::create("listdir").unwrap(), PyNativeFunction::create("listdir", [](PyTuple *args, PyDict *kwargs) -> py::PyResult { - ASSERT(!kwargs || kwargs->map().empty()); - - if (args->size() > 1) { - return Err(type_error( - "posix.listdir() takes at most one argument ({} given)", args->size())); - } - - const auto path = args->size() == 1 ? PyObject::from(args->elements()[0]) - : PyObject::from(String{ "." }); - if (path.is_err()) return path; - if (!as(path.unwrap())) { - return Err(type_error( - "expected to be string but got '{}'", path.unwrap()->type()->name())); + auto parsed = PyArgsParser::unpack_tuple(args, + kwargs, + "posix.listdir", + std::integral_constant{}, + std::integral_constant{}, + nullptr); + if (parsed.is_err()) return Err(parsed.unwrap_err()); + auto *path_obj = std::get<0>(parsed.unwrap()); + + std::string dir_name; + if (!path_obj) { + dir_name = "."; + } else if (auto *path_str = as(path_obj)) { + dir_name = path_str->value(); + } else { + return Err( + type_error("expected to be string but got '{}'", path_obj->type()->name())); } - - const auto dir_name = as(path.unwrap())->value(); const auto dir = fs::path(dir_name); { @@ -259,25 +258,19 @@ PyModule *posix_module() s_posix_module->add_symbol(PyString::create("fspath").unwrap(), PyNativeFunction::create("fspath", [](PyTuple *args, PyDict *kwargs) -> py::PyResult { - ASSERT(!kwargs || kwargs->map().empty()); - - if (!args) { - return Err(type_error("posix.fspath() takes one argument (0 given)")); - } - - if (args->size() != 1) { - return Err( - type_error("posix.fspath() takes one argument ({} given)", args->size())); - } - - const auto path = PyObject::from(args->elements()[0]); - if (path.is_err()) return path; - if (!as(path.unwrap()) && !as(path.unwrap())) { + auto result = PyArgsParser::unpack_tuple(args, + kwargs, + "posix.fspath", + std::integral_constant{}, + std::integral_constant{}); + if (result.is_err()) return Err(result.unwrap_err()); + auto *path_obj = std::get<0>(result.unwrap()); + if (!as(path_obj) && !as(path_obj)) { // should check __fspath__ slot if not string or bytes return Err(type_error("expected str, bytes or os.PathLike object, not {}", - path.unwrap()->type()->name())); + path_obj->type()->name())); } - return path; + return Ok(path_obj); }) .unwrap());