Skip to content

Commit

Permalink
[stdlib] Add repr() function and Representable trait
Browse files Browse the repository at this point in the history
Signed-off-by: gabrieldemarmiesse <gabrieldemarmiesse@gmail.com>
  • Loading branch information
gabrieldemarmiesse committed Apr 20, 2024
1 parent 6af6934 commit 0409719
Show file tree
Hide file tree
Showing 12 changed files with 224 additions and 16 deletions.
8 changes: 8 additions & 0 deletions stdlib/src/builtin/int.mojo
Expand Up @@ -314,6 +314,14 @@ struct Int(Intable, Stringable, KeyElement, Boolable):
buf.size += 1 # for the null terminator.
return buf^

fn __repr__(self) -> String:
"""Get the integer as a string. Returns the same `String` as `__str__`.
Returns:
A string representation.
"""
return str(self)

@always_inline("nodebug")
fn __mlir_index__(self) -> __mlir_type.index:
"""Convert to index.
Expand Down
88 changes: 88 additions & 0 deletions stdlib/src/builtin/repr.mojo
@@ -0,0 +1,88 @@
# ===----------------------------------------------------------------------=== #
# Copyright (c) 2024, Modular Inc. All rights reserved.
#
# Licensed under the Apache License v2.0 with LLVM Exceptions:
# https://llvm.org/LICENSE.txt
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ===----------------------------------------------------------------------=== #
"""Provide the `repr` function.
The functions and traits provided here are built-ins, so you don't need to import them.
"""


trait Representable:
"""A trait that describes a type that has a String representation.
Any type that conforms to the `Representable` trait can be used with the
`repr` function. Any conforming type must also implement the `__repr__` method.
Here is an example:
```mojo
@value
struct Dog(Representable):
var name: String
var age: Int
fn __repr__(self) -> String:
return "Dog(name=" + repr(self.name) + ", age=" + repr(self.age) + ")"
var dog = Dog("Rex", 5)
print(repr(dog))
# Dog(name='Rex', age=5)
```
The method `__repr__` should compute the "official" string representation of a type.
If at all possible, this should look like a valid Mojo expression
that could be used to recreate a struct instance with the same
value (given an appropriate environment).
So a returned String of the form `module_name.SomeStruct(arg1=value1, arg2=value2)` is advised.
If this is not possible, a string of the form `<...some useful description...>`
should be returned.
The return value must be a `String` instance.
This is typically used for debugging, so it is important that the representation is information-rich and unambiguous.
Note that when computing the string representation of a collection (`Dict`, `List`, `Set`, etc...),
the `repr` function is called on each element, not the `str()` function.
"""

fn __repr__(self) -> String:
"""Get the string representation of the type instance, if possible, compatible with Mojo syntax.
Returns:
The string representation of the instance.
"""
pass


fn repr[T: Representable](value: T) -> String:
"""
Args:
value: The value to get the string representation of.
Parameters:
T: The type of `value`. Must implement the `Representable` trait.
Returns:
The string representation of the given value.
"""
return value.__repr__()


fn repr(value: None) -> String:
"""Returns the string representation of `None`.
Args:
value: A `None` value.
Returns:
The string representation of `None`.
"""
return "None"
7 changes: 7 additions & 0 deletions stdlib/src/builtin/str.mojo
Expand Up @@ -57,6 +57,13 @@ trait Stringable:
**Note:** If the `__str__()` method might raise an error, use the
[`StringableRaising`](/mojo/stdlib/builtin/str/stringableraising)
trait, instead.
About the difference between `__repr__()` and `__str__()`:
The method `__repr__` compute the compute the "official" string representation of an object
while `__str__` computes the "informal" or nicely printable string representation of an object.
This method differs from `__repr__()` in that there is no expectation that `__str__()`
return a valid Python expression: a more convenient or concise representation can be used.
"""

fn __str__(self) -> String:
Expand Down
15 changes: 14 additions & 1 deletion stdlib/src/builtin/string.mojo
Expand Up @@ -324,7 +324,9 @@ fn isspace(c: Int8) -> Bool:
# ===----------------------------------------------------------------------===#
# String
# ===----------------------------------------------------------------------===#
struct String(Sized, Stringable, IntableRaising, KeyElement, Boolable):
struct String(
Sized, Stringable, Representable, IntableRaising, KeyElement, Boolable
):
"""Represents a mutable string."""

alias _buffer_type = List[Int8]
Expand All @@ -335,6 +337,17 @@ struct String(Sized, Stringable, IntableRaising, KeyElement, Boolable):
fn __str__(self) -> String:
return self

@always_inline
fn __repr__(self) -> String:
"""Return a Mojo-compatible representation of the `String` instance.
You don't need to call this method directly, use `repr(my_string)` instead.
"""
if "'" in self:
return '"' + self + "'"
else:
return "'" + self + "'"

@always_inline
fn __init__(inout self, owned impl: Self._buffer_type):
"""Construct a string from a buffer of bytes.
Expand Down
12 changes: 12 additions & 0 deletions stdlib/src/builtin/value.mojo
Expand Up @@ -121,3 +121,15 @@ trait StringableCollectionElement(CollectionElement, Stringable):
"""

pass


trait RepresentableCollectionElement(CollectionElement, Representable):
"""The RepresentableCollectionElement trait denotes a trait composition
of the `CollectionElement` and `Representable` traits.
This is useful to have as a named entity since Mojo does not
currently support anonymous trait compositions to constrain
on `CollectionElement & Representable` in the parameter.
"""

pass
13 changes: 8 additions & 5 deletions stdlib/src/collections/dict.mojo
Expand Up @@ -46,7 +46,7 @@ trait KeyElement(CollectionElement, Hashable, EqualityComparable):
pass


trait StringableKeyElement(KeyElement, Stringable):
trait RepresentableKeyElement(KeyElement, Representable):
pass


Expand Down Expand Up @@ -506,7 +506,7 @@ struct Dict[K: KeyElement, V: CollectionElement](

@staticmethod
fn __str__[
T: StringableKeyElement, U: StringableCollectionElement
T: RepresentableKeyElement, U: RepresentableCollectionElement
](self: Dict[T, U]) -> String:
"""Returns a string representation of a `Dict`.
Expand All @@ -525,14 +525,17 @@ struct Dict[K: KeyElement, V: CollectionElement](
When the compiler supports conditional methods, then a simple `str(my_dict)` will
be enough.
Note that both they keys and values' types must implement the `__repr__()` method
for this to work. See the `Representable` trait for more information.
Args:
self: The Dict to represent as a string.
Parameters:
T: The type of the keys in the Dict. Must implement the
traits `Stringable` and `KeyElement`.
traits `Representable` and `KeyElement`.
U: The type of the values in the Dict. Must implement the
traits `Stringable` and `CollectionElement`.
traits `Representable` and `CollectionElement`.
Returns:
A string representation of the Dict.
Expand All @@ -543,7 +546,7 @@ struct Dict[K: KeyElement, V: CollectionElement](

var i = 0
for key_value in self.items():
result += str(key_value[].key) + ": " + str(key_value[].value)
result += repr(key_value[].key) + ": " + repr(key_value[].value)
if i < len(self) - 1:
result += ", "
i += 1
Expand Down
8 changes: 5 additions & 3 deletions stdlib/src/collections/list.mojo
Expand Up @@ -571,7 +571,7 @@ struct List[T: CollectionElement](CollectionElement, Sized, Boolable):
return _ListIter[T, mutability, self_life, False](len(ref[]), ref)

@staticmethod
fn __str__[U: StringableCollectionElement](self: List[U]) -> String:
fn __str__[U: RepresentableCollectionElement](self: List[U]) -> String:
"""Returns a string representation of a `List`.
Note that since we can't condition methods on a trait yet,
Expand All @@ -585,12 +585,14 @@ struct List[T: CollectionElement](CollectionElement, Sized, Boolable):
When the compiler supports conditional methods, then a simple `str(my_list)` will
be enough.
The elements' type must implement the `__repr__()` for this to work.
Args:
self: The list to represent as a string.
Parameters:
U: The type of the elements in the list. Must implement the
traits `Stringable` and `CollectionElement`.
traits `Representable` and `CollectionElement`.
Returns:
A string representation of the list.
Expand All @@ -605,7 +607,7 @@ struct List[T: CollectionElement](CollectionElement, Sized, Boolable):
var result = String(List[Int8](capacity=minimum_capacity))
result += "["
for i in range(len(self)):
result += str(self[i])
result += repr(self[i])
if i < len(self) - 1:
result += ", "
result += "]"
Expand Down
18 changes: 18 additions & 0 deletions stdlib/test/builtin/test_int.mojo
Expand Up @@ -69,6 +69,22 @@ def test_mod():
assert_equal(1, Int(-3) % Int(2))


def test_string_conversion():
assert_equal(str(Int(3)), "3")
assert_equal(str(Int(-3)), "-3")
assert_equal(str(Int(0)), "0")
assert_equal(str(Int(100)), "100")
assert_equal(str(Int(-100)), "-100")


def test_int_representation():
assert_equal(repr(Int(3)), "3")
assert_equal(repr(Int(-3)), "-3")
assert_equal(repr(Int(0)), "0")
assert_equal(repr(Int(100)), "100")
assert_equal(repr(Int(-100)), "-100")


def main():
test_constructors()
test_properties()
Expand All @@ -78,3 +94,5 @@ def main():
test_pow()
test_floordiv()
test_mod()
test_string_conversion()
test_int_representation()
53 changes: 53 additions & 0 deletions stdlib/test/builtin/test_repr.mojo
@@ -0,0 +1,53 @@
# ===----------------------------------------------------------------------=== #
# Copyright (c) 2024, Modular Inc. All rights reserved.
#
# Licensed under the Apache License v2.0 with LLVM Exceptions:
# https://llvm.org/LICENSE.txt
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ===----------------------------------------------------------------------=== #
# RUN: %mojo-no-debug %s

from testing import assert_equal


@value
struct Dog(Representable):
var name: String
var age: Int

fn __repr__(self) -> String:
return "Dog(name=" + repr(self.name) + ", age=" + repr(self.age) + ")"


def test_explicit_conformance():
dog = Dog(name="Fido", age=3)
assert_equal(repr(dog), "Dog(name='Fido', age=3)")


@value
struct Cat:
var name: String
var age: Int

fn __repr__(self) -> String:
return "Cat(name=" + repr(self.name) + ", age=" + repr(self.age) + ")"


def test_implicit_conformance():
cat = Cat(name="Whiskers", age=2)
assert_equal(repr(cat), "Cat(name='Whiskers', age=2)")


def test_none_representation():
assert_equal(repr(None), "None")


def main():
test_explicit_conformance()
test_implicit_conformance()
test_none_representation()
7 changes: 7 additions & 0 deletions stdlib/test/builtin/test_string.mojo
Expand Up @@ -43,6 +43,12 @@ fn test_stringable() raises:
assert_equal("a string", str(String(AString())))


fn test_representable() raises:
assert_equal(repr(String("hello")), "'hello'")
assert_equal(repr(String(0)), "'0'")
assert_equal(repr(String("Some' quotes' ")), "'Some\\' quotes\\' '")


fn test_constructors() raises:
# Default construction
assert_equal(0, len(String()))
Expand Down Expand Up @@ -614,6 +620,7 @@ def main():
test_equality_operators()
test_add()
test_stringable()
test_representable()
test_string_join()
test_stringref()
test_stringref_from_dtypepointer()
Expand Down
2 changes: 1 addition & 1 deletion stdlib/test/collections/test_dict.mojo
Expand Up @@ -71,7 +71,7 @@ def test_dict_string_representation_string_int():
some_dict._minimum_size_of_string_representation()
<= len(dict_as_string)
)
assert_equal(dict_as_string, "{a: 1, b: 2}")
assert_equal(dict_as_string, "{'a': 1, 'b': 2}")


def test_dict_string_representation_int_int():
Expand Down
9 changes: 3 additions & 6 deletions stdlib/test/collections/test_list.mojo
Expand Up @@ -575,13 +575,10 @@ def test_converting_list_to_string():
var my_list = List[Int](1, 2, 3)
assert_equal(__type_of(my_list).__str__(my_list), "[1, 2, 3]")

var my_list2 = List[SIMD[DType.int8, 2]](
SIMD[DType.int8, 2](1, 2), SIMD[DType.int8, 2](3, 4)
var my_list4 = List[String]("a", "b", "c", "foo")
assert_equal(
__type_of(my_list4).__str__(my_list4), "['a', 'b', 'c', 'foo']"
)
assert_equal(__type_of(my_list2).__str__(my_list2), "[[1, 2], [3, 4]]")

var my_list3 = List[Float64](1.0, 2.0, 3.0)
assert_equal(__type_of(my_list3).__str__(my_list3), "[1.0, 2.0, 3.0]")


def main():
Expand Down

0 comments on commit 0409719

Please sign in to comment.