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 { /// /// Interpreter for Property Definition Tree (PDT) file format. /// 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, }; /// /// Interprets a source string to an object of type . /// /// The source string. /// The interpreted object. public static T Interpret(string src) { return Interpret(src, BinderAttribute.CreateBinderOfType(typeof(T))); } /// /// Interprets a source string to an object of type with a binder. /// /// The source string. /// The binder. /// The interpreted object. public static T Interpret(string src, Binder binder) { return (T)new PdtInterpreter(src, binder).Interpret(typeof(T)); } /// /// The source string. /// public string Source { get; protected set; } protected Binder _binder; /// /// The current position in the string being parsed by the interpreter. /// public int Position { get; protected set; } readonly StringBuilder _sb = new(); #pragma warning disable IDE1006 /// /// The character at the current position. /// /// The end of the source string is reached. protected char cc { get { return Source[Position]; } } /// /// The category of the character. /// /// The end of the source string is reached. protected CharCategory ct { get { return (CharCategory)cm[cc]; } } /// /// Reads a token until a character of type is met. /// /// The type filter. /// A token from the current position (inclusive) to the next character of type (exclusive). /// No character of type is met. protected string tokenb(CharCategory flag) { int sp = Position; while ((ct & flag) == 0) Position++; return Source[sp..Position]; } /// /// Reads a token until a character that is not of type is met. /// /// The type filter. /// A token from the current position (inclusive) to the next character that is not of type (exclusive). /// No character that is not of type is met. protected string tokenw(CharCategory flag) { int sp = Position; while ((ct & flag) != 0) Position++; return Source[sp..Position]; } /// /// Skips over whitespaces. /// /// The end of the source string is reached. protected void ws() { while ((ct & CharCategory.WhiteSpace) != 0) Position++; } #pragma warning restore IDE1006 /// /// Reads the current character and increments the position. /// /// The current character. /// The end of the source string is reached. protected char GetChar() { char r = cc; Position++; return r; } /// /// Reads an identifier. /// /// An identifier. /// The end of the source string is reached. protected string GetIdentifier() { if ((ct & CharCategory.IdentifierBegin) == 0) return ""; return tokenw(CharCategory.Identifier); } /// /// Reads a number. /// /// A number. /// The end of the source string is reached. protected string GetNumber() { return tokenw(CharCategory.Digit); } /// /// Reads a string. /// /// A string. /// The end of the source string is reached. protected string GetString() { _sb.Clear(); Position++; while (ct != CharCategory.StringDelimiter) { if (cc == '\\') Position++; _sb.Append(cc); Position++; } Position++; return _sb.ToString(); } /// /// Reads an expression. /// /// An expression. /// The end of the source string is reached. protected PdtExpression GetExp() { var ins = new LinkedList(); int _; InterpretExp(ins, -2, out _); return new PdtExpression(ins); } readonly Dictionary defs = new(); /// /// Creates an instance of the class. /// /// The source string. /// The binder. May be null. public PdtInterpreter(string src, Binder binder) { Source = src; _binder = binder; } int[] m_formatVersion; public int[] GetFormatVersion() { if (m_formatVersion == null) InterpretDirectives(); return m_formatVersion; } /// /// Interprets the source to an object. /// /// The output type. /// The interpreted object. 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(pcflag, type, pkey, result, type => InterpretObject(type)); break; case ':': case ';': var exp = c == ';' ? PdtExpression.Empty : GetExp(); InterpretObjectInternal(pcflag, type, pkey, result, type => _binder.ChangeType(exp, type, null)); break; default: throw new InvalidOperationException("Internal error: Invalid key interpretation"); } } } void InterpretObjectInternal(bool pcflag, Type type, object pkey, object result, Func 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(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); } } /// /// Interprets a key from the current position. /// /// The interpreted key. protected virtual object InterpretKey(Type type) { return tokenb(CharCategory.EndOfKey).Trim(); } } /// /// The exception that is thrown when the interpretation of a PDT fails. /// 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] ); } } }