ci: tests for macOS on Gitlab CI
[nit.git] / lib / serialization / inspect.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 # Refine `Serializable::inspect` to show more useful information
16 module inspect
17
18 import serialization_core
19 private import caching
20
21 private fun inspect_testing: Bool do return "NIT_TESTING".environ == "true"
22
23 # Serialization engine writing the object attributes to strings
24 private class InspectSerializer
25 super CachingSerializer
26
27 # Target writing stream
28 var stream: Writer
29
30 redef var current_object = null
31
32 var first_object: nullable Object = null
33
34 redef fun serialize(object)
35 do
36 if object == null then
37 stream.write "null"
38 else
39 if current_object == null then
40 first_object = object
41 end
42
43 var last_object = current_object
44 current_object = object
45 object.accept_inspect_serializer self
46 current_object = last_object
47 end
48 end
49
50 var first_attribute_serialized = false
51
52 redef fun serialize_attribute(name, value)
53 do
54 if first_attribute_serialized then
55 stream.write ", "
56 else
57 stream.write " "
58 first_attribute_serialized = true
59 end
60
61 stream.write name
62 stream.write ":"
63
64 super
65 end
66
67 redef fun serialize_reference(object)
68 do
69 if cache.has_object(object) then
70 # Cycle
71 var id = object.object_id
72 if inspect_testing then id = cache.id_for(object)
73
74 stream.write "<"
75 stream.write object.class_name
76 stream.write "#"
77 stream.write id.to_s
78 stream.write ">"
79 else if object != first_object and (not object isa DirectSerializable) then
80 # Another object, print class and id only
81 var id = object.object_id
82 if inspect_testing then id = cache.new_id_for(object)
83
84 stream.write "<"
85 stream.write object.class_name
86 stream.write "#"
87 stream.write id.to_s
88 stream.write ">"
89 else
90 # Main object
91 serialize object
92 end
93 end
94 end
95
96 redef class Serializable
97
98 # Improve the default inspection reading serializable attributes
99 #
100 # Simple immutable data are inspected as they would be written in Nit code.
101 #
102 # ~~~
103 # assert 123.inspect == "123"
104 # assert 1.5.inspect == "1.5"
105 # assert 0xa1u8.inspect == "0xa1u8"
106 # assert 'c'.inspect == "'c'"
107 # assert "asdf\n".inspect == "\"asdf\\n\""
108 # ~~~
109 #
110 # Inspections of mutable serializable objects show their dynamic type,
111 # their `object_id` and their first level attributes. When testing,
112 # the `object_id` is replaced by an id unique to each call to `inspect`.
113 #
114 # ~~~
115 # class MyClass
116 # serialize
117 #
118 # var i: Int
119 # var o: nullable Object
120 # end
121 #
122 # var class_with_null = new MyClass(123)
123 # assert class_with_null.to_s == class_with_null.inspect
124 # assert class_with_null.to_s == "<MyClass#0 i:123, o:null>"
125 #
126 # var class_with_other = new MyClass(456, class_with_null)
127 # assert class_with_other.to_s == "<MyClass#0 i:456, o:<MyClass#1>>"
128 #
129 # var class_with_cycle = new MyClass(789)
130 # class_with_cycle.o = class_with_cycle
131 # assert class_with_cycle.to_s == "<MyClass#0 i:789, o:<MyClass#0>>"
132 # ~~~
133 #
134 # Items of collections are flattened and appended to the output.
135 #
136 # ~~~
137 # assert [1, 2, 3].inspect == "<Array[Int]#0 [1, 2, 3]>"
138 #
139 # var set = new HashSet[Object].from([1, 1.5, "two": Object])
140 # assert set.inspect == """<HashSet[Object]#0 [1, 1.5, "two"]>"""
141 #
142 # var map = new Map[Int, String]
143 # map[1] = "one"
144 # map[2] = "two"
145 # assert map.inspect == """<HashMap[Int, String]#0 {1:"one", 2:"two"}>"""
146 # ~~~
147 #
148 # Inspections producing over 80 characters are cut short.
149 #
150 # ~~~
151 # var long_class = new MyClass(123456789, "Some " + "very "*8 + "long string")
152 # assert long_class.to_s == "<MyClass#0 i:123456789, o:\"Some very very very very very very very very long s…>"
153 # ~~~
154 redef fun inspect
155 do
156 var stream = new StringWriter
157 var serializer = new InspectSerializer(stream)
158 serializer.serialize self
159 stream.close
160 var str = stream.to_s
161
162 # Cut long inspects
163 var max_length = 80
164 if str.length > max_length then
165 str = str.substring(0, max_length-2) + "…>"
166 end
167
168 return str
169 end
170
171 private fun accept_inspect_serializer(v: InspectSerializer)
172 do
173 v.stream.write "<"
174
175 v.stream.write class_name
176 v.stream.write "#"
177
178 var id = object_id
179 if inspect_testing then id = v.cache.new_id_for(self)
180 v.stream.write id.to_s
181
182 accept_inspect_serializer_core v
183
184 v.stream.write ">"
185 end
186
187 private fun accept_inspect_serializer_core(v: InspectSerializer)
188 do v.serialize_core(self)
189 end
190
191 redef class Int
192 redef fun accept_inspect_serializer(v) do v.stream.write to_s
193 end
194
195 redef class Float
196 redef fun accept_inspect_serializer(v) do v.stream.write to_s
197 end
198
199 redef class Bool
200 redef fun accept_inspect_serializer(v) do v.stream.write to_s
201 end
202
203 redef class Char
204 redef fun accept_inspect_serializer(v)
205 do
206 v.stream.write "'"
207 v.stream.write to_s.escape_to_nit
208 v.stream.write "'"
209 end
210 end
211
212 redef class Byte
213 redef fun accept_inspect_serializer(v)
214 do
215 v.stream.write to_s
216 v.stream.write "u8"
217 end
218 end
219
220 redef class CString
221 redef fun accept_inspect_serializer_core(v)
222 do
223 v.stream.write " \""
224 v.stream.write to_s.escape_to_nit
225 v.stream.write_char '"'
226 end
227 end
228
229 redef class Text
230
231 redef fun accept_inspect_serializer(v)
232 do
233 v.stream.write "\""
234 v.stream.write escape_to_nit
235 v.stream.write "\""
236 end
237 end
238
239 redef class Collection[E]
240 private fun serialize_as_inspect(v: InspectSerializer)
241 do
242 v.stream.write "["
243 var is_first = true
244 for e in self do
245 if is_first then
246 is_first = false
247 else
248 v.stream.write ", "
249 end
250
251 if not v.try_to_serialize(e) then
252 assert e != null
253 v.stream.write e.inspect
254 end
255 end
256 v.stream.write "]"
257 end
258 end
259
260 redef class SimpleCollection[E]
261 redef fun accept_inspect_serializer_core(v)
262 do
263 v.stream.write " "
264 serialize_as_inspect v
265 end
266 end
267
268 redef class Map[K, V]
269 redef fun accept_inspect_serializer_core(v)
270 do
271 v.stream.write " \{"
272
273 var first = true
274 for key, val in self do
275 if not first then
276 v.stream.write ", "
277 else first = false
278
279 if not v.try_to_serialize(key) then
280 assert key != null
281 v.stream.write key.inspect
282 end
283
284 v.stream.write ":"
285
286 if not v.try_to_serialize(val) then
287 assert val != null
288 v.stream.write val.inspect
289 end
290 end
291
292 v.stream.write "\}"
293 end
294 end