# This file is part of NIT ( http://www.nitlanguage.org ). # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # 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. # Refine `Serializable::inspect` to show more useful information module inspect import serialization_core private import caching private fun inspect_testing: Bool do return "NIT_TESTING".environ == "true" # Serialization engine writing the object attributes to strings private class InspectSerializer super CachingSerializer # Target writing stream var stream: Writer redef var current_object = null var first_object: nullable Object = null redef fun serialize(object) do if object == null then stream.write "null" else if current_object == null then first_object = object end var last_object = current_object current_object = object object.accept_inspect_serializer self current_object = last_object end end var first_attribute_serialized = false redef fun serialize_attribute(name, value) do if first_attribute_serialized then stream.write ", " else stream.write " " first_attribute_serialized = true end stream.write name stream.write ":" super end redef fun serialize_reference(object) do if cache.has_object(object) then # Cycle var id = object.object_id if inspect_testing then id = cache.id_for(object) stream.write "<" stream.write object.class_name stream.write "#" stream.write id.to_s stream.write ">" else if object != first_object and (not object isa DirectSerializable) then # Another object, print class and id only var id = object.object_id if inspect_testing then id = cache.new_id_for(object) stream.write "<" stream.write object.class_name stream.write "#" stream.write id.to_s stream.write ">" else # Main object serialize object end end end redef class Serializable # Improve the default inspection reading serializable attributes # # Simple immutable data are inspected as they would be written in Nit code. # # ~~~ # assert 123.inspect == "123" # assert 1.5.inspect == "1.5" # assert 0xa1u8.inspect == "0xa1u8" # assert 'c'.inspect == "'c'" # assert "asdf\n".inspect == "\"asdf\\n\"" # ~~~ # # Inspections of mutable serializable objects show their dynamic type, # their `object_id` and their first level attributes. When testing, # the `object_id` is replaced by an id unique to each call to `inspect`. # # ~~~ # class MyClass # serialize # # var i: Int # var o: nullable Object # end # # var class_with_null = new MyClass(123) # assert class_with_null.to_s == class_with_null.inspect # assert class_with_null.to_s == "" # # var class_with_other = new MyClass(456, class_with_null) # assert class_with_other.to_s == ">" # # var class_with_cycle = new MyClass(789) # class_with_cycle.o = class_with_cycle # assert class_with_cycle.to_s == ">" # ~~~ # # Items of collections are flattened and appended to the output. # # ~~~ # assert [1, 2, 3].inspect == "" # # var set = new HashSet[Object].from([1, 1.5, "two": Object]) # assert set.inspect == """""" # # var map = new Map[Int, String] # map[1] = "one" # map[2] = "two" # assert map.inspect == """""" # ~~~ # # Inspections producing over 80 characters are cut short. # # ~~~ # var long_class = new MyClass(123456789, "Some " + "very "*8 + "long string") # assert long_class.to_s == "" # ~~~ redef fun inspect do var stream = new StringWriter var serializer = new InspectSerializer(stream) serializer.serialize self stream.close var str = stream.to_s # Cut long inspects var max_length = 80 if str.length > max_length then str = str.substring(0, max_length-2) + "…>" end return str end private fun accept_inspect_serializer(v: InspectSerializer) do v.stream.write "<" v.stream.write class_name v.stream.write "#" var id = object_id if inspect_testing then id = v.cache.new_id_for(self) v.stream.write id.to_s accept_inspect_serializer_core v v.stream.write ">" end private fun accept_inspect_serializer_core(v: InspectSerializer) do v.serialize_core(self) end redef class Int redef fun accept_inspect_serializer(v) do v.stream.write to_s end redef class Float redef fun accept_inspect_serializer(v) do v.stream.write to_s end redef class Bool redef fun accept_inspect_serializer(v) do v.stream.write to_s end redef class Char redef fun accept_inspect_serializer(v) do v.stream.write "'" v.stream.write to_s.escape_to_nit v.stream.write "'" end end redef class Byte redef fun accept_inspect_serializer(v) do v.stream.write to_s v.stream.write "u8" end end redef class CString redef fun accept_inspect_serializer_core(v) do v.stream.write " \"" v.stream.write to_s.escape_to_nit v.stream.write_char '"' end end redef class Text redef fun accept_inspect_serializer(v) do v.stream.write "\"" v.stream.write escape_to_nit v.stream.write "\"" end end redef class Collection[E] private fun serialize_as_inspect(v: InspectSerializer) do v.stream.write "[" var is_first = true for e in self do if is_first then is_first = false else v.stream.write ", " end if not v.try_to_serialize(e) then assert e != null v.stream.write e.inspect end end v.stream.write "]" end end redef class SimpleCollection[E] redef fun accept_inspect_serializer_core(v) do v.stream.write " " serialize_as_inspect v end end redef class Map[K, V] redef fun accept_inspect_serializer_core(v) do v.stream.write " \{" var first = true for key, val in self do if not first then v.stream.write ", " else first = false if not v.try_to_serialize(key) then assert key != null v.stream.write key.inspect end v.stream.write ":" if not v.try_to_serialize(val) then assert val != null v.stream.write val.inspect end end v.stream.write "\}" end end