using Cryville.Common.Collections; using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text.RegularExpressions; namespace Cryville.Common.Pdt { /// /// Interpreter for Property Definition Tree (PDT) file format. /// public partial class PdtInterpreter { /// /// The character category map. /// /// /// /// 0x0001White Space /// 0x0010Identifier /// 0x0020Identifier Begin /// 0x0040Digit /// 0x0080Operator /// 0x0100String /// 0x0200Opening Bracket /// 0x0400Closing Bracket /// 0x0800End of Expression /// 0x1000End of Key /// /// 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; private set; } Binder _binder; /// /// The current position in the string being parsed by the interpreter. /// public int Position { get; private set; } #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 int ct { get { return 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(int flag) { int sp = Position; while ((ct & flag) == 0) Position++; return Source.Substring(sp, Position - sp); } /// /// 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(int flag) { int sp = Position; while ((ct & flag) != 0) Position++; return Source.Substring(sp, Position - sp); } /// /// Skips over whitespaces. /// /// The end of the source string is reached. protected void ws() { while ((ct & 0x0001) != 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 & 0x0020) == 0) return ""; return tokenw(0x0010); } /// /// Reads a number. /// /// A number. /// The end of the source string is reached. protected string GetNumber() { return tokenw(0x0040); } /// /// Reads a string. /// /// A string. /// The end of the source string is reached. protected string GetString() { int sp = Position; do { if (cc == '\\') Position++; Position++; } while (ct != 0x0100); Position++; return Regex.Replace(Source.Substring(sp + 1, Position - sp - 2), @"\\(.)", "$1"); } /// /// 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 Dictionary(); /// /// 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(); if (_binder == null) _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(); Logger.Log("main", 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 = ReflectionHelper.InvokeEmptyConstructor(type); bool dictflag = typeof(IDictionary).IsAssignableFrom(type); while (true) { try { ws(); } catch (IndexOutOfRangeException) { return result; } if (cc == '}') { GetChar(); return result; } object pkey = InterpretKey(type); char c = GetChar(); switch (c) { case '{': if (dictflag) { var ktype = type.GetGenericArguments()[0]; var ptype = type.GetGenericArguments()[1]; object key = _binder.ChangeType(pkey, ktype, null); object value = InterpretObject(ptype); ((IDictionary)result).Add(key, value); } else { MemberInfo prop = null; bool flag = false; if (pkey is string) prop = ReflectionHelper.GetMember(type, (string)pkey); if (prop == null) flag = ReflectionHelper.TryFindMemberWithAttribute(type, out prop); if (prop == null) throw new MissingMemberException(string.Format("The property \"{0}\" is not found", pkey)); Type ptype = ReflectionHelper.GetMemberType(prop); if (flag) { using (var collection = new PairCollection(ReflectionHelper.GetValue(prop, result))) { var ktype = ptype.GetGenericArguments()[0]; var vtype = ptype.GetGenericArguments()[1]; object key = _binder.ChangeType(pkey, ktype, null); object value = InterpretObject(vtype); collection.Add(key, value); } } else ReflectionHelper.SetValue(prop, result, InterpretObject(ptype)); } break; case ':': case ';': var exp = c == ';' ? _emptyexp : GetExp(); if (dictflag) { var ktype = type.GetGenericArguments()[0]; var vtype = type.GetGenericArguments()[1]; object key = _binder.ChangeType(pkey, ktype, null); object value = _binder.ChangeType(exp, vtype, null); ((IDictionary)result).Add(key, value); } else { MemberInfo prop = null; bool flag = false; if (pkey is string) prop = ReflectionHelper.GetMember(type, (string)pkey); if (prop == null) flag = ReflectionHelper.TryFindMemberWithAttribute(type, out prop); if (prop == null) throw new MissingMemberException(string.Format("The property \"{0}\" is not found", pkey)); var ptype = ReflectionHelper.GetMemberType(prop); if (flag) { using (var collection = new PairCollection(ReflectionHelper.GetValue(prop, result))) { var ktype = ptype.GetGenericArguments()[0]; var vtype = ptype.GetGenericArguments()[1]; object key = _binder.ChangeType(pkey, ktype, null); object value = _binder.ChangeType(exp, vtype, null); collection.Add(key, value); } } else { object value = _binder.ChangeType(exp, ptype, null); ReflectionHelper.SetValue(prop, result, value, _binder); } } break; default: throw new InvalidOperationException("Internal error: Invalid key interpretation"); } } } /// /// Interprets a key from the current position. /// /// The interpreted key. protected virtual object InterpretKey(Type type) { return tokenb(0x1000).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, 64); if (previewStartPos == -1) { previewStartPos = pos - 64; if (previewStartPos < 0) previewStartPos = 0; } else previewStartPos++; int previewEndPos = src.IndexOf('\n', pos, 64); 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.Substring(previewStartPos, previewEndPos - previewStartPos) ); } } }