mirror of
https://github.com/ZuneDev/ZuneUIXTools.git
synced 2026-07-27 13:11:59 -07:00
Use two-way mapping for mnemonics -> opcodes
This commit is contained in:
@@ -64,22 +64,22 @@ public class Disassembler
|
|||||||
{
|
{
|
||||||
case OpCode.ConstructObject:
|
case OpCode.ConstructObject:
|
||||||
// COBJ <typeIndex>
|
// COBJ <typeIndex>
|
||||||
yield return new Instruction("COBJ", [new(reader.ReadUInt16())]);
|
yield return new Instruction(opCode, [new(reader.ReadUInt16())]);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OpCode.ConstructObjectIndirect:
|
case OpCode.ConstructObjectIndirect:
|
||||||
// COBI <assignmentTypeIndex>
|
// COBI <assignmentTypeIndex>
|
||||||
yield return new Instruction("COBI", [new(reader.ReadUInt16())]);
|
yield return new Instruction(opCode, [new(reader.ReadUInt16())]);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OpCode.ConstructObjectParam:
|
case OpCode.ConstructObjectParam:
|
||||||
// COBP <targetTypeIndex> <constructorIndex>
|
// COBP <targetTypeIndex> <constructorIndex>
|
||||||
yield return new Instruction("COBP", [new(reader.ReadUInt16()), new(reader.ReadUInt16())]);
|
yield return new Instruction(opCode, [new(reader.ReadUInt16()), new(reader.ReadUInt16())]);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OpCode.ConstructFromString:
|
case OpCode.ConstructFromString:
|
||||||
// CSTR <typeIndex> <stringIndex>
|
// CSTR <typeIndex> <stringIndex>
|
||||||
yield return new Instruction("CSTR", [new(reader.ReadUInt16()), new(reader.ReadUInt16())]);
|
yield return new Instruction(opCode, [new(reader.ReadUInt16()), new(reader.ReadUInt16())]);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OpCode.ConstructFromBinary:
|
case OpCode.ConstructFromBinary:
|
||||||
@@ -88,38 +88,38 @@ public class Disassembler
|
|||||||
TypeSchema cbinTypeSchema = _loadResult.ImportTables.TypeImports[cbinTypeIndex];
|
TypeSchema cbinTypeSchema = _loadResult.ImportTables.TypeImports[cbinTypeIndex];
|
||||||
object cbinObject = cbinTypeSchema.DecodeBinary(reader);
|
object cbinObject = cbinTypeSchema.DecodeBinary(reader);
|
||||||
|
|
||||||
yield return new Instruction("CBIN", [new(cbinTypeIndex), new(cbinObject)]);
|
yield return new Instruction(opCode, [new(cbinTypeIndex), new(cbinObject)]);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
|
|
||||||
case OpCode.PropertyInitialize:
|
case OpCode.PropertyInitialize:
|
||||||
// PINI <propertyIndex>
|
// PINI <propertyIndex>
|
||||||
yield return new Instruction("PINI", [new(reader.ReadUInt16())]);
|
yield return new Instruction(opCode, [new(reader.ReadUInt16())]);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OpCode.PropertyInitializeIndirect:
|
case OpCode.PropertyInitializeIndirect:
|
||||||
// PINII <propertyIndex>
|
// PINII <propertyIndex>
|
||||||
yield return new Instruction("PINII", [new(reader.ReadUInt16())]);
|
yield return new Instruction(opCode, [new(reader.ReadUInt16())]);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
|
|
||||||
case OpCode.PushConstant:
|
case OpCode.PushConstant:
|
||||||
// PSHC <constantIndex>
|
// PSHC <constantIndex>
|
||||||
yield return new Instruction("PSHC", [new(reader.ReadUInt16())]);
|
yield return new Instruction(opCode, [new(reader.ReadUInt16())]);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
|
|
||||||
case OpCode.ReturnValue:
|
case OpCode.ReturnValue:
|
||||||
// RET
|
// RET
|
||||||
yield return new Instruction("RET", []);
|
yield return new Instruction(opCode, []);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OpCode.ReturnVoid:
|
case OpCode.ReturnVoid:
|
||||||
// RETV
|
// RETV
|
||||||
yield return new Instruction("RETV", []);
|
yield return new Instruction(opCode, []);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Microsoft.Iris.Asm;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a dictionary where entries can be quickly retrieved by their key or value.
|
||||||
|
/// Optimal for one-to-one mappings.
|
||||||
|
/// </summary>
|
||||||
|
public class DoubleDictionary<TLeft, TRight>(int capacity = 0) : ICollection<(TLeft, TRight)>
|
||||||
|
{
|
||||||
|
private readonly Dictionary<TLeft, TRight> _leftDict = new(capacity);
|
||||||
|
private readonly Dictionary<TRight, TLeft> _rightDict = new(capacity);
|
||||||
|
|
||||||
|
public int Count => _leftDict.Count;
|
||||||
|
public bool IsReadOnly => false;
|
||||||
|
|
||||||
|
public IReadOnlyDictionary<TLeft, TRight> GetLeftDictionary() => _leftDict;
|
||||||
|
public IReadOnlyDictionary<TRight, TLeft> GetRightDictionary() => _rightDict;
|
||||||
|
|
||||||
|
public void Add(TLeft l, TRight r)
|
||||||
|
{
|
||||||
|
_leftDict[l] = r;
|
||||||
|
_rightDict[r] = l;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TRight this[TLeft l] => _leftDict[l];
|
||||||
|
public TLeft this[TRight r] => _rightDict[r];
|
||||||
|
|
||||||
|
public bool Contains(TLeft l) => _leftDict.ContainsKey(l);
|
||||||
|
public bool Contains(TRight r) => _rightDict.ContainsKey(r);
|
||||||
|
|
||||||
|
public bool TryGetRight(TLeft l, out TRight r) => _leftDict.TryGetValue(l, out r);
|
||||||
|
public bool TryGetLeft(TRight r, out TLeft l) => _rightDict.TryGetValue(r, out l);
|
||||||
|
|
||||||
|
public bool Remove(TLeft l)
|
||||||
|
{
|
||||||
|
if (!TryGetRight(l, out var r))
|
||||||
|
return false;
|
||||||
|
_leftDict.Remove(l);
|
||||||
|
|
||||||
|
return _rightDict.Remove(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Remove(TRight r)
|
||||||
|
{
|
||||||
|
if (!TryGetLeft(r, out var l))
|
||||||
|
return false;
|
||||||
|
_rightDict.Remove(r);
|
||||||
|
|
||||||
|
return _leftDict.Remove(l);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerator<(TLeft, TRight)> GetEnumerator() => _leftDict
|
||||||
|
.Select(kv => (kv.Key, kv.Value))
|
||||||
|
.GetEnumerator();
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
|
|
||||||
|
public void Add((TLeft, TRight) item) => Add(item.Item1, item.Item2);
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
_leftDict.Clear();
|
||||||
|
_rightDict.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Contains((TLeft, TRight) item)
|
||||||
|
=> _leftDict.TryGetValue(item.Item1, out var r) && r!.Equals(item.Item2);
|
||||||
|
|
||||||
|
public void CopyTo((TLeft, TRight)[] array, int arrayIndex)
|
||||||
|
{
|
||||||
|
var i = arrayIndex;
|
||||||
|
foreach (var pair in this)
|
||||||
|
array[i++] = pair;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Remove((TLeft, TRight) item) => Remove(item.Item1) && Remove(item.Item2);
|
||||||
|
}
|
||||||
@@ -13,6 +13,11 @@ public static class Lexer
|
|||||||
|
|
||||||
public static readonly Parser<string> StatementEnd = Parse.Char(';').Return(";").Or(Parse.LineTerminator);
|
public static readonly Parser<string> StatementEnd = Parse.Char(';').Return(";").Or(Parse.LineTerminator);
|
||||||
|
|
||||||
|
public static readonly Parser<string> SectionDirective =
|
||||||
|
from _ in Parse.String(".section").Token()
|
||||||
|
from sectionId in Parse.Letter.AtLeastOnce().Text()
|
||||||
|
select sectionId;
|
||||||
|
|
||||||
public static readonly Parser<IImport> Import = ParseImport;
|
public static readonly Parser<IImport> Import = ParseImport;
|
||||||
|
|
||||||
public static readonly Parser<IBodyItem> BodyItem = ParseBodyItem;
|
public static readonly Parser<IBodyItem> BodyItem = ParseBodyItem;
|
||||||
|
|||||||
+83
-74
@@ -1,5 +1,4 @@
|
|||||||
using Microsoft.Iris.Markup;
|
using Microsoft.Iris.Markup;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Microsoft.Iris.Asm;
|
namespace Microsoft.Iris.Asm;
|
||||||
|
|
||||||
@@ -8,90 +7,100 @@ internal static class LexerMaps
|
|||||||
public static OperationType OperationMnemonicToType(string mnemonic) => OperationMnemonicMap[mnemonic.ToUpperInvariant()];
|
public static OperationType OperationMnemonicToType(string mnemonic) => OperationMnemonicMap[mnemonic.ToUpperInvariant()];
|
||||||
public static OperationType? TryOperationMnemonicToType(string mnemonic)
|
public static OperationType? TryOperationMnemonicToType(string mnemonic)
|
||||||
{
|
{
|
||||||
return OperationMnemonicMap.TryGetValue(mnemonic.ToUpperInvariant(), out var type)
|
return OperationMnemonicMap.TryGetRight(mnemonic.ToUpperInvariant(), out var type)
|
||||||
? type : null;
|
? type : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static OpCode MnemonicToOpCode(string mnemonic)
|
public static OpCode MnemonicToOpCode(string mnemonic)
|
||||||
{
|
{
|
||||||
if (MnemonicMap.TryGetValue(mnemonic, out var opCode))
|
if (MnemonicMap.TryGetRight(mnemonic, out var opCode))
|
||||||
return opCode;
|
return opCode;
|
||||||
else if (OperationMnemonicMap.TryGetValue(mnemonic, out _))
|
else if (OperationMnemonicMap.TryGetRight(mnemonic, out _))
|
||||||
return OpCode.Operation;
|
return OpCode.Operation;
|
||||||
|
|
||||||
throw new System.ArgumentException($"'{mnemonic}' is not a known UIXA instruction.", nameof(mnemonic));
|
throw new System.ArgumentException($"'{mnemonic}' is not a known UIXA instruction.", nameof(mnemonic));
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static readonly IDictionary<string, OperationType> OperationMnemonicMap = new Dictionary<string, OperationType>
|
public static string GetMnemonic(OpCode opCode, OperationType? opType = null)
|
||||||
{
|
{
|
||||||
["ADD"] = OperationType.MathAdd,
|
if (MnemonicMap.TryGetLeft(opCode, out var mnemonic))
|
||||||
["SUB"] = OperationType.MathSubtract,
|
return mnemonic;
|
||||||
["MUL"] = OperationType.MathMultiply,
|
else if (opType.HasValue && OperationMnemonicMap.TryGetLeft(opType.Value, out var operationMnemonic))
|
||||||
["DIV"] = OperationType.MathDivide,
|
return operationMnemonic;
|
||||||
["MOD"] = OperationType.MathModulus,
|
|
||||||
["NEG"] = OperationType.MathModulus,
|
|
||||||
["AND"] = OperationType.LogicalAnd,
|
|
||||||
["ORR"] = OperationType.LogicalOr,
|
|
||||||
["NOT"] = OperationType.LogicalNot,
|
|
||||||
["REQ"] = OperationType.RelationalEquals,
|
|
||||||
["RNE"] = OperationType.RelationalNotEquals,
|
|
||||||
["RLT"] = OperationType.RelationalLessThan,
|
|
||||||
["RGT"] = OperationType.RelationalGreaterThan,
|
|
||||||
["RLE"] = OperationType.RelationalLessThanEquals,
|
|
||||||
["RGE"] = OperationType.RelationalGreaterThanEquals,
|
|
||||||
["RIS"] = OperationType.RelationalIs,
|
|
||||||
["INC"] = OperationType.PostIncrement,
|
|
||||||
["DEC"] = OperationType.PostDecrement,
|
|
||||||
};
|
|
||||||
|
|
||||||
internal static readonly IDictionary<string, OpCode> MnemonicMap = new Dictionary<string, OpCode>
|
throw new System.ArgumentException($"'{opCode}' is not a known UIXA instruction.", nameof(opCode));
|
||||||
{
|
}
|
||||||
["COBJ"] = OpCode.ConstructObject,
|
|
||||||
["COBI"] = OpCode.ConstructObjectIndirect,
|
internal static readonly DoubleDictionary<string, OperationType> OperationMnemonicMap =
|
||||||
["COBP"] = OpCode.ConstructObjectParam,
|
[
|
||||||
["CSTR"] = OpCode.ConstructFromString,
|
("ADD", OperationType.MathAdd),
|
||||||
["CBIN"] = OpCode.ConstructFromBinary,
|
("SUB", OperationType.MathSubtract),
|
||||||
["INIT"] = OpCode.InitializeInstance,
|
("MUL", OperationType.MathMultiply),
|
||||||
["INID"] = OpCode.InitializeInstanceIndirect,
|
("DIV", OperationType.MathDivide),
|
||||||
["LSYM"] = OpCode.LookupSymbol,
|
("MOD", OperationType.MathModulus),
|
||||||
["WSYM"] = OpCode.WriteSymbol,
|
("NEG", OperationType.MathModulus),
|
||||||
["WSYP"] = OpCode.WriteSymbolPeek,
|
("AND", OperationType.LogicalAnd),
|
||||||
["CSYM"] = OpCode.ClearSymbol,
|
("ORR", OperationType.LogicalOr),
|
||||||
["PINI"] = OpCode.PropertyInitialize,
|
("NOT", OperationType.LogicalNot),
|
||||||
["PINII"] = OpCode.PropertyInitializeIndirect,
|
("REQ", OperationType.RelationalEquals),
|
||||||
["PLAD"] = OpCode.PropertyListAdd,
|
("RNE", OperationType.RelationalNotEquals),
|
||||||
["PDAD"] = OpCode.PropertyDictionaryAdd,
|
("RLT", OperationType.RelationalLessThan),
|
||||||
["PASS"] = OpCode.PropertyAssign,
|
("RGT", OperationType.RelationalGreaterThan),
|
||||||
["PASST"] = OpCode.PropertyAssignStatic,
|
("RLE", OperationType.RelationalLessThanEquals),
|
||||||
["PGET"] = OpCode.PropertyGet,
|
("RGE", OperationType.RelationalGreaterThanEquals),
|
||||||
["PGETP"] = OpCode.PropertyGetPeek,
|
("RIS", OperationType.RelationalIs),
|
||||||
["PGETT"] = OpCode.PropertyGetStatic,
|
("INC", OperationType.PostIncrement),
|
||||||
["MINV"] = OpCode.MethodInvoke,
|
("DEC", OperationType.PostDecrement),
|
||||||
["MINVP"] = OpCode.MethodInvokePeek,
|
];
|
||||||
["MINVT"] = OpCode.MethodInvokeStatic,
|
|
||||||
["MINVA"] = OpCode.MethodInvokePushLastParam,
|
internal static readonly DoubleDictionary<string, OpCode> MnemonicMap =
|
||||||
["MINVAT"] = OpCode.MethodInvokeStaticPushLastParam, // Avoid using "LT" as suffix
|
[
|
||||||
["VTC"] = OpCode.VerifyTypeCast,
|
("COBJ", OpCode.ConstructObject),
|
||||||
["CON"] = OpCode.ConvertType,
|
("COBI", OpCode.ConstructObjectIndirect),
|
||||||
["OPR"] = OpCode.Operation, // Generic operation, allow dynamic invocations of operators
|
("COBP", OpCode.ConstructObjectParam),
|
||||||
["ISC"] = OpCode.IsCheck,
|
("CSTR", OpCode.ConstructFromString),
|
||||||
["ASC"] = OpCode.As,
|
("CBIN", OpCode.ConstructFromBinary),
|
||||||
["TYP"] = OpCode.TypeOf,
|
("INIT", OpCode.InitializeInstance),
|
||||||
["PSHN"] = OpCode.PushNull,
|
("INID", OpCode.InitializeInstanceIndirect),
|
||||||
["PSHC"] = OpCode.PushConstant,
|
("LSYM", OpCode.LookupSymbol),
|
||||||
["PSHT"] = OpCode.PushThis,
|
("WSYM", OpCode.WriteSymbol),
|
||||||
["DIS"] = OpCode.DiscardValue,
|
("WSYP", OpCode.WriteSymbolPeek),
|
||||||
["RET"] = OpCode.ReturnValue,
|
("CSYM", OpCode.ClearSymbol),
|
||||||
["RETV"] = OpCode.ReturnVoid,
|
("PINI", OpCode.PropertyInitialize),
|
||||||
["JMPF"] = OpCode.JumpIfFalse,
|
("PINII", OpCode.PropertyInitializeIndirect),
|
||||||
["JMPFP"] = OpCode.JumpIfFalsePeek,
|
("PLAD", OpCode.PropertyListAdd),
|
||||||
["JMPTP"] = OpCode.JumpIfTruePeek,
|
("PDAD", OpCode.PropertyDictionaryAdd),
|
||||||
["JMPD"] = OpCode.JumpIfDictionaryContains,
|
("PASS", OpCode.PropertyAssign),
|
||||||
["JMPNP"] = OpCode.JumpIfNullPeek,
|
("PASST", OpCode.PropertyAssignStatic),
|
||||||
["JMP"] = OpCode.Jump,
|
("PGET", OpCode.PropertyGet),
|
||||||
["CLIS"] = OpCode.ConstructListenerStorage,
|
("PGETP", OpCode.PropertyGetPeek),
|
||||||
["LIS"] = OpCode.Listen,
|
("PGETT", OpCode.PropertyGetStatic),
|
||||||
["DLS"] = OpCode.DestructiveListen,
|
("MINV", OpCode.MethodInvoke),
|
||||||
["DBG"] = OpCode.EnterDebugState,
|
("MINVP", OpCode.MethodInvokePeek),
|
||||||
};
|
("MINVT", OpCode.MethodInvokeStatic),
|
||||||
|
("MINVA", OpCode.MethodInvokePushLastParam),
|
||||||
|
("MINVAT", OpCode.MethodInvokeStaticPushLastParam), // Avoid using "LT" as suffix
|
||||||
|
("VTC", OpCode.VerifyTypeCast),
|
||||||
|
("CON", OpCode.ConvertType),
|
||||||
|
("OPR", OpCode.Operation), // Generic operation, allow dynamic invocations of operators
|
||||||
|
("ISC", OpCode.IsCheck),
|
||||||
|
("ASC", OpCode.As),
|
||||||
|
("TYP", OpCode.TypeOf),
|
||||||
|
("PSHN", OpCode.PushNull),
|
||||||
|
("PSHC", OpCode.PushConstant),
|
||||||
|
("PSHT", OpCode.PushThis),
|
||||||
|
("DIS", OpCode.DiscardValue),
|
||||||
|
("RET", OpCode.ReturnValue),
|
||||||
|
("RETV", OpCode.ReturnVoid),
|
||||||
|
("JMPF", OpCode.JumpIfFalse),
|
||||||
|
("JMPFP", OpCode.JumpIfFalsePeek),
|
||||||
|
("JMPTP", OpCode.JumpIfTruePeek),
|
||||||
|
("JMPD", OpCode.JumpIfDictionaryContains),
|
||||||
|
("JMPNP", OpCode.JumpIfNullPeek),
|
||||||
|
("JMP", OpCode.Jump),
|
||||||
|
("CLIS", OpCode.ConstructListenerStorage),
|
||||||
|
("LIS", OpCode.Listen),
|
||||||
|
("DLS", OpCode.DestructiveListen),
|
||||||
|
("DBG", OpCode.EnterDebugState),
|
||||||
|
];
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using Microsoft.Iris.Markup;
|
using Microsoft.Iris.Markup;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace Microsoft.Iris.Asm.Models;
|
namespace Microsoft.Iris.Asm.Models;
|
||||||
|
|
||||||
@@ -9,6 +10,15 @@ public interface IImport { }
|
|||||||
|
|
||||||
public record Instruction(string Mnemonic, IEnumerable<Operand> Operands) : IBodyItem
|
public record Instruction(string Mnemonic, IEnumerable<Operand> Operands) : IBodyItem
|
||||||
{
|
{
|
||||||
|
public Instruction(OpCode opCode, OperationType? operationType, IEnumerable<Operand> Operands)
|
||||||
|
: this(LexerMaps.GetMnemonic(opCode, operationType), Operands)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
public Instruction(OpCode opCode, IEnumerable<Operand> Operands)
|
||||||
|
: this(opCode, null, Operands)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public OpCode OpCode => LexerMaps.MnemonicToOpCode(Mnemonic);
|
public OpCode OpCode => LexerMaps.MnemonicToOpCode(Mnemonic);
|
||||||
public OperationType? OperationType => LexerMaps.TryOperationMnemonicToType(Mnemonic);
|
public OperationType? OperationType => LexerMaps.TryOperationMnemonicToType(Mnemonic);
|
||||||
|
|
||||||
@@ -27,5 +37,13 @@ public record Operand(object Value, string Content = null)
|
|||||||
|
|
||||||
public record Program(IEnumerable<IImport> Imports, IEnumerable<IBodyItem> Body)
|
public record Program(IEnumerable<IImport> Imports, IEnumerable<IBodyItem> Body)
|
||||||
{
|
{
|
||||||
public override string ToString() => string.Join("\r\n", Imports.Select(i => i.ToString()).Concat(Body.Select(b => b.ToString())));
|
public override string ToString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new();
|
||||||
|
|
||||||
|
sb.AppendJoin("\r\n", Imports.Select(i => i.ToString()));
|
||||||
|
sb.AppendJoin("\r\n", Body.Select(b => b.ToString()));
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Microsoft.Iris.Asm;
|
||||||
|
|
||||||
|
internal static class StringBuilderExtensions
|
||||||
|
{
|
||||||
|
#if NETSTANDARD
|
||||||
|
public static StringBuilder AppendJoin<T>(this StringBuilder sb, string? separator, IEnumerable<T> values)
|
||||||
|
{
|
||||||
|
_ = values ?? throw new ArgumentNullException(nameof(values));
|
||||||
|
|
||||||
|
separator ??= string.Empty;
|
||||||
|
using (IEnumerator<T> en = values.GetEnumerator())
|
||||||
|
{
|
||||||
|
if (!en.MoveNext())
|
||||||
|
{
|
||||||
|
return sb;
|
||||||
|
}
|
||||||
|
|
||||||
|
T value = en.Current;
|
||||||
|
if (value != null)
|
||||||
|
{
|
||||||
|
sb.Append(value.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
while (en.MoveNext())
|
||||||
|
{
|
||||||
|
sb.Append(separator);
|
||||||
|
value = en.Current;
|
||||||
|
if (value != null)
|
||||||
|
{
|
||||||
|
sb.Append(value.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user