1 /**
2  * Dynamic Array
3  * Copyright: © 2015 Economic Modeling Specialists, Intl.
4  * Authors: Brian Schott
5  * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
6  */
7 
8 module libwasm.rt.array;
9 import libwasm.rt.memory;
10 import libwasm.rt.allocator;
11 
12 @safe:
13 nothrow:
14 
15 /* an optimized version of DynamicArray when T is an pointer
16  *
17  * to reduce template bloat all PointerArray!T* are backed by a
18  * DynamicArray!(void*)
19  */
20 struct PointerArray(T, Allocator = ThreadMemAllocator) if (is(T : U*, U))
21 {
22 	private DynamicArray!(void*, Allocator) array;
23 
24 	alias array this;
25 
26 	/// Slice operator overload
27 	pragma(inline, true) auto opSlice(this This)() @nogc
28 	{
29 		return opSlice!(This)(0, l);
30 	}
31 
32 	/// ditto
33 	pragma(inline, true) auto opSlice(this This)(size_t a, size_t b) @nogc @trusted
34 	{
35 		alias ET = ContainerElementType!(This, T);
36 		return cast(ET[]) array.arr[a .. b];
37 	}
38 
39 	/// Index operator overload
40 	pragma(inline, true) auto opIndex(this This)(size_t i) @nogc
41 	{
42 		return cast(T) array[i];
43 	}
44 
45 	void insertBack(T value)
46 	{
47 		array.insertBack(cast(void*) value);
48 	}
49 
50 	void shrinkTo(int idx)
51 	{
52 		array.shrinkTo(idx);
53 	}
54 
55 	alias insert = insertBack;
56 
57 	/// ditto
58 	alias insertAnywhere = insertBack;
59 
60 	/// ditto
61 	alias put = insertBack;
62 
63 	/// Index assignment support
64 	void opIndexAssign(T value, size_t i) @nogc
65 	{
66 		array.arr[i] = cast(void*) value;
67 	}
68 
69 	/// Slice assignment support
70 	void opSliceAssign(T value) @nogc
71 	{
72 		array.arr[0 .. l] = cast(void*) value;
73 	}
74 
75 	/// ditto
76 	void opSliceAssign(T value, size_t i, size_t j) @nogc
77 	{
78 		array.arr[i .. j] = cast(void*) value;
79 	}
80 
81 	auto ref T front() pure @property @trusted
82 	{
83 		return cast(T) array.front();
84 	}
85 
86 	/// Returns: the back element of the DynamicArray.
87 	auto ref T back() pure @property @trusted
88 	{
89 		return cast(T) array.back();
90 	}
91 
92 	size_t length() const nothrow pure @property @safe @nogc
93 	{
94 		return array.l;
95 	}
96 	/// Ditto
97 	alias opDollar = length;
98 
99 	void remove(const size_t i)
100 	{
101 		array.remove(i);
102 	}
103 }
104 /**
105  * Array that is able to grow itself when items are appended to it. Uses
106  * malloc/free/realloc to manage its storage.
107  *
108  * Params:
109  *     T = the array element type
110  *     Allocator = the allocator to use. Defaults to `Mallocator`.
111  */
112 
113 struct DynamicArray(T, Allocator = ThreadMemAllocator)
114 {
115 	static Allocator allocator;
116 
117 	this(this) @disable;
118 
119 	/**
120 	Returns the size in bytes of the state that needs to be allocated to hold an
121 	object of type $(D T). $(D stateSize!T) is zero for $(D struct)s that are not
122 	nested and have no nonstatic member variables.
123 	*/
124 	template stateSize(T)
125 	{
126 		import std.traits;
127 
128 		static if (is(T == class) || is(T == interface))
129 			enum stateSize = __traits(classInstanceSize, T);
130 		else static if (is(T == struct) || is(T == union))
131 			enum stateSize = Fields!T.length || isNested!T ? T.sizeof : 0;
132 		else static if (is(T == void))
133 			enum size_t stateSize = 0;
134 		else
135 			enum stateSize = T.sizeof;
136 	}
137 
138 	static if (is(typeof((T[] a, const T[] b) => a[0 .. b.length] = b[0 .. $])))
139 	{
140 		/// Either `const(T)` or `T`.
141 		alias AppendT = const(T);
142 
143 		/// Either `const(typeof(this))` or `typeof(this)`.
144 		alias AppendTypeOfThis = const(typeof(this));
145 	}
146 	else
147 	{
148 		alias AppendT = T;
149 		alias AppendTypeOfThis = typeof(this);
150 	}
151 
152 	@trusted ~this()
153 	{
154 		if (arr is null)
155 			return;
156 
157 		static if ((is(T == struct) || is(T == union))
158 			&& __traits(hasMember, T, "__xdtor"))
159 		{
160 			foreach (ref item; arr[0 .. l])
161 			{
162 				item.__xdtor();
163 			}
164 		}
165 		allocator.deallocate(arr);
166 	}
167 
168 	/// Slice operator overload
169 	pragma(inline, true)
170 	auto opSlice(this This)() @nogc
171 	{
172 		return opSlice!(This)(0, l);
173 	}
174 
175 	/// ditto
176 	pragma(inline, true)
177 	auto opSlice(this This)(size_t a, size_t b) @nogc
178 	{
179 		alias ET = ContainerElementType!(This, T);
180 		return cast(ET[]) arr[a .. b];
181 	}
182 
183 	/// Index operator overload
184 	pragma(inline, true)
185 	auto opIndex(this This)(size_t i) @nogc
186 	{
187 		return opSlice!(This)(i, i + 1)[0];
188 	}
189 
190 	/**
191 	 * Inserts the given value into the end of the array.
192 	 */
193 	@trusted void insertBack(T value)
194 	{
195 
196 		if (arr.length == 0)
197 		{
198 			immutable size_t c = 4;
199 			void[] a = allocator.allocate(c * T.sizeof);
200 			arr = cast(typeof(arr)) a;
201 		}
202 		else if (l >= arr.length)
203 		{
204 			immutable size_t c = arr.length > 512 ? arr.length + 1024 : arr.length << 1;
205 			void[] a = cast(void[]) arr;
206 			void[] b = allocator.reallocate(a, c * T.sizeof);
207 			arr = cast(typeof(arr)) b;
208 		}
209 		import std.traits : hasElaborateAssign, hasElaborateDestructor;
210 
211 		static if (is(T == struct) && (hasElaborateAssign!T || hasElaborateDestructor!T))
212 		{
213 			// If a destructor is run before blit or assignment involves
214 			// more than just a blit, ensure that arr[l] is in a valid
215 			// state before assigning to it.
216 			import core.stdc.string : memcpy, memset;
217 
218 			const init = typeid(T).initializer();
219 			if (init.ptr is null) // null pointer means initialize to 0s
220 					(() @trusted => memset(arr.ptr + l, 0, T.sizeof))();
221 			else
222 						(() @trusted => memcpy(arr.ptr + l, init.ptr, T.sizeof))();
223 		}
224 		emplace(arr[l++], value);
225 	}
226 
227 	/// ditto
228 	alias insert = insertBack;
229 
230 	/// ditto
231 	alias insertAnywhere = insertBack;
232 
233 	/// ditto
234 	alias put = insertBack;
235 
236 	/**
237 	 * ~= operator overload
238 	 */
239 	scope ref typeof(this) opOpAssign(string op)(T value) if (op == "~")
240 	{
241 		insert(value);
242 		return this;
243 	}
244 
245 	/**
246 	* ~= operator overload for an array of items
247 	*/
248 	scope ref typeof(this) opOpAssign(string op, bool checkForOverlap = true)(AppendT[] rhs)
249 			if (op == "~" && !is(T == AppendT[]))
250 	{
251 		// Disabling checkForOverlap when this function is called from opBinary!"~"
252 		// is not just for efficiency, but to avoid circular function calls that
253 		// would prevent inference of @nogc, etc.
254 		static if (checkForOverlap)
255 			if ((()@trusted => arr.ptr <= rhs.ptr && arr.ptr + arr.length > rhs.ptr)())
256 				{
257 				// Special case where rhs is a slice of this array.
258 				this = this ~ rhs;
259 				return this;
260 			}
261 		reserve(l + rhs.length);
262 		import std.traits : hasElaborateAssign, hasElaborateDestructor;
263 
264 		static if (is(T == struct) && (hasElaborateAssign!T || hasElaborateDestructor!T))
265 		{
266 			foreach (ref value; rhs)
267 				emplace(arr[l++], value);
268 		}
269 		else
270 		{
271 			arr[l .. l + rhs.length] = rhs[0 .. rhs.length];
272 			l += rhs.length;
273 		}
274 		return this;
275 	}
276 
277 	/// ditto
278 	scope ref typeof(this) opOpAssign(string op)(ref AppendTypeOfThis rhs)
279 			if (op == "~")
280 	{
281 		return this ~= rhs.arr[0 .. rhs.l];
282 	}
283 
284 	/**
285 	 * ~ operator overload
286 	 */
287 	typeof(this) opBinary(string op)(ref AppendTypeOfThis other) if (op == "~")
288 	{
289 		typeof(this) ret;
290 		ret.reserve(l + other.l);
291 		ret.opOpAssign!("~", false)(arr[0 .. l]);
292 		ret.opOpAssign!("~", false)(other.arr[0 .. other.l]);
293 		return ret;
294 	}
295 
296 	/// ditto
297 	typeof(this) opBinary(string op)(AppendT[] values) if (op == "~")
298 	{
299 		typeof(this) ret;
300 		ret.reserve(l + values.length);
301 		ret.opOpAssign!("~", false)(arr[0 .. l]);
302 		ret.opOpAssign!("~", false)(values);
303 		return ret;
304 	}
305 
306 	/**
307 	 * Ensures sufficient capacity to accommodate `n` elements.
308 	 */
309 	@trusted void reserve(size_t n)
310 	{
311 		if (arr.length >= n)
312 			return;
313 		if (arr.ptr is null)
314 		{
315 			size_t c = 4;
316 			if (c < n)
317 				c = n;
318 			void[] a = allocator.allocate(c * T.sizeof);
319 			arr = cast(typeof(arr)) a;
320 		}
321 		else
322 		{
323 			size_t c = arr.length > 512 ? arr.length + 1024 : arr.length << 1;
324 			if (c < n)
325 				c = n;
326 			void[] a = cast(void[]) arr;
327 			allocator.reallocate(a, c * T.sizeof);
328 			arr = cast(typeof(arr)) a;
329 		}
330 	}
331 
332 	static if (is(typeof({ T value; }))) // default construction is allowed
333 	{
334 			/**
335 		 * Change the array length.
336 		 * When growing, initialize new elements to the default value.
337 		 */
338 			void resize(size_t n)
339 			{
340 				resize(n, T.init);
341 			}
342 		}
343 
344 	/**
345 	 * Change the array length.
346 	 * When growing, initialize new elements to the given value.
347 	 */
348 	void resize(size_t n, T value)
349 	{
350 		if (arr.length < n)
351 			reserve(n);
352 
353 		if (l < n) // Growing?
354 		{
355 			import std.traits : hasElaborateAssign, hasElaborateDestructor;
356 
357 			static if (is(T == struct) && (hasElaborateAssign!T || hasElaborateDestructor!T))
358 			{
359 				foreach (i; l .. n)
360 					emplace(arr[l], value);
361 			}
362 			else
363 				arr[l .. n] = value;
364 		}
365 		else
366 		{
367 			static if ((is(T == struct) || is(T == union))
368 				&& __traits(hasMember, T, "__xdtor"))
369 			{
370 				foreach (i; n .. l)
371 					arr[i].__xdtor();
372 			}
373 		}
374 
375 		l = n;
376 	}
377 
378 	/**
379 	 * Remove the item at the given index from the array.
380 	 */
381 	void remove(const size_t i)
382 	{
383 		assert(i < this.l);
384 		auto next = i + 1;
385 		while (next < this.l)
386 		{
387 			arr[next - 1] = arr[next];
388 			++next;
389 		}
390 
391 		--l;
392 		static if ((is(T == struct) || is(T == union)) && __traits(hasMember, T, "__xdtor"))
393 		{
394 			arr[l].__xdtor();
395 		}
396 	}
397 
398 	/**
399 	 * Removes the last element from the array.
400 	 */
401 	void removeBack()
402 	{
403 		assert(l > 0);
404 		--l;
405 		static if ((is(T == struct) || is(T == union)) && __traits(hasMember, T, "__xdtor"))
406 		{
407 			arr[l].__xdtor();
408 		}
409 	}
410 
411 	void shrinkTo(const size_t nl)
412 	{
413 		assert(l >= nl);
414 		static if ((is(T == struct) || is(T == union)) && __traits(hasMember, T, "__xdtor"))
415 		{
416 			foreach_reverse (i; nl .. l)
417 				arr[i].__xdtor();
418 		}
419 
420 		l = nl;
421 	}
422 
423 	/// Index assignment support
424 	void opIndexAssign(T value, size_t i) @nogc
425 	{
426 		arr[i] = value;
427 	}
428 
429 	/// Slice assignment support
430 	void opSliceAssign(T value) @nogc
431 	{
432 		arr[0 .. l] = value;
433 	}
434 
435 	/// ditto
436 	void opSliceAssign(T value, size_t i, size_t j) @nogc
437 	{
438 		arr[i .. j] = value;
439 	}
440 
441 	/// Returns: the number of items in the array
442 	size_t length() const nothrow pure @property @safe @nogc
443 	{
444 		return l;
445 	}
446 
447 	/// Ditto
448 	alias opDollar = length;
449 
450 	/// Returns: whether or not the DynamicArray is empty.
451 	bool empty() const nothrow pure @property @safe @nogc
452 	{
453 		return l == 0;
454 	}
455 
456 	/**
457 	 * Returns: a slice to the underlying array.
458 	 *
459 	 * As the memory of the array may be freed, access to this array is
460 	 * highly unsafe.
461 	 */
462 	auto ptr(this This)() @nogc @property
463 	{
464 		alias ET = ContainerElementType!(This, T);
465 		return cast(ET) arr.ptr;
466 	}
467 
468 	/// Returns: the front element of the DynamicArray.
469 	auto ref T front() pure @property
470 	{
471 		return arr[0];
472 	}
473 
474 	/// Returns: the back element of the DynamicArray.
475 	auto ref T back() pure @property
476 	{
477 		return arr[l - 1];
478 	}
479 
480 private:
481 
482 	@trusted static void emplace(ref ContainerStorageType!T target, ref AppendT source)
483 	{
484 		(cast(void[])((&target)[0 .. 1]))[] = cast(void[])((&source)[0 .. 1]);
485 		static if (__traits(hasMember, T, "__xpostblit"))
486 			target.__xpostblit();
487 	}
488 
489 	enum bool useGC = false;
490 	ContainerStorageType!(T)[] arr;
491 	size_t l;
492 }
493 
494 template removePred(alias pred)
495 {
496 	auto removePred(T)(ref DynamicArray!T arr)
497 	{
498 		size_t target = 0;
499 		auto len = arr.l < arr.arr.length ? arr.l : arr.arr.length;
500 		foreach (i; 0 .. len)
501 		{
502 			if (pred(arr.arr[i]))
503 			{
504 				static if ((is(T == struct) || is(T == union)) && __traits(hasMember, T, "__xdtor"))
505 				{
506 					arr.arr[l].__xdtor();
507 				}
508 				continue;
509 			}
510 			else
511 			{
512 				arr.arr[target] = arr.arr[i];
513 				target++;
514 			}
515 		}
516 		arr.l = target;
517 	}
518 }
519 
520 template ContainerStorageType(T)
521 {
522 	import std.traits : hasElaborateCopyConstructor, hasElaborateDestructor,
523 		hasElaborateAssign, isBasicType, isDynamicArray, isPointer, Unqual;
524 	import std.typecons : Rebindable;
525 
526 	static if (is(T == const) || is(T == immutable))
527 	{
528 		static if (isBasicType!T || isDynamicArray!T || isPointer!T)
529 			alias ContainerStorageType = Unqual!T;
530 		else static if (is(T == class) || is(T == interface))
531 			alias ContainerStorageType = Rebindable!T;
532 		else static if (is(T == struct))
533 		{
534 			alias U = Unqual!T;
535 			static if (hasElaborateAssign!U || hasElaborateCopyConstructor!U || hasElaborateDestructor!U)
536 				static assert(false, "Cannot store " ~ T.stringof ~ " because of postblit, opAssign, or ~this");
537 			else
538 				alias ContainerStorageType = U;
539 		}
540 		else
541 			static assert(false, "Don't know how to handle type " ~ T.stringof);
542 	}
543 	else
544 		alias ContainerStorageType = T;
545 }
546 
547 ///
548 unittest
549 {
550 	static assert(is(ContainerStorageType!(int) == int));
551 	static assert(is(ContainerStorageType!(const int) == int));
552 }
553 
554 ///
555 unittest
556 {
557 	import std.typecons : Rebindable;
558 
559 	static assert(is(ContainerStorageType!(Object) == Object));
560 	static assert(is(ContainerStorageType!(const(Object)) == Rebindable!(const(Object))));
561 }
562 
563 ///
564 unittest
565 {
566 	struct A
567 	{
568 		int foo;
569 	}
570 
571 	struct B
572 	{
573 		void opAssign(typeof(this))
574 		{
575 			this.foo *= 2;
576 		}
577 
578 		int foo;
579 	}
580 
581 	// A can be stored easily because it is plain data
582 	static assert(is(ContainerStorageType!(A) == A));
583 	static assert(is(ContainerStorageType!(const(A)) == A));
584 
585 	// const(B) cannot be stored in the container because of its
586 	// opAssign. Casting away const could lead to some very unexpected
587 	// behavior.
588 	static assert(!is(typeof(ContainerStorageType!(const(B)))));
589 	// Mutable B is not a problem
590 	static assert(is(ContainerStorageType!(B) == B));
591 
592 	// Arrays can be stored because the entire pointer-length pair is moved as
593 	// a unit.
594 	static assert(is(ContainerStorageType!(const(int[])) == const(int)[]));
595 }
596 
597 ///
598 unittest
599 {
600 	static assert(is(ContainerStorageType!(const(int*)) == const(int)*));
601 }
602 
603 template ContainerElementType(ContainerType, ElementType)
604 {
605 	import std.traits : isMutable, hasIndirections, PointerTarget, isPointer, Unqual;
606 
607 	template ET(bool isConst, T)
608 	{
609 		static if (isPointer!ElementType)
610 		{
611 			enum PointerIsConst = is(ElementType == const);
612 			enum PointerIsImmutable = is(ElementType == immutable);
613 			enum DataIsConst = is(PointerTarget!(ElementType) == const);
614 			enum DataIsImmutable = is(PointerTarget!(ElementType) == immutable);
615 			static if (isConst)
616 			{
617 				static if (PointerIsConst)
618 					alias ET = ElementType;
619 				else static if (PointerIsImmutable)
620 					alias ET = ElementType;
621 				else
622 					alias ET = const(PointerTarget!(ElementType))*;
623 			}
624 			else
625 			{
626 				static assert(DataIsImmutable, "An immutable container cannot reference const or mutable data");
627 				static if (PointerIsConst)
628 					alias ET = immutable(PointerTarget!ElementType)*;
629 				else
630 					alias ET = ElementType;
631 			}
632 		}
633 		else
634 		{
635 			static if (isConst)
636 			{
637 				static if (is(ElementType == immutable))
638 					alias ET = ElementType;
639 				else
640 					alias ET = const(Unqual!ElementType);
641 			}
642 			else
643 				alias ET = immutable(Unqual!ElementType);
644 		}
645 	}
646 
647 	static if (isMutable!ContainerType)
648 		alias ContainerElementType = ElementType;
649 	else
650 	{
651 		static if (hasIndirections!ElementType)
652 			alias ContainerElementType = ET!(is(ContainerType == const), ElementType);
653 		else
654 			alias ContainerElementType = ElementType;
655 	}
656 }
657 
658 struct StringAppender(Allocator)
659 {
660 	DynamicArray!(char, Allocator) arr = void;
661 	this(ref Allocator allocator)
662 	{
663 		arr = DynamicArray!(char, Allocator)(allocator);
664 	}
665 
666 	alias arr this;
667 	void put(string s)
668 	{
669 		foreach (c; s)
670 			arr.put(c);
671 	}
672 
673 	void put(char c)
674 	{
675 		arr.put(c);
676 	}
677 
678 	void put(char[] cs)
679 	{
680 		foreach (c; cs)
681 			arr.put(c);
682 	}
683 }
684 
685 @trusted string text(Allocator, T...)(ref Allocator allocator, T t)
686 {
687 	auto app = StringAppender!(Allocator)(allocator);
688 	write(app, t);
689 	auto end = app.length;
690 	return cast(string) app[0 .. end];
691 }
692 
693 @trusted string text(T...)(T t)
694 {
695 	StringAppender!() app;
696 	write(app, t);
697 	auto end = app.length;
698 	return cast(string) app[0 .. end];
699 }
700 
701 char[] unsignedToTempString()(ulong value, return scope char[] buf, uint radix = 10) @safe
702 {
703 	if (radix < 2) // not a valid radix, just return an empty string
704 		return buf[$ .. $];
705 
706 	size_t i = buf.length;
707 	do
708 	{
709 		if (value < radix)
710 		{
711 			ubyte x = cast(ubyte) value;
712 			buf[--i] = cast(char)((x < 10) ? x + '0' : x - 10 + 'a');
713 			break;
714 		}
715 		else
716 		{
717 			ubyte x = cast(ubyte)(value % radix);
718 			value = value / radix;
719 			buf[--i] = cast(char)((x < 10) ? x + '0' : x - 10 + 'a');
720 		}
721 	}
722 	while (value);
723 	return buf[i .. $];
724 }
725 
726 private struct TempStringNoAlloc
727 {
728 	// need to handle 65 bytes for radix of 2 with negative sign.
729 	private char[65] _buf = 0;
730 	private ubyte _len;
731 	auto get() return
732 	{
733 		return _buf[$ - _len .. $];
734 	}
735 
736 	alias get this;
737 }
738 
739 auto unsignedToTempString()(ulong value, uint radix = 10) @safe
740 {
741 	TempStringNoAlloc result = void;
742 	result._len = unsignedToTempString(value, result._buf, radix).length & 0xff;
743 	return result;
744 }
745 
746 char[] signedToTempString(long value, return scope char[] buf, uint radix = 10) @safe
747 {
748 	bool neg = value < 0;
749 	if (neg)
750 		value = cast(ulong)-value;
751 	auto r = unsignedToTempString(value, buf, radix);
752 	if (neg)
753 	{
754 		// about to do a slice without a bounds check
755 		auto trustedSlice(return char[] r) @trusted
756 		{
757 			assert(r.ptr > buf.ptr);
758 			return (r.ptr - 1)[0 .. r.length + 1];
759 		}
760 
761 		r = trustedSlice(r);
762 		r[0] = '-';
763 	}
764 	return r;
765 }
766 
767 auto signedToTempString(long value, uint radix = 10) @safe
768 {
769 	bool neg = value < 0;
770 	if (neg)
771 		value = cast(ulong)-value;
772 	auto r = unsignedToTempString(value, radix);
773 	if (neg)
774 	{
775 		r._len++;
776 		r.get()[0] = '-';
777 	}
778 	return r;
779 }
780 
781 import std.traits : isIntegral, isArray;
782 
783 // TODO: std.range.put doesn't do scope on second args therefor compiler thinks buf escapes. resolve it and we can avoid the @trusted
784 @trusted
785 void toTextRange(T, W)(T value, auto ref W writer) if (isIntegral!T && isArray!T)
786 {
787 	import core.internal.string : SignedStringBuf,
788 		UnsignedStringBuf;
789 
790 	if (value < 0)
791 	{
792 		SignedStringBuf buf = void;
793 		writer.put(signedToTempString(value, buf, 10));
794 	}
795 	else
796 	{
797 		UnsignedStringBuf buf = void;
798 		writer.put(unsignedToTempString(value, buf, 10));
799 	}
800 }
801 
802 void write(Sink, S...)(auto ref Sink sink, S args)
803 {
804 	import std.traits : isBoolean, isIntegral, isAggregateType, isSomeString, isSomeChar;
805 
806 	foreach (arg; args)
807 	{
808 		alias A = typeof(arg);
809 		static if (isAggregateType!A || is(A == enum))
810 		{
811 			sink.put(arg.toString());
812 		}
813 		else static if (isSomeString!A)
814 		{
815 			sink.put(arg);
816 		}
817 		else static if (isIntegral!A)
818 		{
819 			toTextRange(arg, sink);
820 		}
821 		else static if (isBoolean!A)
822 		{
823 			sink.put(arg ? "true" : "false");
824 		}
825 		else static if (isSomeChar!A)
826 		{
827 			sink.put(arg);
828 		}
829 		else
830 		{
831 			static assert(0);
832 		}
833 	}
834 }