mirror of https://github.com/icedland/iced.git
590 lines
20 KiB
C#
590 lines
20 KiB
C#
/*
|
|
Copyright (C) 2018-2019 de4dot@gmail.com
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining
|
|
a copy of this software and associated documentation files (the
|
|
"Software"), to deal in the Software without restriction, including
|
|
without limitation the rights to use, copy, modify, merge, publish,
|
|
distribute, sublicense, and/or sell copies of the Software, and to
|
|
permit persons to whom the Software is furnished to do so, subject to
|
|
the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be
|
|
included in all copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.Diagnostics;
|
|
using System.Runtime.CompilerServices;
|
|
|
|
namespace Iced.Intel {
|
|
/// <summary>
|
|
/// A list of <see cref="Instruction"/>s. It's faster than <see cref="List{T}"/> and has
|
|
/// methods to get references to the elements so no <see cref="Instruction"/> gets copied.
|
|
/// Use 'foreach (ref var instr in list)' to use the foreach ref iterator.
|
|
/// </summary>
|
|
[DebuggerDisplay("Count = {Count}")]
|
|
[DebuggerTypeProxy(typeof(InstructionListDebugView))]
|
|
public sealed class InstructionList : IList<Instruction>, IReadOnlyList<Instruction>, IList {
|
|
Instruction[] elements;
|
|
int count;
|
|
|
|
/// <summary>
|
|
/// Gets the number of valid elements
|
|
/// </summary>
|
|
public int Count => count;
|
|
int ICollection<Instruction>.Count => count;
|
|
int ICollection.Count => count;
|
|
int IReadOnlyCollection<Instruction>.Count => count;
|
|
|
|
/// <summary>
|
|
/// Gets the size of the internal array
|
|
/// </summary>
|
|
public int Capacity => elements.Length;
|
|
|
|
bool ICollection<Instruction>.IsReadOnly => false;
|
|
bool IList.IsReadOnly => false;
|
|
|
|
bool IList.IsFixedSize => false;
|
|
bool ICollection.IsSynchronized => false;
|
|
object ICollection.SyncRoot => this;
|
|
|
|
static void ThrowArgumentNullException(string paramName) => throw new ArgumentNullException(paramName);
|
|
static void ThrowArgumentException() => throw new ArgumentException();
|
|
static void ThrowArgumentOutOfRangeException(string paramName) => throw new ArgumentOutOfRangeException(paramName);
|
|
|
|
/// <summary>
|
|
/// Gets a reference to an element. The returned reference is valid until the internal array is resized.
|
|
/// </summary>
|
|
/// <param name="index">Index of element</param>
|
|
/// <returns></returns>
|
|
public ref Instruction this[int index] =>
|
|
// Let the jitter validate the index. Note that this also allows code to accidentally
|
|
// get a reference to an unused element if Count < Capacity; this code won't throw.
|
|
// It's acceptable, this list is used for PERF, use List<Instruction> to throw.
|
|
ref elements[index];
|
|
Instruction IList<Instruction>.this[int index] {
|
|
get => elements[index];
|
|
set => elements[index] = value;
|
|
}
|
|
Instruction IReadOnlyList<Instruction>.this[int index] => elements[index];
|
|
object IList.this[int index] {
|
|
get => elements[index];
|
|
set {
|
|
if (value == null)
|
|
ThrowArgumentNullException(nameof(value));
|
|
if (!(value is Instruction))
|
|
ThrowArgumentException();
|
|
elements[index] = (Instruction)value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructor
|
|
/// </summary>
|
|
public InstructionList() => elements = Array.Empty<Instruction>();
|
|
|
|
/// <summary>
|
|
/// Constructor
|
|
/// </summary>
|
|
/// <param name="capacity">Initial size of the internal array</param>
|
|
public InstructionList(int capacity) {
|
|
if (capacity < 0)
|
|
ThrowArgumentOutOfRangeException(nameof(capacity));
|
|
elements = capacity == 0 ? Array.Empty<Instruction>() : new Instruction[capacity];
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructor
|
|
/// </summary>
|
|
/// <param name="list">List that will be copied to this instance</param>
|
|
public InstructionList(InstructionList list) {
|
|
if (list == null)
|
|
ThrowArgumentNullException(nameof(list));
|
|
int length = list.count;
|
|
if (length == 0)
|
|
elements = Array.Empty<Instruction>();
|
|
else {
|
|
var elements = new Instruction[length];
|
|
this.elements = elements;
|
|
count = length;
|
|
Array.Copy(list.elements, 0, elements, 0, length);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructor
|
|
/// </summary>
|
|
/// <param name="collection">Collection that will be copied to this instance</param>
|
|
public InstructionList(IEnumerable<Instruction> collection) {
|
|
if (collection == null)
|
|
ThrowArgumentNullException(nameof(collection));
|
|
if (collection is ICollection<Instruction> coll) {
|
|
int count = coll.Count;
|
|
if (count == 0)
|
|
elements = Array.Empty<Instruction>();
|
|
else {
|
|
var elements = new Instruction[count];
|
|
this.elements = elements;
|
|
coll.CopyTo(elements, 0);
|
|
this.count = count;
|
|
}
|
|
}
|
|
else {
|
|
elements = Array.Empty<Instruction>();
|
|
foreach (var elem in collection)
|
|
Add(elem);
|
|
}
|
|
}
|
|
|
|
void SetMinCapacity(int minCapacity) {
|
|
var elements = this.elements;
|
|
uint capacity = (uint)elements.Length;
|
|
if (minCapacity <= (int)capacity)
|
|
return;
|
|
const uint DEFAULT_SIZE = 4;// Same as List<Instruction>
|
|
uint newCapacity = capacity * 2;
|
|
if (newCapacity < DEFAULT_SIZE)
|
|
newCapacity = DEFAULT_SIZE;
|
|
if (newCapacity < (uint)minCapacity)
|
|
newCapacity = (uint)minCapacity;
|
|
// See coreclr/vm/gchelpers.cpp:MaxArrayLength()
|
|
const uint MaxArrayLength = 0x7FEFFFFF;
|
|
Debug.Assert(MaxArrayLength <= int.MaxValue);
|
|
if (newCapacity > MaxArrayLength)
|
|
newCapacity = MaxArrayLength;
|
|
var newElements = new Instruction[(int)newCapacity];
|
|
Array.Copy(elements, 0, newElements, 0, count);
|
|
this.elements = newElements;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Allocates an uninitialized element at the end of the list and returns a reference to it.
|
|
/// The return value can be passed to eg. Decoder.Decode(out Instruction).
|
|
/// The returned reference is valid until the internal array is resized.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]// Add() is inlined, and this method does almost the same thing
|
|
public ref Instruction AllocUninitializedElement() {
|
|
var count = this.count;
|
|
var elements = this.elements;
|
|
if (count == elements.Length) {
|
|
SetMinCapacity(count + 1);
|
|
elements = this.elements;
|
|
}
|
|
this.count = count + 1;
|
|
return ref elements[count];
|
|
}
|
|
|
|
void MakeRoom(int index, int extraLength) {
|
|
//TODO: This can be optimized to copy less data. The current code can copy the same
|
|
// data twice if the internal array is resized by SetMinCapacity()
|
|
SetMinCapacity(count + extraLength);
|
|
int copyCount = count - index;
|
|
if (copyCount != 0) {
|
|
var elements = this.elements;
|
|
Array.Copy(elements, index, elements, index + extraLength, copyCount);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Inserts an element
|
|
/// </summary>
|
|
/// <param name="index">Index of element</param>
|
|
/// <param name="instruction">Instruction to add</param>
|
|
public void Insert(int index, in Instruction instruction) {
|
|
var count = this.count;
|
|
if ((uint)index > (uint)count)
|
|
ThrowArgumentOutOfRangeException(nameof(index));
|
|
MakeRoom(index, 1);
|
|
elements[index] = instruction;
|
|
this.count = count + 1;
|
|
}
|
|
void IList<Instruction>.Insert(int index, Instruction instruction) => Insert(index, instruction);
|
|
void IList.Insert(int index, object value) {
|
|
if (value == null)
|
|
ThrowArgumentNullException(nameof(value));
|
|
if (!(value is Instruction))
|
|
ThrowArgumentException();
|
|
Insert(index, (Instruction)value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes an element from the list
|
|
/// </summary>
|
|
/// <param name="index">Index of element to remove</param>
|
|
public void RemoveAt(int index) {
|
|
var newCount = count;
|
|
if ((uint)index >= (uint)newCount)
|
|
ThrowArgumentOutOfRangeException(nameof(index));
|
|
newCount--;
|
|
count = newCount;
|
|
int copyCount = newCount - index;
|
|
if (copyCount != 0) {
|
|
var elements = this.elements;
|
|
Array.Copy(elements, index + 1, elements, index, copyCount);
|
|
}
|
|
}
|
|
void IList<Instruction>.RemoveAt(int index) => RemoveAt(index);
|
|
void IList.RemoveAt(int index) => RemoveAt(index);
|
|
|
|
/// <summary>
|
|
/// Adds a collection to the end of this list
|
|
/// </summary>
|
|
/// <param name="collection">Collection to add</param>
|
|
public void AddRange(IEnumerable<Instruction> collection) => InsertRange(count, collection);
|
|
|
|
/// <summary>
|
|
/// Inserts elements
|
|
/// </summary>
|
|
/// <param name="index">Index of element</param>
|
|
/// <param name="collection">Items to insert</param>
|
|
public void InsertRange(int index, IEnumerable<Instruction> collection) {
|
|
if ((uint)index > (uint)count)
|
|
ThrowArgumentOutOfRangeException(nameof(index));
|
|
if (collection == null)
|
|
ThrowArgumentNullException(nameof(collection));
|
|
if (collection is InstructionList list) {
|
|
int list_count = list.count;
|
|
if (list_count != 0) {
|
|
MakeRoom(index, list_count);
|
|
count += list_count;
|
|
Array.Copy(list.elements, 0, elements, index, list_count);
|
|
}
|
|
}
|
|
else if (collection is IReadOnlyList<Instruction> roList) {
|
|
int roList_Count = roList.Count;
|
|
if (roList_Count != 0) {
|
|
MakeRoom(index, roList_Count);
|
|
count += roList_Count;
|
|
var elements = this.elements;
|
|
for (int i = 0; i < roList_Count; i++)
|
|
elements[index + i] = roList[i];
|
|
}
|
|
}
|
|
else {
|
|
foreach (var instruction in collection)
|
|
Insert(index++, instruction);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes elements
|
|
/// </summary>
|
|
/// <param name="index">Index of element</param>
|
|
/// <param name="count">Number of elements to remove</param>
|
|
public void RemoveRange(int index, int count) {
|
|
if (index < 0)
|
|
ThrowArgumentOutOfRangeException(nameof(index));
|
|
if (count < 0)
|
|
ThrowArgumentOutOfRangeException(nameof(count));
|
|
if ((uint)index + (uint)count > (uint)this.count)
|
|
ThrowArgumentOutOfRangeException(nameof(count));
|
|
var newCount = this.count;
|
|
newCount -= count;
|
|
this.count = newCount;
|
|
int copyCount = newCount - index;
|
|
if (copyCount != 0) {
|
|
var elements = this.elements;
|
|
Array.Copy(elements, index + count, elements, index, copyCount);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a new instruction to the end of the list
|
|
/// </summary>
|
|
/// <param name="instruction">Instruction to add</param>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]// Needed since it's not inlined otherwise (because of 'in') (List<Instruction>.Add() gets auto-inlined)
|
|
public void Add(in Instruction instruction) {
|
|
var count = this.count;
|
|
var elements = this.elements;
|
|
if (count == elements.Length) {
|
|
SetMinCapacity(count + 1);
|
|
elements = this.elements;
|
|
}
|
|
elements[count] = instruction;
|
|
this.count = count + 1;
|
|
}
|
|
void ICollection<Instruction>.Add(Instruction instruction) => Add(instruction);
|
|
int IList.Add(object value) {
|
|
if (value == null)
|
|
ThrowArgumentNullException(nameof(value));
|
|
if (!(value is Instruction))
|
|
ThrowArgumentException();
|
|
Add((Instruction)value);
|
|
return count - 1;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clears the list
|
|
/// </summary>
|
|
public void Clear() => count = 0;// There are no GC refs in Instruction, so we don't have to clear the elements
|
|
void ICollection<Instruction>.Clear() => Clear();
|
|
void IList.Clear() => Clear();
|
|
|
|
/// <summary>
|
|
/// Checks if <paramref name="instruction"/> exists in the list
|
|
/// </summary>
|
|
/// <param name="instruction">Instruction</param>
|
|
/// <returns></returns>
|
|
public bool Contains(in Instruction instruction) => IndexOf(instruction) >= 0;
|
|
bool ICollection<Instruction>.Contains(Instruction instruction) => Contains(instruction);
|
|
bool IList.Contains(object value) {
|
|
if (value is Instruction)
|
|
return Contains((Instruction)value);
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the index of <paramref name="instruction"/> or -1 if it doesn't exist in the list
|
|
/// </summary>
|
|
/// <param name="instruction">Instruction</param>
|
|
/// <returns></returns>
|
|
public int IndexOf(in Instruction instruction) {
|
|
var elements = this.elements;
|
|
int count = this.count;
|
|
for (int i = 0; i < count; i++) {
|
|
if (elements[i] == instruction)
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
int IList<Instruction>.IndexOf(Instruction instruction) => IndexOf(instruction);
|
|
int IList.IndexOf(object value) {
|
|
if (value is Instruction)
|
|
return IndexOf((Instruction)value);
|
|
return -1;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the index of <paramref name="instruction"/> or -1 if it doesn't exist in the list
|
|
/// </summary>
|
|
/// <param name="instruction">Instruction</param>
|
|
/// <param name="index">Start index</param>
|
|
/// <returns></returns>
|
|
public int IndexOf(in Instruction instruction, int index) {
|
|
int count = this.count;
|
|
if ((uint)index > (uint)count)
|
|
ThrowArgumentOutOfRangeException(nameof(index));
|
|
var elements = this.elements;
|
|
for (int i = index; i < count; i++) {
|
|
if (elements[i] == instruction)
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the index of <paramref name="instruction"/> or -1 if it doesn't exist in the list
|
|
/// </summary>
|
|
/// <param name="instruction">Instruction</param>
|
|
/// <param name="index">Start index</param>
|
|
/// <param name="count">Number of instructions to check</param>
|
|
/// <returns></returns>
|
|
public int IndexOf(in Instruction instruction, int index, int count) {
|
|
if (index < 0)
|
|
ThrowArgumentOutOfRangeException(nameof(index));
|
|
if (count < 0)
|
|
ThrowArgumentOutOfRangeException(nameof(count));
|
|
int end = index + count;
|
|
if ((uint)end > (uint)this.count)
|
|
ThrowArgumentOutOfRangeException(nameof(count));
|
|
var elements = this.elements;
|
|
for (int i = index; i < end; i++) {
|
|
if (elements[i] == instruction)
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the last index of <paramref name="instruction"/> or -1 if it doesn't exist in the list
|
|
/// </summary>
|
|
/// <param name="instruction">Instruction</param>
|
|
/// <returns></returns>
|
|
public int LastIndexOf(in Instruction instruction) {
|
|
for (int i = count - 1; i >= 0; i--) {
|
|
if (elements[i] == instruction)
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the last index of <paramref name="instruction"/> or -1 if it doesn't exist in the list
|
|
/// </summary>
|
|
/// <param name="instruction">Instruction</param>
|
|
/// <param name="index">Start index</param>
|
|
/// <returns></returns>
|
|
public int LastIndexOf(in Instruction instruction, int index) {
|
|
int count = this.count;
|
|
if ((uint)index > (uint)count)
|
|
ThrowArgumentOutOfRangeException(nameof(index));
|
|
var elements = this.elements;
|
|
for (int i = count - 1; i >= index; i--) {
|
|
if (elements[i] == instruction)
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the last index of <paramref name="instruction"/> or -1 if it doesn't exist in the list
|
|
/// </summary>
|
|
/// <param name="instruction">Instruction</param>
|
|
/// <param name="index">Start index</param>
|
|
/// <param name="count">Number of instructions to check</param>
|
|
/// <returns></returns>
|
|
public int LastIndexOf(in Instruction instruction, int index, int count) {
|
|
if (index < 0)
|
|
ThrowArgumentOutOfRangeException(nameof(index));
|
|
if (count < 0)
|
|
ThrowArgumentOutOfRangeException(nameof(count));
|
|
int end = index + count;
|
|
if ((uint)end > (uint)this.count)
|
|
ThrowArgumentOutOfRangeException(nameof(count));
|
|
var elements = this.elements;
|
|
for (int i = end - 1; i >= index; i--) {
|
|
if (elements[i] == instruction)
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes the first copy of <paramref name="instruction"/> and returns true if it was removed
|
|
/// </summary>
|
|
/// <param name="instruction">Instruction</param>
|
|
/// <returns></returns>
|
|
public bool Remove(in Instruction instruction) {
|
|
int index = IndexOf(instruction);
|
|
if (index >= 0)
|
|
RemoveAt(index);
|
|
return index >= 0;
|
|
}
|
|
bool ICollection<Instruction>.Remove(Instruction instruction) => Remove(instruction);
|
|
void IList.Remove(object value) {
|
|
if (value is Instruction)
|
|
Remove((Instruction)value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Copies this collection to <paramref name="array"/>
|
|
/// </summary>
|
|
/// <param name="array">Destination array</param>
|
|
public void CopyTo(Instruction[] array) => CopyTo(array, 0);
|
|
|
|
/// <summary>
|
|
/// Copies this collection to <paramref name="array"/>
|
|
/// </summary>
|
|
/// <param name="array">Destination array</param>
|
|
/// <param name="arrayIndex">Start index in <paramref name="array"/></param>
|
|
public void CopyTo(Instruction[] array, int arrayIndex) =>
|
|
Array.Copy(elements, 0, array, arrayIndex, count);
|
|
void ICollection<Instruction>.CopyTo(Instruction[] array, int arrayIndex) => CopyTo(array, arrayIndex);
|
|
void ICollection.CopyTo(Array array, int index) {
|
|
if (array == null)
|
|
ThrowArgumentNullException(nameof(array));
|
|
else if (array is Instruction[] elemArray)
|
|
CopyTo(elemArray, index);
|
|
else
|
|
ThrowArgumentException();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Copies this collection to <paramref name="array"/>
|
|
/// </summary>
|
|
/// <param name="index">Index in this collection</param>
|
|
/// <param name="array">Destination array</param>
|
|
/// <param name="arrayIndex">Destination index</param>
|
|
/// <param name="count">Number of elements to copy</param>
|
|
public void CopyTo(int index, Instruction[] array, int arrayIndex, int count) =>
|
|
Array.Copy(elements, index, array, arrayIndex, count);
|
|
|
|
/// <summary>
|
|
/// Creates a new list that contains some of the instructions in this list
|
|
/// </summary>
|
|
/// <param name="index">Index of first instruction</param>
|
|
/// <param name="count">Number of instructions</param>
|
|
/// <returns></returns>
|
|
public InstructionList GetRange(int index, int count) {
|
|
if (index < 0)
|
|
ThrowArgumentOutOfRangeException(nameof(index));
|
|
if (count < 0)
|
|
ThrowArgumentOutOfRangeException(nameof(count));
|
|
if ((uint)index + (uint)count > (uint)this.count)
|
|
ThrowArgumentOutOfRangeException(nameof(count));
|
|
var list = new InstructionList(count);
|
|
Array.Copy(elements, index, list.elements, 0, count);
|
|
list.count = count;
|
|
return list;
|
|
}
|
|
|
|
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
|
public struct Enumerator : IEnumerator<Instruction> {
|
|
readonly InstructionList list;
|
|
int index;
|
|
|
|
public ref Instruction Current => ref list.elements[index];
|
|
Instruction IEnumerator<Instruction>.Current => list.elements[index];
|
|
object IEnumerator.Current => list.elements[index];
|
|
|
|
internal Enumerator(InstructionList list) {
|
|
// Only two fields, the jitter can put both fields in two registers and
|
|
// won't allocate anything on the stack
|
|
this.list = list;
|
|
index = -1;
|
|
}
|
|
|
|
public bool MoveNext() {
|
|
// Keep both statements, the jitter generates better code if it looks like this.
|
|
// Both fields should already be in registers.
|
|
index++;
|
|
return index < list.count;
|
|
}
|
|
|
|
void IEnumerator.Reset() => throw new NotSupportedException();
|
|
public void Dispose() { }
|
|
}
|
|
#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
|
|
|
|
/// <summary>
|
|
/// Gets a ref iterator (use 'foreach ref')
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public Enumerator GetEnumerator() => new Enumerator(this);
|
|
IEnumerator<Instruction> IEnumerable<Instruction>.GetEnumerator() => new Enumerator(this);
|
|
IEnumerator IEnumerable.GetEnumerator() => new Enumerator(this);
|
|
|
|
/// <summary>
|
|
/// Returns a read-only wrapper for this list
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public ReadOnlyCollection<Instruction> AsReadOnly() => new ReadOnlyCollection<Instruction>(this);
|
|
|
|
/// <summary>
|
|
/// Creates a new array with all instructions and returns it
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public Instruction[] ToArray() {
|
|
int count = this.count;
|
|
if (count == 0)
|
|
return Array.Empty<Instruction>();
|
|
var res = new Instruction[count];
|
|
Array.Copy(elements, 0, res, 0, res.Length);
|
|
return res;
|
|
}
|
|
}
|
|
}
|