331 lines
12 KiB
C#
331 lines
12 KiB
C#
using Cryville.Common.Collections;
|
|
using Cryville.Common.Reflection;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Text;
|
|
using CMath = System.Math;
|
|
|
|
namespace Cryville.Common.Pdt {
|
|
/// <summary>
|
|
/// Interpreter for Property Definition Tree (PDT) file format.
|
|
/// </summary>
|
|
public partial class PdtInterpreter {
|
|
[Flags]
|
|
protected enum CharCategory {
|
|
WhiteSpace = 0x0001,
|
|
Identifier = 0x0010,
|
|
IdentifierBegin = 0x0020,
|
|
Digit = 0x0040,
|
|
Operator = 0x0080,
|
|
StringDelimiter = 0x0100,
|
|
OpeningBracket = 0x0200,
|
|
ClosingBracket = 0x0400,
|
|
EndOfExpression = 0x0800,
|
|
EndOfKey = 0x1000,
|
|
}
|
|
static readonly int[] cm = new int[] {
|
|
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
|
|
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0000, 0x0000,
|
|
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
|
|
0x0001, 0x0080, 0x0100, 0x0000, 0x0030, 0x0080, 0x0080, 0x0000, 0x0200, 0x0400, 0x0080, 0x0080, 0x0080, 0x0080, 0x0040, 0x0080,
|
|
0x0050, 0x0050, 0x0050, 0x0050, 0x0050, 0x0050, 0x0050, 0x0050, 0x0050, 0x0050, 0x1000, 0x1800, 0x0080, 0x0080, 0x0080, 0x0030,
|
|
0x0080, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030,
|
|
0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0000, 0x0080, 0x0000, 0x0080, 0x0030,
|
|
0x0000, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030,
|
|
0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x0030, 0x1000, 0x0080, 0x1000, 0x0080, 0x0000,
|
|
};
|
|
|
|
/// <summary>
|
|
/// Interprets a source string to an object of type <typeparamref name="T" />.
|
|
/// </summary>
|
|
/// <param name="src">The source string.</param>
|
|
/// <returns>The interpreted object.</returns>
|
|
public static T Interpret<T>(string src) {
|
|
return Interpret<T>(src, BinderAttribute.CreateBinderOfType(typeof(T)));
|
|
}
|
|
/// <summary>
|
|
/// Interprets a source string to an object of type <typeparamref name="T"/> with a binder.
|
|
/// </summary>
|
|
/// <param name="src">The source string.</param>
|
|
/// <param name="binder">The binder.</param>
|
|
/// <returns>The interpreted object.</returns>
|
|
public static T Interpret<T>(string src, Binder binder) {
|
|
return (T)new PdtInterpreter(src, binder).Interpret(typeof(T));
|
|
}
|
|
|
|
/// <summary>
|
|
/// The source string.
|
|
/// </summary>
|
|
public string Source { get; protected set; }
|
|
protected Binder _binder;
|
|
/// <summary>
|
|
/// The current position in the string being parsed by the interpreter.
|
|
/// </summary>
|
|
public int Position { get; protected set; }
|
|
|
|
readonly StringBuilder _sb = new();
|
|
#pragma warning disable IDE1006
|
|
/// <summary>
|
|
/// The character at the current position.
|
|
/// </summary>
|
|
/// <exception cref="IndexOutOfRangeException">The end of the source string is reached.</exception>
|
|
protected char cc { get { return Source[Position]; } }
|
|
/// <summary>
|
|
/// The category of the character.
|
|
/// </summary>
|
|
/// <exception cref="IndexOutOfRangeException">The end of the source string is reached.</exception>
|
|
protected CharCategory ct { get { return (CharCategory)cm[cc]; } }
|
|
/// <summary>
|
|
/// Reads a token until a character of type <paramref name="flag" /> is met.
|
|
/// </summary>
|
|
/// <param name="flag">The type filter.</param>
|
|
/// <returns>A token from the current position (inclusive) to the next character of type <paramref name="flag" /> (exclusive).</returns>
|
|
/// <exception cref="IndexOutOfRangeException">No character of type <paramref name="flag" /> is met.</exception>
|
|
protected string tokenb(CharCategory flag) {
|
|
int sp = Position;
|
|
while ((ct & flag) == 0) Position++;
|
|
return Source[sp..Position];
|
|
}
|
|
/// <summary>
|
|
/// Reads a token until a character that is not of type <paramref name="flag" /> is met.
|
|
/// </summary>
|
|
/// <param name="flag">The type filter.</param>
|
|
/// <returns>A token from the current position (inclusive) to the next character that is not of type <paramref name="flag" /> (exclusive).</returns>
|
|
/// <exception cref="IndexOutOfRangeException">No character that is not of type <paramref name="flag" /> is met.</exception>
|
|
protected string tokenw(CharCategory flag) {
|
|
int sp = Position;
|
|
while ((ct & flag) != 0) Position++;
|
|
return Source[sp..Position];
|
|
}
|
|
/// <summary>
|
|
/// Skips over whitespaces.
|
|
/// </summary>
|
|
/// <exception cref="IndexOutOfRangeException">The end of the source string is reached.</exception>
|
|
protected void ws() {
|
|
while ((ct & CharCategory.WhiteSpace) != 0) Position++;
|
|
}
|
|
#pragma warning restore IDE1006
|
|
|
|
/// <summary>
|
|
/// Reads the current character and increments the position.
|
|
/// </summary>
|
|
/// <returns>The current character.</returns>
|
|
/// <exception cref="IndexOutOfRangeException">The end of the source string is reached.</exception>
|
|
protected char GetChar() {
|
|
char r = cc;
|
|
Position++;
|
|
return r;
|
|
}
|
|
/// <summary>
|
|
/// Reads an identifier.
|
|
/// </summary>
|
|
/// <returns>An identifier.</returns>
|
|
/// <exception cref="IndexOutOfRangeException">The end of the source string is reached.</exception>
|
|
protected string GetIdentifier() {
|
|
if ((ct & CharCategory.IdentifierBegin) == 0) return "";
|
|
return tokenw(CharCategory.Identifier);
|
|
}
|
|
/// <summary>
|
|
/// Reads a number.
|
|
/// </summary>
|
|
/// <returns>A number.</returns>
|
|
/// <exception cref="IndexOutOfRangeException">The end of the source string is reached.</exception>
|
|
protected string GetNumber() {
|
|
return tokenw(CharCategory.Digit);
|
|
}
|
|
/// <summary>
|
|
/// Reads a string.
|
|
/// </summary>
|
|
/// <returns>A string.</returns>
|
|
/// <exception cref="IndexOutOfRangeException">The end of the source string is reached.</exception>
|
|
protected string GetString() {
|
|
_sb.Clear();
|
|
Position++;
|
|
while (ct != CharCategory.StringDelimiter) {
|
|
if (cc == '\\') Position++;
|
|
_sb.Append(cc);
|
|
Position++;
|
|
}
|
|
Position++;
|
|
return _sb.ToString();
|
|
}
|
|
/// <summary>
|
|
/// Reads an expression.
|
|
/// </summary>
|
|
/// <returns>An expression.</returns>
|
|
/// <exception cref="IndexOutOfRangeException">The end of the source string is reached.</exception>
|
|
protected PdtExpression GetExp() {
|
|
var ins = new LinkedList<PdtInstruction>();
|
|
int _;
|
|
InterpretExp(ins, -2, out _);
|
|
return new PdtExpression(ins);
|
|
}
|
|
|
|
readonly Dictionary<string, PdtExpression> defs = new();
|
|
/// <summary>
|
|
/// Creates an instance of the <see cref="PdtInterpreter" /> class.
|
|
/// </summary>
|
|
/// <param name="src">The source string.</param>
|
|
/// <param name="binder">The binder. May be <c>null</c>.</param>
|
|
public PdtInterpreter(string src, Binder binder) {
|
|
Source = src;
|
|
_binder = binder;
|
|
}
|
|
int[] m_formatVersion;
|
|
public int[] GetFormatVersion() {
|
|
if (m_formatVersion == null) InterpretDirectives();
|
|
return m_formatVersion;
|
|
}
|
|
/// <summary>
|
|
/// Interprets the source to an object.
|
|
/// </summary>
|
|
/// <param name="type">The output type.</param>
|
|
/// <returns>The interpreted object.</returns>
|
|
public object Interpret(Type type) {
|
|
try {
|
|
if (m_formatVersion == null) InterpretDirectives();
|
|
_binder ??= BinderAttribute.CreateBinderOfType(type);
|
|
return InterpretObject(type);
|
|
}
|
|
catch (Exception ex) {
|
|
throw new PdtParsingException(this, ex);
|
|
}
|
|
}
|
|
void InterpretDirectives() {
|
|
bool flag = false;
|
|
ws();
|
|
while (cc == '#') {
|
|
Position++;
|
|
switch (GetIdentifier()) {
|
|
case "ver":
|
|
ws();
|
|
Shared.Logger.Log(3, "PDT", "Legacy PDT directive #ver={0} found. Ignoring.", GetNumber());
|
|
break;
|
|
case "format":
|
|
ws();
|
|
m_formatVersion = (from i in GetNumber().Split('.') select int.Parse(i)).ToArray();
|
|
if (m_formatVersion.Length == 0)
|
|
throw new FormatException("Invalid format version");
|
|
if (m_formatVersion[0] != 1)
|
|
throw new NotSupportedException("Format not supported");
|
|
flag = true;
|
|
break;
|
|
case "define":
|
|
if (!flag) throw new FormatException("Format directive not found");
|
|
ws();
|
|
string dname = GetIdentifier();
|
|
ws();
|
|
PdtExpression dexp = GetExp();
|
|
defs.Add(dname, dexp);
|
|
break;
|
|
default:
|
|
throw new NotSupportedException("Unsupported directive found");
|
|
}
|
|
ws();
|
|
}
|
|
if (!flag) throw new FormatException("Format directive not found");
|
|
}
|
|
object InterpretObject(Type type) {
|
|
var result = Activator.CreateInstance(type);
|
|
bool pcflag = PairCollection.IsPairCollection(type);
|
|
while (true) {
|
|
try { ws(); }
|
|
catch (IndexOutOfRangeException) { return result; }
|
|
if (cc == '}') {
|
|
GetChar();
|
|
return result;
|
|
}
|
|
object pkey = InterpretKey(type);
|
|
char c = GetChar();
|
|
switch (c) {
|
|
case '{':
|
|
InterpretObjectInternal<ElementListAttribute>(pcflag, type, pkey, result, type => InterpretObject(type));
|
|
break;
|
|
case ':':
|
|
case ';':
|
|
var exp = c == ';' ? PdtExpression.Empty : GetExp();
|
|
InterpretObjectInternal<PropertyListAttribute>(pcflag, type, pkey, result, type => _binder.ChangeType(exp, type, null));
|
|
break;
|
|
default:
|
|
throw new InvalidOperationException("Internal error: Invalid key interpretation");
|
|
}
|
|
}
|
|
}
|
|
void InterpretObjectInternal<T>(bool pcflag, Type type, object pkey, object result, Func<Type, object> vfunc) where T : Attribute {
|
|
if (pcflag) {
|
|
using var collection = new PairCollection(result);
|
|
var ktype = type.GetGenericArguments()[0];
|
|
var ptype = type.GetGenericArguments()[1];
|
|
object key = _binder.ChangeType(pkey, ktype, null);
|
|
object value = vfunc(ptype);
|
|
collection.Add(key, value);
|
|
}
|
|
else {
|
|
MemberInfo prop = null;
|
|
bool flag = false;
|
|
if (pkey is string pname) prop = FieldLikeHelper.GetMember(type, pname);
|
|
if (prop == null) {
|
|
prop = FieldLikeHelper.FindMemberWithAttribute<T>(type);
|
|
flag = true;
|
|
}
|
|
if (prop == null) throw new MissingMemberException(string.Format("The property \"{0}\" is not found", pkey));
|
|
Type ptype = FieldLikeHelper.GetMemberType(prop);
|
|
if (flag) {
|
|
var origCollection = FieldLikeHelper.GetValue(prop, result);
|
|
if (origCollection == null) {
|
|
FieldLikeHelper.SetValue(prop, result, origCollection = Activator.CreateInstance(ptype));
|
|
}
|
|
using var collection = new PairCollection(origCollection);
|
|
var ktype = ptype.GetGenericArguments()[0];
|
|
var vtype = ptype.GetGenericArguments()[1];
|
|
object key = _binder.ChangeType(pkey, ktype, null);
|
|
object value = vfunc(vtype);
|
|
collection.Add(key, value);
|
|
}
|
|
else FieldLikeHelper.SetValue(prop, result, vfunc(ptype), _binder);
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// Interprets a key from the current position.
|
|
/// </summary>
|
|
/// <returns>The interpreted key.</returns>
|
|
protected virtual object InterpretKey(Type type) {
|
|
return tokenb(CharCategory.EndOfKey).Trim();
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// The exception that is thrown when the interpretation of a PDT fails.
|
|
/// </summary>
|
|
public class PdtParsingException : Exception {
|
|
public PdtParsingException(PdtInterpreter interpreter) : this(interpreter, null) { }
|
|
public PdtParsingException(PdtInterpreter interpreter, Exception innerException)
|
|
: base(GenerateMessage(interpreter, innerException), innerException) { }
|
|
static string GenerateMessage(PdtInterpreter interpreter, Exception innerException) {
|
|
string src = interpreter.Source;
|
|
int pos = interpreter.Position;
|
|
if (pos >= src.Length) return "Failed to interpret the PDT: There are some missing or redundant tokens";
|
|
int lineStartPos = src.LastIndexOf('\n', pos) + 1;
|
|
int previewStartPos = src.LastIndexOf('\n', pos, CMath.Min(64, pos));
|
|
if (previewStartPos == -1) {
|
|
previewStartPos = pos - 64;
|
|
if (previewStartPos < 0) previewStartPos = 0;
|
|
}
|
|
else previewStartPos++;
|
|
int previewEndPos = src.IndexOf('\n', pos, CMath.Min(64, src.Length - pos));
|
|
if (previewEndPos == -1) {
|
|
previewEndPos = pos + 64;
|
|
if (previewEndPos > src.Length) previewEndPos = src.Length;
|
|
}
|
|
return string.Format(
|
|
"Failed to interpret the PDT at line {0}, position {1}: {2}\n{3}",
|
|
src.Take(interpreter.Position).Count(c => c == '\n') + 1,
|
|
pos - lineStartPos + 1,
|
|
innerException == null ? "Unknown error" : innerException.Message,
|
|
src[previewStartPos..previewEndPos]
|
|
);
|
|
}
|
|
}
|
|
}
|