using IFPSLib.Types; using System; using System.Collections.Generic; using System.Text; using System.Linq; using System.IO; using SharpFloat.FloatingPoint; using System.Runtime.InteropServices; using System.Collections; using System.Runtime.CompilerServices; using System.Globalization; namespace IFPSLib { /// /// Represents an object with a PascalScript base type. /// public class TypedData { private static readonly CultureInfo s_Culture = new CultureInfo("en"); public IType Type { get; } public object Value { get; } internal TypedData(IType type, object value) { Type = type; Value = value; } public static TypedData Create(PrimitiveType type, TType value) { if (!type.BaseType.EqualsType(typeof(TType))) throw new ArgumentOutOfRangeException(nameof(value)); return new TypedData(type, value); } public static TypedData Create(FunctionPointerType type, IFunction value) { return new TypedData(type, value); } public static TypedData Create(TType value) => Create(PrimitiveType.Create(), value); internal static TypedData Create(IType value) { return new TypedData(TypeType.Instance, value); } internal static TypedData Create(Emit.Instruction value) { return new TypedData(InstructionType.Instance, value); } internal static TypedData Create(IFunction value) { return new TypedData(ImmediateFunctionType.Instance, value); } internal static TypedData Create(BitArray bitarr) { return new TypedData(new SetType(bitarr.Length), bitarr); } public TType ValueAs() { return (TType)Value; } private static void TrimDecimalString(StringBuilder sb) { // find the "e" looking from the end int idx = sb.Length; for (int i = sb.Length - 1; i >= 0; i--) { if (sb[i] == 'e') { idx = i - 1; break; } } // remove while character is '0' int length = 0; while (idx >= 0 && sb[idx] == '0') { length++; idx--; } if (sb[idx] == '.') // need at least one zero { length--; idx++; } if (length == 0) return; sb.Remove(idx + 1, length); } internal static TypedData Load(BinaryReader br, Script script) { var idxType = br.Read(); var type = script.Types[(int)idxType]; switch (type.BaseType) { case PascalTypeCode.S8: return new TypedData(type, br.Read()); case PascalTypeCode.U8: return new TypedData(type, br.Read()); case PascalTypeCode.Char: return new TypedData(type, (char)br.Read()); case PascalTypeCode.S16: return new TypedData(type, br.Read()); case PascalTypeCode.U16: return new TypedData(type, br.Read()); case PascalTypeCode.WideChar: return new TypedData(type, br.Read()); case PascalTypeCode.S32: return new TypedData(type, br.Read()); case PascalTypeCode.U32: return new TypedData(type, br.Read()); case PascalTypeCode.S64: return new TypedData(type, br.Read()); case PascalTypeCode.Single: return new TypedData(type, br.Read()); case PascalTypeCode.Double: return new TypedData(type, br.Read()); case PascalTypeCode.Extended: { // BUGBUG: there must be something better than this... but for now, it'll do var sb = new StringBuilder(); ExtF80.PrintFloat80(sb, br.Read(), PrintFloatFormat.ScientificFormat, 19); TrimDecimalString(sb); return new TypedData(type, decimal.Parse(sb.ToString(), NumberStyles.Float, s_Culture)); } case PascalTypeCode.Currency: return new TypedData(type, new CurrencyWrapper(decimal.FromOACurrency(br.Read()))); case PascalTypeCode.PChar: case PascalTypeCode.String: var asciilen = br.Read(); return new TypedData(type, br.ReadAsciiString(asciilen)); case PascalTypeCode.WideString: case PascalTypeCode.UnicodeString: var unicodelen = br.Read(); return new TypedData(type, br.ReadUnicodeString(unicodelen)); case PascalTypeCode.ProcPtr: var funcIdx = br.Read(); return new TypedData(type, script.Functions[(int)funcIdx]); case PascalTypeCode.Set: return new TypedData(type, ((SetType)type).Load(br)); default: throw new ArgumentOutOfRangeException(nameof(type)); } } internal void WriteValue(BinaryWriter bw) where T : unmanaged { bw.Write(ValueAs()); } internal void Save(BinaryWriter bw, Script.SaveContext ctx) { bw.Write(ctx.GetTypeIndex(Type)); switch (Type.BaseType) { case PascalTypeCode.S8: WriteValue(bw); break; case PascalTypeCode.U8: WriteValue(bw); break; case PascalTypeCode.Char: bw.Write((byte)ValueAs()); break; case PascalTypeCode.S16: WriteValue(bw); break; case PascalTypeCode.U16: WriteValue(bw); break; case PascalTypeCode.WideChar: WriteValue(bw); break; case PascalTypeCode.S32: WriteValue(bw); break; case PascalTypeCode.U32: WriteValue(bw); break; case PascalTypeCode.S64: WriteValue(bw); break; case PascalTypeCode.Single: WriteValue(bw); break; case PascalTypeCode.Double: WriteValue(bw); break; case PascalTypeCode.Extended: if (!ExtF80.TryParse(ValueAs().ToString(s_Culture), out var extf)) throw new ArgumentOutOfRangeException("Value {0} cannot fit into an 80-bit floating point number"); bw.Write(extf); break; case PascalTypeCode.Currency: bw.Write(decimal.ToOACurrency(ValueAs().WrappedObject)); break; case PascalTypeCode.PChar: case PascalTypeCode.String: bw.WriteAsciiString(ValueAs(), true); break; case PascalTypeCode.WideString: case PascalTypeCode.UnicodeString: bw.Write(Encoding.Unicode.GetByteCount(ValueAs()) / sizeof(short)); bw.WriteUnicodeString(ValueAs()); break; case PascalTypeCode.ProcPtr: bw.Write(ctx.GetFunctionIndex(ValueAs())); break; case PascalTypeCode.Set: ((SetType)Type).Save(bw, ValueAs()); break; default: throw new InvalidOperationException(); } } public int Size { get { const int HEADER = sizeof(uint); // type index switch (Type.BaseType) { case PascalTypeCode.S8: return sizeof(sbyte) + HEADER; case PascalTypeCode.U8: case PascalTypeCode.Char: return sizeof(byte) + HEADER; case PascalTypeCode.S16: return sizeof(short) + HEADER; case PascalTypeCode.U16: return sizeof(ushort) + HEADER; case PascalTypeCode.WideChar: return sizeof(char) + HEADER; case PascalTypeCode.S32: return sizeof(int) + HEADER; case PascalTypeCode.U32: return sizeof(uint) + HEADER; case PascalTypeCode.S64: return sizeof(long) + HEADER; case PascalTypeCode.Single: return sizeof(float) + HEADER; case PascalTypeCode.Double: return sizeof(double) + HEADER; case PascalTypeCode.Extended: return Unsafe.SizeOf() + HEADER; case PascalTypeCode.Currency: return sizeof(long) + HEADER; case PascalTypeCode.PChar: case PascalTypeCode.String: return sizeof(uint) + Encoding.ASCII.GetByteCount(ValueAs()) + HEADER; case PascalTypeCode.WideString: case PascalTypeCode.UnicodeString: return sizeof(uint) + Encoding.Unicode.GetByteCount(ValueAs()) + HEADER; case PascalTypeCode.ProcPtr: return sizeof(uint) + HEADER; case PascalTypeCode.Set: return (Type as SetType).ByteSize + HEADER; case PascalTypeCode.Type: return HEADER; // 32-bit type index only case PascalTypeCode.Instruction: return HEADER; // 32-bit offset case PascalTypeCode.Function: return HEADER; // 32-bit function index only default: throw new InvalidOperationException(string.Format("Data of type {0} cannot be serialised", Type.BaseType)); } } } public override string ToString() { switch (Type.BaseType) { case PascalTypeCode.Type: return ValueAs().Name; case PascalTypeCode.Instruction: if (Value == null) return "null"; return string.Format("loc_{0}", ValueAs().Offset.ToString("x")); case PascalTypeCode.ProcPtr: return string.Format("{0}({1})", Type.Name, ValueAs().Name); case PascalTypeCode.Function: return ValueAs().Name; default: if (Value is string) { return string.Format("{0}({1})", Type.Name, ValueAs().ToLiteral()); } if (Value is char) { return string.Format("{0}({1})", Type.Name, new string(ValueAs(), 1).ToLiteral()); } if (Value is BitArray) { var val = ValueAs(); var sb = new StringBuilder(Type.Name); sb.Append("(0b"); for (int i = val.Count - 1; i >= 0; i--) { sb.Append(val[i] ? '1' : '0'); } sb.Append(')'); return sb.ToString(); } return string.Format(s_Culture, "{0}({1})", Type.Name, Value); } } } }