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;
namespace IFPSLib
{
///
/// Represents an object with a PascalScript base type.
///
public class TypedData
{
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;
}
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 beter than this... but for now, it'll do
return new TypedData(type, decimal.Parse(br.Read().ToString()));
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(), 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("{0}({1})", Type.Name, Value);
}
}
}
}