From fcfdaa6b5e599a940d72840b249e54dc3aba4e0e Mon Sep 17 00:00:00 2001 From: Mohammad Hadi Hosseinpour Date: Tue, 4 Apr 2017 04:38:06 +0430 Subject: [PATCH 1/8] Improved TL scheme transcompiler functionality --- TeleSharp.Generator/Constructor.tmp | 4 +- TeleSharp.Generator/ConstructorAbs.tmp | 1 + TeleSharp.Generator/Options.cs | 1103 +++++++++++++++++ TeleSharp.Generator/Program.cs | 284 ++++- TeleSharp.Generator/TL2JSON.cs | 110 ++ .../TeleSharp.Generator.csproj | 29 + 6 files changed, 1494 insertions(+), 37 deletions(-) create mode 100644 TeleSharp.Generator/Options.cs create mode 100644 TeleSharp.Generator/TL2JSON.cs diff --git a/TeleSharp.Generator/Constructor.tmp b/TeleSharp.Generator/Constructor.tmp index 73bf205..0653e54 100644 --- a/TeleSharp.Generator/Constructor.tmp +++ b/TeleSharp.Generator/Constructor.tmp @@ -17,9 +17,7 @@ namespace /* NAMESPACE */ return /*Constructor*/; } } - - /* PARAMS */ - + /* OVERRIDE_PARAMS */ public void ComputeFlags() { /* COMPUTE */ diff --git a/TeleSharp.Generator/ConstructorAbs.tmp b/TeleSharp.Generator/ConstructorAbs.tmp index 8a6dd2c..75b93b9 100644 --- a/TeleSharp.Generator/ConstructorAbs.tmp +++ b/TeleSharp.Generator/ConstructorAbs.tmp @@ -9,5 +9,6 @@ namespace /* NAMESPACE */ { public abstract class /* NAME */ : TLObject { + /* PARAMS */ } } diff --git a/TeleSharp.Generator/Options.cs b/TeleSharp.Generator/Options.cs new file mode 100644 index 0000000..9a43b19 --- /dev/null +++ b/TeleSharp.Generator/Options.cs @@ -0,0 +1,1103 @@ +// +// Options.cs +// +// Authors: +// Jonathan Pryor +// +// Copyright (C) 2008 Novell (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +// Compile With: +// gmcs -debug+ -r:System.Core Options.cs -o:NDesk.Options.dll +// gmcs -debug+ -d:LINQ -r:System.Core Options.cs -o:NDesk.Options.dll +// +// The LINQ version just changes the implementation of +// OptionSet.Parse(IEnumerable), and confers no semantic changes. + +// +// A Getopt::Long-inspired option parsing library for C#. +// +// NDesk.Options.OptionSet is built upon a key/value table, where the +// key is a option format string and the value is a delegate that is +// invoked when the format string is matched. +// +// Option format strings: +// Regex-like BNF Grammar: +// name: .+ +// type: [=:] +// sep: ( [^{}]+ | '{' .+ '}' )? +// aliases: ( name type sep ) ( '|' name type sep )* +// +// Each '|'-delimited name is an alias for the associated action. If the +// format string ends in a '=', it has a required value. If the format +// string ends in a ':', it has an optional value. If neither '=' or ':' +// is present, no value is supported. `=' or `:' need only be defined on one +// alias, but if they are provided on more than one they must be consistent. +// +// Each alias portion may also end with a "key/value separator", which is used +// to split option values if the option accepts > 1 value. If not specified, +// it defaults to '=' and ':'. If specified, it can be any character except +// '{' and '}' OR the *string* between '{' and '}'. If no separator should be +// used (i.e. the separate values should be distinct arguments), then "{}" +// should be used as the separator. +// +// Options are extracted either from the current option by looking for +// the option name followed by an '=' or ':', or is taken from the +// following option IFF: +// - The current option does not contain a '=' or a ':' +// - The current option requires a value (i.e. not a Option type of ':') +// +// The `name' used in the option format string does NOT include any leading +// option indicator, such as '-', '--', or '/'. All three of these are +// permitted/required on any named option. +// +// Option bundling is permitted so long as: +// - '-' is used to start the option group +// - all of the bundled options are a single character +// - at most one of the bundled options accepts a value, and the value +// provided starts from the next character to the end of the string. +// +// This allows specifying '-a -b -c' as '-abc', and specifying '-D name=value' +// as '-Dname=value'. +// +// Option processing is disabled by specifying "--". All options after "--" +// are returned by OptionSet.Parse() unchanged and unprocessed. +// +// Unprocessed options are returned from OptionSet.Parse(). +// +// Examples: +// int verbose = 0; +// OptionSet p = new OptionSet () +// .Add ("v", v => ++verbose) +// .Add ("name=|value=", v => Console.WriteLine (v)); +// p.Parse (new string[]{"-v", "--v", "/v", "-name=A", "/name", "B", "extra"}); +// +// The above would parse the argument string array, and would invoke the +// lambda expression three times, setting `verbose' to 3 when complete. +// It would also print out "A" and "B" to standard output. +// The returned array would contain the string "extra". +// +// C# 3.0 collection initializers are supported and encouraged: +// var p = new OptionSet () { +// { "h|?|help", v => ShowHelp () }, +// }; +// +// System.ComponentModel.TypeConverter is also supported, allowing the use of +// custom data types in the callback type; TypeConverter.ConvertFromString() +// is used to convert the value option to an instance of the specified +// type: +// +// var p = new OptionSet () { +// { "foo=", (Foo f) => Console.WriteLine (f.ToString ()) }, +// }; +// +// Random other tidbits: +// - Boolean options (those w/o '=' or ':' in the option format string) +// are explicitly enabled if they are followed with '+', and explicitly +// disabled if they are followed with '-': +// string a = null; +// var p = new OptionSet () { +// { "a", s => a = s }, +// }; +// p.Parse (new string[]{"-a"}); // sets v != null +// p.Parse (new string[]{"-a+"}); // sets v != null +// p.Parse (new string[]{"-a-"}); // sets v == null +// + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Globalization; +using System.IO; +using System.Runtime.Serialization; +using System.Security.Permissions; +using System.Text; +using System.Text.RegularExpressions; + +#if LINQ +using System.Linq; +#endif + +#if TEST +using NDesk.Options; +#endif + +namespace NDesk.Options { + + public class OptionValueCollection : IList, IList { + + List values = new List (); + OptionContext c; + + internal OptionValueCollection (OptionContext c) + { + this.c = c; + } + + #region ICollection + void ICollection.CopyTo (Array array, int index) {(values as ICollection).CopyTo (array, index);} + bool ICollection.IsSynchronized {get {return (values as ICollection).IsSynchronized;}} + object ICollection.SyncRoot {get {return (values as ICollection).SyncRoot;}} + #endregion + + #region ICollection + public void Add (string item) {values.Add (item);} + public void Clear () {values.Clear ();} + public bool Contains (string item) {return values.Contains (item);} + public void CopyTo (string[] array, int arrayIndex) {values.CopyTo (array, arrayIndex);} + public bool Remove (string item) {return values.Remove (item);} + public int Count {get {return values.Count;}} + public bool IsReadOnly {get {return false;}} + #endregion + + #region IEnumerable + IEnumerator IEnumerable.GetEnumerator () {return values.GetEnumerator ();} + #endregion + + #region IEnumerable + public IEnumerator GetEnumerator () {return values.GetEnumerator ();} + #endregion + + #region IList + int IList.Add (object value) {return (values as IList).Add (value);} + bool IList.Contains (object value) {return (values as IList).Contains (value);} + int IList.IndexOf (object value) {return (values as IList).IndexOf (value);} + void IList.Insert (int index, object value) {(values as IList).Insert (index, value);} + void IList.Remove (object value) {(values as IList).Remove (value);} + void IList.RemoveAt (int index) {(values as IList).RemoveAt (index);} + bool IList.IsFixedSize {get {return false;}} + object IList.this [int index] {get {return this [index];} set {(values as IList)[index] = value;}} + #endregion + + #region IList + public int IndexOf (string item) {return values.IndexOf (item);} + public void Insert (int index, string item) {values.Insert (index, item);} + public void RemoveAt (int index) {values.RemoveAt (index);} + + private void AssertValid (int index) + { + if (c.Option == null) + throw new InvalidOperationException ("OptionContext.Option is null."); + if (index >= c.Option.MaxValueCount) + throw new ArgumentOutOfRangeException ("index"); + if (c.Option.OptionValueType == OptionValueType.Required && + index >= values.Count) + throw new OptionException (string.Format ( + c.OptionSet.MessageLocalizer ("Missing required value for option '{0}'."), c.OptionName), + c.OptionName); + } + + public string this [int index] { + get { + AssertValid (index); + return index >= values.Count ? null : values [index]; + } + set { + values [index] = value; + } + } + #endregion + + public List ToList () + { + return new List (values); + } + + public string[] ToArray () + { + return values.ToArray (); + } + + public override string ToString () + { + return string.Join (", ", values.ToArray ()); + } + } + + public class OptionContext { + private Option option; + private string name; + private int index; + private OptionSet set; + private OptionValueCollection c; + + public OptionContext (OptionSet set) + { + this.set = set; + this.c = new OptionValueCollection (this); + } + + public Option Option { + get {return option;} + set {option = value;} + } + + public string OptionName { + get {return name;} + set {name = value;} + } + + public int OptionIndex { + get {return index;} + set {index = value;} + } + + public OptionSet OptionSet { + get {return set;} + } + + public OptionValueCollection OptionValues { + get {return c;} + } + } + + public enum OptionValueType { + None, + Optional, + Required, + } + + public abstract class Option { + string prototype, description; + string[] names; + OptionValueType type; + int count; + string[] separators; + + protected Option (string prototype, string description) + : this (prototype, description, 1) + { + } + + protected Option (string prototype, string description, int maxValueCount) + { + if (prototype == null) + throw new ArgumentNullException ("prototype"); + if (prototype.Length == 0) + throw new ArgumentException ("Cannot be the empty string.", "prototype"); + if (maxValueCount < 0) + throw new ArgumentOutOfRangeException ("maxValueCount"); + + this.prototype = prototype; + this.names = prototype.Split ('|'); + this.description = description; + this.count = maxValueCount; + this.type = ParsePrototype (); + + if (this.count == 0 && type != OptionValueType.None) + throw new ArgumentException ( + "Cannot provide maxValueCount of 0 for OptionValueType.Required or " + + "OptionValueType.Optional.", + "maxValueCount"); + if (this.type == OptionValueType.None && maxValueCount > 1) + throw new ArgumentException ( + string.Format ("Cannot provide maxValueCount of {0} for OptionValueType.None.", maxValueCount), + "maxValueCount"); + if (Array.IndexOf (names, "<>") >= 0 && + ((names.Length == 1 && this.type != OptionValueType.None) || + (names.Length > 1 && this.MaxValueCount > 1))) + throw new ArgumentException ( + "The default option handler '<>' cannot require values.", + "prototype"); + } + + public string Prototype {get {return prototype;}} + public string Description {get {return description;}} + public OptionValueType OptionValueType {get {return type;}} + public int MaxValueCount {get {return count;}} + + public string[] GetNames () + { + return (string[]) names.Clone (); + } + + public string[] GetValueSeparators () + { + if (separators == null) + return new string [0]; + return (string[]) separators.Clone (); + } + + protected static T Parse (string value, OptionContext c) + { + TypeConverter conv = TypeDescriptor.GetConverter (typeof (T)); + T t = default (T); + try { + if (value != null) + t = (T) conv.ConvertFromString (value); + } + catch (Exception e) { + throw new OptionException ( + string.Format ( + c.OptionSet.MessageLocalizer ("Could not convert string `{0}' to type {1} for option `{2}'."), + value, typeof (T).Name, c.OptionName), + c.OptionName, e); + } + return t; + } + + internal string[] Names {get {return names;}} + internal string[] ValueSeparators {get {return separators;}} + + static readonly char[] NameTerminator = new char[]{'=', ':'}; + + private OptionValueType ParsePrototype () + { + char type = '\0'; + List seps = new List (); + for (int i = 0; i < names.Length; ++i) { + string name = names [i]; + if (name.Length == 0) + throw new ArgumentException ("Empty option names are not supported.", "prototype"); + + int end = name.IndexOfAny (NameTerminator); + if (end == -1) + continue; + names [i] = name.Substring (0, end); + if (type == '\0' || type == name [end]) + type = name [end]; + else + throw new ArgumentException ( + string.Format ("Conflicting option types: '{0}' vs. '{1}'.", type, name [end]), + "prototype"); + AddSeparators (name, end, seps); + } + + if (type == '\0') + return OptionValueType.None; + + if (count <= 1 && seps.Count != 0) + throw new ArgumentException ( + string.Format ("Cannot provide key/value separators for Options taking {0} value(s).", count), + "prototype"); + if (count > 1) { + if (seps.Count == 0) + this.separators = new string[]{":", "="}; + else if (seps.Count == 1 && seps [0].Length == 0) + this.separators = null; + else + this.separators = seps.ToArray (); + } + + return type == '=' ? OptionValueType.Required : OptionValueType.Optional; + } + + private static void AddSeparators (string name, int end, ICollection seps) + { + int start = -1; + for (int i = end+1; i < name.Length; ++i) { + switch (name [i]) { + case '{': + if (start != -1) + throw new ArgumentException ( + string.Format ("Ill-formed name/value separator found in \"{0}\".", name), + "prototype"); + start = i+1; + break; + case '}': + if (start == -1) + throw new ArgumentException ( + string.Format ("Ill-formed name/value separator found in \"{0}\".", name), + "prototype"); + seps.Add (name.Substring (start, i-start)); + start = -1; + break; + default: + if (start == -1) + seps.Add (name [i].ToString ()); + break; + } + } + if (start != -1) + throw new ArgumentException ( + string.Format ("Ill-formed name/value separator found in \"{0}\".", name), + "prototype"); + } + + public void Invoke (OptionContext c) + { + OnParseComplete (c); + c.OptionName = null; + c.Option = null; + c.OptionValues.Clear (); + } + + protected abstract void OnParseComplete (OptionContext c); + + public override string ToString () + { + return Prototype; + } + } + + [Serializable] + public class OptionException : Exception { + private string option; + + public OptionException () + { + } + + public OptionException (string message, string optionName) + : base (message) + { + this.option = optionName; + } + + public OptionException (string message, string optionName, Exception innerException) + : base (message, innerException) + { + this.option = optionName; + } + + protected OptionException (SerializationInfo info, StreamingContext context) + : base (info, context) + { + this.option = info.GetString ("OptionName"); + } + + public string OptionName { + get {return this.option;} + } + + [SecurityPermission (SecurityAction.LinkDemand, SerializationFormatter = true)] + public override void GetObjectData (SerializationInfo info, StreamingContext context) + { + base.GetObjectData (info, context); + info.AddValue ("OptionName", option); + } + } + + public delegate void OptionAction (TKey key, TValue value); + + public class OptionSet : KeyedCollection + { + public OptionSet () + : this (delegate (string f) {return f;}) + { + } + + public OptionSet (Converter localizer) + { + this.localizer = localizer; + } + + Converter localizer; + + public Converter MessageLocalizer { + get {return localizer;} + } + + protected override string GetKeyForItem (Option item) + { + if (item == null) + throw new ArgumentNullException ("option"); + if (item.Names != null && item.Names.Length > 0) + return item.Names [0]; + // This should never happen, as it's invalid for Option to be + // constructed w/o any names. + throw new InvalidOperationException ("Option has no names!"); + } + + [Obsolete ("Use KeyedCollection.this[string]")] + protected Option GetOptionForName (string option) + { + if (option == null) + throw new ArgumentNullException ("option"); + try { + return base [option]; + } + catch (KeyNotFoundException) { + return null; + } + } + + protected override void InsertItem (int index, Option item) + { + base.InsertItem (index, item); + AddImpl (item); + } + + protected override void RemoveItem (int index) + { + base.RemoveItem (index); + Option p = Items [index]; + // KeyedCollection.RemoveItem() handles the 0th item + for (int i = 1; i < p.Names.Length; ++i) { + Dictionary.Remove (p.Names [i]); + } + } + + protected override void SetItem (int index, Option item) + { + base.SetItem (index, item); + RemoveItem (index); + AddImpl (item); + } + + private void AddImpl (Option option) + { + if (option == null) + throw new ArgumentNullException ("option"); + List added = new List (option.Names.Length); + try { + // KeyedCollection.InsertItem/SetItem handle the 0th name. + for (int i = 1; i < option.Names.Length; ++i) { + Dictionary.Add (option.Names [i], option); + added.Add (option.Names [i]); + } + } + catch (Exception) { + foreach (string name in added) + Dictionary.Remove (name); + throw; + } + } + + public new OptionSet Add (Option option) + { + base.Add (option); + return this; + } + + sealed class ActionOption : Option { + Action action; + + public ActionOption (string prototype, string description, int count, Action action) + : base (prototype, description, count) + { + if (action == null) + throw new ArgumentNullException ("action"); + this.action = action; + } + + protected override void OnParseComplete (OptionContext c) + { + action (c.OptionValues); + } + } + + public OptionSet Add (string prototype, Action action) + { + return Add (prototype, null, action); + } + + public OptionSet Add (string prototype, string description, Action action) + { + if (action == null) + throw new ArgumentNullException ("action"); + Option p = new ActionOption (prototype, description, 1, + delegate (OptionValueCollection v) { action (v [0]); }); + base.Add (p); + return this; + } + + public OptionSet Add (string prototype, OptionAction action) + { + return Add (prototype, null, action); + } + + public OptionSet Add (string prototype, string description, OptionAction action) + { + if (action == null) + throw new ArgumentNullException ("action"); + Option p = new ActionOption (prototype, description, 2, + delegate (OptionValueCollection v) {action (v [0], v [1]);}); + base.Add (p); + return this; + } + + sealed class ActionOption : Option { + Action action; + + public ActionOption (string prototype, string description, Action action) + : base (prototype, description, 1) + { + if (action == null) + throw new ArgumentNullException ("action"); + this.action = action; + } + + protected override void OnParseComplete (OptionContext c) + { + action (Parse (c.OptionValues [0], c)); + } + } + + sealed class ActionOption : Option { + OptionAction action; + + public ActionOption (string prototype, string description, OptionAction action) + : base (prototype, description, 2) + { + if (action == null) + throw new ArgumentNullException ("action"); + this.action = action; + } + + protected override void OnParseComplete (OptionContext c) + { + action ( + Parse (c.OptionValues [0], c), + Parse (c.OptionValues [1], c)); + } + } + + public OptionSet Add (string prototype, Action action) + { + return Add (prototype, null, action); + } + + public OptionSet Add (string prototype, string description, Action action) + { + return Add (new ActionOption (prototype, description, action)); + } + + public OptionSet Add (string prototype, OptionAction action) + { + return Add (prototype, null, action); + } + + public OptionSet Add (string prototype, string description, OptionAction action) + { + return Add (new ActionOption (prototype, description, action)); + } + + protected virtual OptionContext CreateOptionContext () + { + return new OptionContext (this); + } + +#if LINQ + public List Parse (IEnumerable arguments) + { + bool process = true; + OptionContext c = CreateOptionContext (); + c.OptionIndex = -1; + var def = GetOptionForName ("<>"); + var unprocessed = + from argument in arguments + where ++c.OptionIndex >= 0 && (process || def != null) + ? process + ? argument == "--" + ? (process = false) + : !Parse (argument, c) + ? def != null + ? Unprocessed (null, def, c, argument) + : true + : false + : def != null + ? Unprocessed (null, def, c, argument) + : true + : true + select argument; + List r = unprocessed.ToList (); + if (c.Option != null) + c.Option.Invoke (c); + return r; + } +#else + public List Parse (IEnumerable arguments) + { + OptionContext c = CreateOptionContext (); + c.OptionIndex = -1; + bool process = true; + List unprocessed = new List (); + Option def = Contains ("<>") ? this ["<>"] : null; + foreach (string argument in arguments) { + ++c.OptionIndex; + if (argument == "--") { + process = false; + continue; + } + if (!process) { + Unprocessed (unprocessed, def, c, argument); + continue; + } + if (!Parse (argument, c)) + Unprocessed (unprocessed, def, c, argument); + } + if (c.Option != null) + c.Option.Invoke (c); + return unprocessed; + } +#endif + + private static bool Unprocessed (ICollection extra, Option def, OptionContext c, string argument) + { + if (def == null) { + extra.Add (argument); + return false; + } + c.OptionValues.Add (argument); + c.Option = def; + c.Option.Invoke (c); + return false; + } + + private readonly Regex ValueOption = new Regex ( + @"^(?--|-|/)(?[^:=]+)((?[:=])(?.*))?$"); + + protected bool GetOptionParts (string argument, out string flag, out string name, out string sep, out string value) + { + if (argument == null) + throw new ArgumentNullException ("argument"); + + flag = name = sep = value = null; + Match m = ValueOption.Match (argument); + if (!m.Success) { + return false; + } + flag = m.Groups ["flag"].Value; + name = m.Groups ["name"].Value; + if (m.Groups ["sep"].Success && m.Groups ["value"].Success) { + sep = m.Groups ["sep"].Value; + value = m.Groups ["value"].Value; + } + return true; + } + + protected virtual bool Parse (string argument, OptionContext c) + { + if (c.Option != null) { + ParseValue (argument, c); + return true; + } + + string f, n, s, v; + if (!GetOptionParts (argument, out f, out n, out s, out v)) + return false; + + Option p; + if (Contains (n)) { + p = this [n]; + c.OptionName = f + n; + c.Option = p; + switch (p.OptionValueType) { + case OptionValueType.None: + c.OptionValues.Add (n); + c.Option.Invoke (c); + break; + case OptionValueType.Optional: + case OptionValueType.Required: + ParseValue (v, c); + break; + } + return true; + } + // no match; is it a bool option? + if (ParseBool (argument, n, c)) + return true; + // is it a bundled option? + if (ParseBundledValue (f, string.Concat (n + s + v), c)) + return true; + + return false; + } + + private void ParseValue (string option, OptionContext c) + { + if (option != null) + foreach (string o in c.Option.ValueSeparators != null + ? option.Split (c.Option.ValueSeparators, StringSplitOptions.None) + : new string[]{option}) { + c.OptionValues.Add (o); + } + if (c.OptionValues.Count == c.Option.MaxValueCount || + c.Option.OptionValueType == OptionValueType.Optional) + c.Option.Invoke (c); + else if (c.OptionValues.Count > c.Option.MaxValueCount) { + throw new OptionException (localizer (string.Format ( + "Error: Found {0} option values when expecting {1}.", + c.OptionValues.Count, c.Option.MaxValueCount)), + c.OptionName); + } + } + + private bool ParseBool (string option, string n, OptionContext c) + { + Option p; + string rn; + if (n.Length >= 1 && (n [n.Length-1] == '+' || n [n.Length-1] == '-') && + Contains ((rn = n.Substring (0, n.Length-1)))) { + p = this [rn]; + string v = n [n.Length-1] == '+' ? option : null; + c.OptionName = option; + c.Option = p; + c.OptionValues.Add (v); + p.Invoke (c); + return true; + } + return false; + } + + private bool ParseBundledValue (string f, string n, OptionContext c) + { + if (f != "-") + return false; + for (int i = 0; i < n.Length; ++i) { + Option p; + string opt = f + n [i].ToString (); + string rn = n [i].ToString (); + if (!Contains (rn)) { + if (i == 0) + return false; + throw new OptionException (string.Format (localizer ( + "Cannot bundle unregistered option '{0}'."), opt), opt); + } + p = this [rn]; + switch (p.OptionValueType) { + case OptionValueType.None: + Invoke (c, opt, n, p); + break; + case OptionValueType.Optional: + case OptionValueType.Required: { + string v = n.Substring (i+1); + c.Option = p; + c.OptionName = opt; + ParseValue (v.Length != 0 ? v : null, c); + return true; + } + default: + throw new InvalidOperationException ("Unknown OptionValueType: " + p.OptionValueType); + } + } + return true; + } + + private static void Invoke (OptionContext c, string name, string value, Option option) + { + c.OptionName = name; + c.Option = option; + c.OptionValues.Add (value); + option.Invoke (c); + } + + private const int OptionWidth = 29; + + public void WriteOptionDescriptions (TextWriter o) + { + foreach (Option p in this) { + int written = 0; + if (!WriteOptionPrototype (o, p, ref written)) + continue; + + if (written < OptionWidth) + o.Write (new string (' ', OptionWidth - written)); + else { + o.WriteLine (); + o.Write (new string (' ', OptionWidth)); + } + + List lines = GetLines (localizer (GetDescription (p.Description))); + o.WriteLine (lines [0]); + string prefix = new string (' ', OptionWidth+2); + for (int i = 1; i < lines.Count; ++i) { + o.Write (prefix); + o.WriteLine (lines [i]); + } + } + } + + bool WriteOptionPrototype (TextWriter o, Option p, ref int written) + { + string[] names = p.Names; + + int i = GetNextOptionIndex (names, 0); + if (i == names.Length) + return false; + + if (names [i].Length == 1) { + Write (o, ref written, " -"); + Write (o, ref written, names [0]); + } + else { + Write (o, ref written, " --"); + Write (o, ref written, names [0]); + } + + for ( i = GetNextOptionIndex (names, i+1); + i < names.Length; i = GetNextOptionIndex (names, i+1)) { + Write (o, ref written, ", "); + Write (o, ref written, names [i].Length == 1 ? "-" : "--"); + Write (o, ref written, names [i]); + } + + if (p.OptionValueType == OptionValueType.Optional || + p.OptionValueType == OptionValueType.Required) { + if (p.OptionValueType == OptionValueType.Optional) { + Write (o, ref written, localizer ("[")); + } + Write (o, ref written, localizer ("=" + GetArgumentName (0, p.MaxValueCount, p.Description))); + string sep = p.ValueSeparators != null && p.ValueSeparators.Length > 0 + ? p.ValueSeparators [0] + : " "; + for (int c = 1; c < p.MaxValueCount; ++c) { + Write (o, ref written, localizer (sep + GetArgumentName (c, p.MaxValueCount, p.Description))); + } + if (p.OptionValueType == OptionValueType.Optional) { + Write (o, ref written, localizer ("]")); + } + } + return true; + } + + static int GetNextOptionIndex (string[] names, int i) + { + while (i < names.Length && names [i] == "<>") { + ++i; + } + return i; + } + + static void Write (TextWriter o, ref int n, string s) + { + n += s.Length; + o.Write (s); + } + + private static string GetArgumentName (int index, int maxIndex, string description) + { + if (description == null) + return maxIndex == 1 ? "VALUE" : "VALUE" + (index + 1); + string[] nameStart; + if (maxIndex == 1) + nameStart = new string[]{"{0:", "{"}; + else + nameStart = new string[]{"{" + index + ":"}; + for (int i = 0; i < nameStart.Length; ++i) { + int start, j = 0; + do { + start = description.IndexOf (nameStart [i], j); + } while (start >= 0 && j != 0 ? description [j++ - 1] == '{' : false); + if (start == -1) + continue; + int end = description.IndexOf ("}", start); + if (end == -1) + continue; + return description.Substring (start + nameStart [i].Length, end - start - nameStart [i].Length); + } + return maxIndex == 1 ? "VALUE" : "VALUE" + (index + 1); + } + + private static string GetDescription (string description) + { + if (description == null) + return string.Empty; + StringBuilder sb = new StringBuilder (description.Length); + int start = -1; + for (int i = 0; i < description.Length; ++i) { + switch (description [i]) { + case '{': + if (i == start) { + sb.Append ('{'); + start = -1; + } + else if (start < 0) + start = i + 1; + break; + case '}': + if (start < 0) { + if ((i+1) == description.Length || description [i+1] != '}') + throw new InvalidOperationException ("Invalid option description: " + description); + ++i; + sb.Append ("}"); + } + else { + sb.Append (description.Substring (start, i - start)); + start = -1; + } + break; + case ':': + if (start < 0) + goto default; + start = i + 1; + break; + default: + if (start < 0) + sb.Append (description [i]); + break; + } + } + return sb.ToString (); + } + + private static List GetLines (string description) + { + List lines = new List (); + if (string.IsNullOrEmpty (description)) { + lines.Add (string.Empty); + return lines; + } + int length = 80 - OptionWidth - 2; + int start = 0, end; + do { + end = GetLineEnd (start, length, description); + bool cont = false; + if (end < description.Length) { + char c = description [end]; + if (c == '-' || (char.IsWhiteSpace (c) && c != '\n')) + ++end; + else if (c != '\n') { + cont = true; + --end; + } + } + lines.Add (description.Substring (start, end - start)); + if (cont) { + lines [lines.Count-1] += "-"; + } + start = end; + if (start < description.Length && description [start] == '\n') + ++start; + } while (end < description.Length); + return lines; + } + + private static int GetLineEnd (int start, int length, string description) + { + int end = Math.Min (start + length, description.Length); + int sep = -1; + for (int i = start; i < end; ++i) { + switch (description [i]) { + case ' ': + case '\t': + case '\v': + case '-': + case ',': + case '.': + case ';': + sep = i; + break; + case '\n': + return i; + } + } + if (sep == -1 || end == description.Length) + return end; + return sep; + } + } +} + diff --git a/TeleSharp.Generator/Program.cs b/TeleSharp.Generator/Program.cs index 535017b..1b250fb 100644 --- a/TeleSharp.Generator/Program.cs +++ b/TeleSharp.Generator/Program.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using System.CodeDom; using System.Reflection; using System.Text.RegularExpressions; +using NDesk.Options; namespace TeleSharp.Generator { @@ -16,39 +17,195 @@ namespace TeleSharp.Generator static List keywords = new List(new string[] { "abstract", "as", "base", "bool", "break", "byte", "case", "catch", "char", "checked", "class", "const", "continue", "decimal", "default", "delegate", "do", "double", "else", "enum", "event", "explicit", "extern", "false", "finally", "fixed", "float", "for", "foreach", "goto", "if", "implicit", "in", "in", "int", "interface", "internal", "is", "lock", "long", "namespace", "new", "null", "object", "operator", "out", "out", "override", "params", "private", "protected", "public", "readonly", "ref", "return", "sbyte", "sealed", "short", "sizeof", "stackalloc", "static", "string", "struct", "switch", "this", "throw", "true", "try", "typeof", "uint", "ulong", "unchecked", "unsafe", "ushort", "using", "virtual", "void", "volatile", "while", "add", "alias", "ascending", "async", "await", "descending", "dynamic", "from", "get", "global", "group", "into", "join", "let", "orderby", "partial", "partial", "remove", "select", "set", "value", "var", "where", "where", "yield" }); static List interfacesList = new List(); static List classesList = new List(); + + static void DisplayHelp(bool full = true) + { + Console.WriteLine("TLSharp TL Parser v1.0, a TL schema to C# transcompiler"); + Console.WriteLine("usage: TeleSharp.Generator [OPTIONS] INPUT [OUTPUT]"); + Console.WriteLine(); + if (full) + { + Console.WriteLine("Options:"); + Console.WriteLine(" -f, --format Sets input format."); + Console.WriteLine(" Accepted formats are \"tl\" and \"json\"."); + Console.WriteLine(" --template-abstract Sets the abstract class template"); + Console.WriteLine(" --template-class Sets the class template"); + Console.WriteLine(" --template-method Sets the method template"); + Console.WriteLine(" --target-namespace Sets the target namespace"); + Console.WriteLine(" Default is \"TeleSharp.TL\""); + Console.WriteLine(" --output-json Only Parses TL and outputs schema as JSON"); + Console.WriteLine(" -h, --help Displays this help message"); + Console.WriteLine(); + Console.WriteLine("For more information, please read the manual,"); + Console.WriteLine("or visit the GitHub page."); + Console.WriteLine("Submit your bug reports to our GitHub repository."); + } + else + { + Console.WriteLine("Try `TeleSharp.Generator --help' for more options."); + } + } + + enum Format + { + TL, + JSON + } + static void Main(string[] args) { + string Json = ""; + string inputPath = ""; + string outputPath = ""; + Format format = Format.TL; + bool forceFormat = false; + bool outputJson = false; + bool showHelp = false; + string AbsStyle = File.ReadAllText("ConstructorAbs.tmp"); string NormalStyle = File.ReadAllText("Constructor.tmp"); string MethodStyle = File.ReadAllText("Method.tmp"); - //string method = File.ReadAllText("constructor.tt"); - string Json = ""; - string url; - if (args.Count() == 0) url = "tl-schema.json"; else url = args[0]; + string TargetNamespace = "TeleSharp.TL"; + + bool invalidFormat = false; + bool invalidTargetNamespace = false; + + OptionSet optionset = new OptionSet() + .Add("h|help", h => showHelp = h != null) + .Add("f|format=", f => + { + f = f ?? ""; + if (f != null) + forceFormat = true; + switch (f.ToLower()) + { + case "tl": + format = Format.TL; + break; + case "json": + format = Format.JSON; + break; + case "": + format = Format.TL; + break; + default: + invalidFormat = true; + break; + } + }) + .Add("target-namespace=", a => + { + if (!string.IsNullOrEmpty(a)) + { + Match m = Regex.Match(a, @"(@?[a-z_A-Z]\w+(?:\.@?[a-z_A-Z]\w+)*)"); + if (m.Success) + { + TargetNamespace = m.Groups[0].Value; + } + } + else + { + invalidTargetNamespace = true; + } + }) + .Add("output-json", a => outputJson = a != null) + .Add("template-abstract=", a => AbsStyle = (a != null) ? File.ReadAllText(a) : AbsStyle) + .Add("template-normal=", a => NormalStyle = (a != null) ? File.ReadAllText(a) : NormalStyle) + .Add("template-method=", a => MethodStyle = (a != null) ? File.ReadAllText(a) : MethodStyle); + List extra; + try + { + extra = optionset.Parse(args); + } + catch (OptionException e) + { + Console.Write("Error: "); + Console.WriteLine(e.Message); + Console.WriteLine("Try `TeleSharp.Generator --help' for more information."); + return; + } + if (showHelp) + { + DisplayHelp(true); + return; + } + if (extra == null || extra.Count == 0) + { + DisplayHelp(false); + return; + } + if (invalidFormat) + { + Console.WriteLine("Error: Invalid input format."); + Console.WriteLine("Try `TeleSharp.Generator --help' for more information."); + return; + } + if (invalidTargetNamespace) + { + Console.WriteLine("Error: Invalid target namespace."); + Console.WriteLine("Try `TeleSharp.Generator --help' for more information."); + return; + } + + + inputPath = extra[0]; + if (!forceFormat) + { + string ext = Path.GetExtension(extra[0]); + if (ext == "json") + { + format = Format.JSON; + } + } + if (extra.Count > 1) + { + outputPath = extra[1]; + } + else + { // no output path provided + if (!outputJson) + outputPath = Path.GetDirectoryName(Path.GetFullPath(extra[0])); + else + outputPath = Path.ChangeExtension(inputPath, ".json"); + } + + Console.WriteLine("TLSharp TL Parser v1.0, a TL schema to C# transcompiler"); + + + Json = File.ReadAllText(inputPath); + + if (format == Format.TL) + { // if input is tl, convert to json + Json = TL2JSON.ParseToJson(Json); + Console.WriteLine("Converting TL to JSON..."); + } + + if (outputJson) + { + File.WriteAllText(outputPath, Json); + return; + } + #region Translate to C# - Json = File.ReadAllText(url); - FileStream file = File.OpenWrite("Result.cs"); - StreamWriter sw = new StreamWriter(file); Schema schema = JsonConvert.DeserializeObject(Json); foreach (var c in schema.constructors) { interfacesList.Add(c.type); classesList.Add(c.predicate); } + Console.WriteLine("Implementing abstract classes..."); + var abstractParams = new Dictionary>(); foreach (var c in schema.constructors) { - var list = schema.constructors.Where(x => x.type == c.type); + var list = schema.constructors.Where(x => x.type == c.type); // check if there is a dependence on this type (it is an abstract class) if (list.Count() > 1) { - string path = (GetNameSpace(c.type).Replace("TeleSharp.TL", "TL\\").Replace(".", "") + "\\" + GetNameofClass(c.type, true) + ".cs").Replace("\\\\", "\\"); + string path = Path.Combine(outputPath, GetNameSpace(c.type, TargetNamespace).Replace(TargetNamespace, @"TL\").Replace(".", ""), GetNameofClass(c.type, true) + ".cs").Replace(@"\\", @"\"); FileStream classFile = MakeFile(path); using (StreamWriter writer = new StreamWriter(classFile)) { - string nspace = (GetNameSpace(c.type).Replace("TeleSharp.TL", "TL\\").Replace(".", "")).Replace("\\\\", "\\").Replace("\\", "."); - if (nspace.EndsWith(".")) - nspace = nspace.Remove(nspace.Length - 1, 1); - string temp = AbsStyle.Replace("/* NAMESPACE */", "TeleSharp." + nspace); + string temp = AbsStyle.Replace("/* NAMESPACE */", GetNameSpace(c.type, TargetNamespace).TrimEnd('.')); temp = temp.Replace("/* NAME */", GetNameofClass(c.type, true)); writer.Write(temp); writer.Close(); @@ -61,28 +218,49 @@ namespace TeleSharp.Generator list.First().type = "himself"; } } + Console.WriteLine("Implementing types..."); foreach (var c in schema.constructors) { - string path = (GetNameSpace(c.predicate).Replace("TeleSharp.TL", "TL\\").Replace(".", "") + "\\" + GetNameofClass(c.predicate, false) + ".cs").Replace("\\\\", "\\"); + string path = Path.Combine(outputPath, GetNameSpace(c.predicate, TargetNamespace).Replace(TargetNamespace, @"TL\").Replace(".", ""), GetNameofClass(c.predicate, false) + ".cs").Replace(@"\\", @"\"); FileStream classFile = MakeFile(path); using (StreamWriter writer = new StreamWriter(classFile)) { #region About Class - string nspace = (GetNameSpace(c.predicate).Replace("TeleSharp.TL", "TL\\").Replace(".", "")).Replace("\\\\", "\\").Replace("\\", "."); - if (nspace.EndsWith(".")) - nspace = nspace.Remove(nspace.Length - 1, 1); - string temp = NormalStyle.Replace("/* NAMESPACE */", "TeleSharp." + nspace); + string temp = NormalStyle.Replace("/* NAMESPACE */", GetNameSpace(c.predicate, TargetNamespace).TrimEnd('.')); temp = (c.type == "himself") ? temp.Replace("/* PARENT */", "TLObject") : temp.Replace("/* PARENT */", GetNameofClass(c.type, true)); temp = temp.Replace("/*Constructor*/", c.id.ToString()); temp = temp.Replace("/* NAME */", GetNameofClass(c.predicate, false)); #endregion #region Fields - string fields = ""; - foreach (var tmp in c.Params) + /* + Note: Fields were mostly moved to abstract classes to provide maximum polymorphism usability. + */ + //string fields = ""; + string parent_name = GetNameofClass(c.type, true); + if (c.type != "himself") { - fields += $" public {CheckForFlagBase(tmp.type, GetTypeName(tmp.type))} {CheckForKeyword(tmp.name)} " + "{get;set;}" + Environment.NewLine; + foreach (var tmp in c.Params) + { + Property field = new Property + { + type = CheckForFlagBase(tmp.type, GetTypeName(tmp.type)), + name = CheckForKeyword(tmp.name) + }; + if (!abstractParams.ContainsKey(c.type)) + abstractParams.Add(c.type, new List()); + else if (!abstractParams[c.type].Contains(field)) + abstractParams[c.type].Add(field); + } + } + else + { + string fields = ""; + foreach (var tmp in c.Params) + { + fields += $" public {CheckForFlagBase(tmp.type, GetTypeName(tmp.type))} {CheckForKeyword(tmp.name)} " + "{ get; set; }" + Environment.NewLine; + } + temp = temp.Replace("/* PARAMS */", fields); } - temp = temp.Replace("/* PARAMS */", fields); #endregion #region ComputeFlagFunc if (!c.Params.Any(x => x.name == "flags")) temp = temp.Replace("/* COMPUTE */", ""); @@ -127,17 +305,15 @@ namespace TeleSharp.Generator classFile.Close(); } } + Console.WriteLine("Implementing methods..."); foreach (var c in schema.methods) { - string path = (GetNameSpace(c.method).Replace("TeleSharp.TL", "TL\\").Replace(".", "") + "\\" + GetNameofClass(c.method, false, true) + ".cs").Replace("\\\\", "\\"); + string path = Path.Combine(outputPath, GetNameSpace(c.method, TargetNamespace).Replace(TargetNamespace, @"TL\").Replace(".", ""), GetNameofClass(c.method, false, true) + ".cs").Replace(@"\\", @"\"); FileStream classFile = MakeFile(path); using (StreamWriter writer = new StreamWriter(classFile)) { #region About Class - string nspace = (GetNameSpace(c.method).Replace("TeleSharp.TL", "TL\\").Replace(".", "")).Replace("\\\\", "\\").Replace("\\", "."); - if (nspace.EndsWith(".")) - nspace = nspace.Remove(nspace.Length - 1, 1); - string temp = MethodStyle.Replace("/* NAMESPACE */", "TeleSharp." + nspace); + string temp = MethodStyle.Replace("/* NAMESPACE */", GetNameSpace(c.method, TargetNamespace).TrimEnd('.')); temp = temp.Replace("/* PARENT */", "TLMethod"); temp = temp.Replace("/*Constructor*/", c.id.ToString()); temp = temp.Replace("/* NAME */", GetNameofClass(c.method, false, true)); @@ -200,11 +376,36 @@ namespace TeleSharp.Generator classFile.Close(); } } + Console.WriteLine("Adding fields to abstract classes..."); + // add fields to abstract classes + foreach (KeyValuePair> absClass in abstractParams) + { + if(absClass.Key == "himself") + throw new InvalidOperationException("ARGH! It was a class without a parent, why it came into the list? :|"); + string path = Path.Combine(outputPath, GetNameSpace(absClass.Key, TargetNamespace).Replace(TargetNamespace, @"TL\").Replace(".", ""), GetNameofClass(absClass.Key, true) + ".cs").Replace(@"\\", @"\"); + string tmp = File.ReadAllText(path); + tmp = tmp.Replace("/* PARAMS */", ConvertPropertyList(absClass.Value)); + File.WriteAllText(path, tmp); + } + + #endregion + Console.WriteLine("Done."); } + + public static string ConvertPropertyList(List list) + { + string output = ""; + foreach (var property in list) + { + output += $" public {property.type} {property.name} {{ get; set; }}" + Environment.NewLine; + } + return output; + } + public static string FormatName(string input) { if (String.IsNullOrEmpty(input)) - throw new ArgumentException("ARGH!"); + throw new ArgumentException("ARGH! Class Name was empty."); if (input.IndexOf('.') != -1) { input = input.Replace(".", " "); @@ -255,12 +456,12 @@ namespace TeleSharp.Generator { return type.Split('?')[1] == "true"; } - public static string GetNameSpace(string type) + public static string GetNameSpace(string type, string targetns) { if (type.IndexOf('.') != -1) - return "TeleSharp.TL" + FormatName(type.Split('.')[0]); + return targetns + FormatName(type.Split('.')[0]); else - return "TeleSharp.TL"; + return targetns; } public static string CheckForFlagBase(string type, string result) { @@ -298,15 +499,16 @@ namespace TeleSharp.Generator return "TLObject"; case "x": return "TLObject"; + case "vector t": + return "List"; } - if (type.StartsWith("Vector")) + if (type.StartsWith("Vector<")) return "TLVector<" + GetTypeName(type.Replace("Vector<", "").Replace(">", "")) + ">"; if (type.ToLower().Contains("inputcontact")) return "TLInputPhoneContact"; - - + if (type.IndexOf('.') != -1 && type.IndexOf('?') == -1) { @@ -429,4 +631,18 @@ namespace TeleSharp.Generator } } + struct Property + { + public string type; + public string name; + public override bool Equals(object obj) + { + if (obj.GetType() == typeof (Property)) + { + return ((Property) obj).type == type && ((Property)obj).name == name; + } + return false; + } + } + } \ No newline at end of file diff --git a/TeleSharp.Generator/TL2JSON.cs b/TeleSharp.Generator/TL2JSON.cs new file mode 100644 index 0000000..a6f18ae --- /dev/null +++ b/TeleSharp.Generator/TL2JSON.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace TeleSharp.Generator +{ + static class TL2JSON + { + + public static string RemoveComments(string input) + { + var blockComments = @"/\*(.*?)\*/"; + var lineComments = @"//(.*?)\r?\n"; + var strings = @"""((\\[^\n]|[^""\n])*)"""; + var verbatimStrings = @"@(""[^""]*"")+"; + return Regex.Replace(input, + blockComments + "|" + lineComments + "|" + strings + "|" + verbatimStrings, + me => + { + if (me.Value.StartsWith("/*") || me.Value.StartsWith("//")) + return me.Value.StartsWith("//") ? Environment.NewLine : ""; + // Keep the literal strings + return me.Value; + }, + RegexOptions.Singleline); + } + public static string RemoveEmptyLines(string input) => Regex.Replace(input, @"^\s+$[\r\n]*", "", RegexOptions.Multiline); + + public static string ParseTypeLine(string line) + { + List convertedParamsList = new List(); + var regex = @"([a-zA-Z0-9.]+)#([0-9a-fA-F]+)([a-zA-Z0-9_:<>. !]+)= ([a-zA-Z0-9 .!]+);"; // 0 = type name, 1 = constructor code, 2 = params, 3 = base type name + var match = Regex.Match(line, regex); + if (!match.Success) + throw new FormatException($"Cannot parse line: \"{line}\""); + // now parse the params to json + + string[] paramslist = Regex.Replace(match.Groups[3].Value, "[ ]{2,}", " ", RegexOptions.None).Split(' '); + + foreach (var param in paramslist) + { + string[] param_split = param.Split(':'); // 0=name,1=type + if(param_split.Length == 2) + convertedParamsList.Add($"{{\"name\": \"{param_split[0]}\", \"type\": \"{param_split[1]}\"}}"); + } + string convertedParams = $"[{string.Join(",", convertedParamsList)}]"; // [ {"name":"NAME","type":"TYPE"}, {"name":"NAME","type":"TYPE"} ] + + // now, make the final object + return $"{{" + + $"\"id\": \"{Convert.ToInt32("0x"+match.Groups[2].Value, 16)}\"" + "," + + $"\"predicate\": \"{match.Groups[1].Value}\"" + "," + + $"\"params\": {convertedParams}" + "," + + $"\"type\": \"{match.Groups[4].Value}\"" + + $"}}"; + + } + + public static string ParseMethodLine(string line) + { + List convertedParamsList = new List(); + var regex = @"([a-zA-Z0-9.]+)#([0-9a-fA-F]+)([a-zA-Z0-9_:<>. !]+)= ([a-zA-Z0-9 .!]+);"; // 0 = method name, 1 = method code, 2 = params, 3 = base type name + var match = Regex.Match(line, regex); + if (!match.Success) + throw new FormatException($"Cannot parse line: \"{line}\""); + // now parse the params to json + + string[] paramslist = Regex.Replace(match.Groups[3].Value, "[ ]{2,}", " ", RegexOptions.None).Split(' '); + + foreach (var param in paramslist) + { + string[] param_split = param.Split(':'); // 0=name,1=type + if (param_split.Length == 2) + convertedParamsList.Add($"{{\"name\": \"{param_split[0]}\", \"type\": \"{param_split[1]}\"}}"); + } + string convertedParams = $"[{string.Join(",", convertedParamsList)}]"; // [ {"name":"NAME","type":"TYPE"}, {"name":"NAME","type":"TYPE"} ] + + // now, make the final object + return $"{{" + + $"\"id\": \"{Convert.ToInt32("0x" + match.Groups[2].Value, 16)}\"" + "," + + $"\"method\": \"{match.Groups[1].Value}\"" + "," + + $"\"params\": {convertedParams}" + "," + + $"\"type\": \"{match.Groups[4].Value}\"" + + $"}}"; + } + + public static string ParseToJson(string input) + { + List convertedTypesList = new List(); + List convertedMethodsList = new List(); + string[] lines = RemoveEmptyLines(RemoveComments(input)).Replace("\r\n","\n").Split('\n'); + + int functions_splitter = Array.IndexOf(lines, "---functions---"); + string[] typeLines = lines.Take(functions_splitter - 1).ToArray(); + string[] methodLines = lines.Skip(functions_splitter + 1).ToArray(); + foreach (var line in typeLines) + { + convertedTypesList.Add(ParseTypeLine(line)); + } + foreach (var line in typeLines) + { + convertedMethodsList.Add(ParseMethodLine(line)); + } + + return $"{{\"constructors\":[{string.Join(",", convertedTypesList)}], \"methods\": [{string.Join(",", convertedMethodsList)}]}}"; // { "constructors": [ OBJECT, OBJECT ], "methods": [ OBJECT, OBJECT ] } + } + } +} \ No newline at end of file diff --git a/TeleSharp.Generator/TeleSharp.Generator.csproj b/TeleSharp.Generator/TeleSharp.Generator.csproj index 94f3e9b..f5b9ac0 100644 --- a/TeleSharp.Generator/TeleSharp.Generator.csproj +++ b/TeleSharp.Generator/TeleSharp.Generator.csproj @@ -12,6 +12,21 @@ v4.5 512 true + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + false + true AnyCPU @@ -47,9 +62,11 @@ + + @@ -69,6 +86,18 @@ Always + + + False + Microsoft .NET Framework 4.5 %28x86 and x64%29 + true + + + False + .NET Framework 3.5 SP1 + false + + - \ No newline at end of file + From 76d2e9ad40184c3b2b1923529621be96fcbdc0ba Mon Sep 17 00:00:00 2001 From: Mohammad Hadi Hosseinpour Date: Tue, 4 Apr 2017 17:30:44 +0430 Subject: [PATCH 5/8] Update TL2JSON.cs --- TeleSharp.Generator/TL2JSON.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/TeleSharp.Generator/TL2JSON.cs b/TeleSharp.Generator/TL2JSON.cs index a6f18ae..ac11b6a 100644 --- a/TeleSharp.Generator/TL2JSON.cs +++ b/TeleSharp.Generator/TL2JSON.cs @@ -27,7 +27,8 @@ namespace TeleSharp.Generator }, RegexOptions.Singleline); } - public static string RemoveEmptyLines(string input) => Regex.Replace(input, @"^\s+$[\r\n]*", "", RegexOptions.Multiline); + public static string RemoveEmptyLines(string input) => + Regex.Replace(input, @"^\s+$[\r\n]*", "", RegexOptions.Multiline); public static string ParseTypeLine(string line) { @@ -107,4 +108,4 @@ namespace TeleSharp.Generator return $"{{\"constructors\":[{string.Join(",", convertedTypesList)}], \"methods\": [{string.Join(",", convertedMethodsList)}]}}"; // { "constructors": [ OBJECT, OBJECT ], "methods": [ OBJECT, OBJECT ] } } } -} \ No newline at end of file +} From 8055ab3162a402019be4af44c241d7663bf36f94 Mon Sep 17 00:00:00 2001 From: Mohammad Hadi Hosseinpour Date: Fri, 7 Apr 2017 05:52:55 +0430 Subject: [PATCH 6/8] Improved TL2JSON and formatted code a bit --- TeleSharp.Generator/Models.cs | 89 ++++++++++++-- TeleSharp.Generator/Program.cs | 207 +++++++++++++++++---------------- TeleSharp.Generator/TL2JSON.cs | 103 ++++++++-------- 3 files changed, 237 insertions(+), 162 deletions(-) diff --git a/TeleSharp.Generator/Models.cs b/TeleSharp.Generator/Models.cs index 8e43820..b379a1d 100644 --- a/TeleSharp.Generator/Models.cs +++ b/TeleSharp.Generator/Models.cs @@ -8,29 +8,98 @@ namespace TeleSharp.Generator { class Method { - public int id { get; set; } - public string method { get; set; } + [Newtonsoft.Json.JsonProperty("id")] + public int Id { get; set; } + + [Newtonsoft.Json.JsonProperty("method")] + public string MethodName { get; set; } + [Newtonsoft.Json.JsonProperty("params")] public List Params { get; set; } - public string type { get; set; } + [Newtonsoft.Json.JsonProperty("type")] + public string ResponseType { get; set; } + + public override bool Equals(Object obj) + { + return obj is Method && this == (Method)obj; + } + public override int GetHashCode() + { + return Id.GetHashCode() ^ MethodName.GetHashCode() ^ Params.GetHashCode() ^ ResponseType.GetHashCode(); + } + public static bool operator ==(Method x, Method y) + { + return Enumerable.SequenceEqual(x.Params, y.Params) && x.MethodName == y.MethodName && x.ResponseType == y.ResponseType && x.Id == y.Id; + } + public static bool operator !=(Method x, Method y) + { + return !(x == y); + } } class Param { - public string name { get; set; } - public string type { get; set; } + [Newtonsoft.Json.JsonProperty("name")] + public string Name { get; set; } + + [Newtonsoft.Json.JsonProperty("type")] + public string Type { get; set; } } class Constructor { - public int id { get; set; } - public string predicate { get; set; } + [Newtonsoft.Json.JsonProperty("id")] + public int Id { get; set; } + + [Newtonsoft.Json.JsonProperty("predicate")] + public string ConstructorName { get; set; } + [Newtonsoft.Json.JsonProperty("params")] public List Params { get; set; } - public string type { get; set; } + + [Newtonsoft.Json.JsonProperty("type")] + public string BaseType { get; set; } + + public override bool Equals(Object obj) + { + return obj is Constructor && this == (Constructor)obj; + } + public override int GetHashCode() + { + return Id.GetHashCode() ^ ConstructorName.GetHashCode() ^ Params.GetHashCode() ^ BaseType.GetHashCode(); + } + public static bool operator ==(Constructor x, Constructor y) + { + return Enumerable.SequenceEqual(x.Params, y.Params) && x.ConstructorName == y.ConstructorName && x.BaseType == y.BaseType && x.Id == y.Id; + } + public static bool operator !=(Constructor x, Constructor y) + { + return !(x == y); + } } class Schema { - public List constructors { get; set; } - public List methods { get; set; } + [Newtonsoft.Json.JsonProperty("constructors")] + public List Constructors { get; set; } + + [Newtonsoft.Json.JsonProperty("methods")] + public List Methods { get; set; } + + public override bool Equals(Object obj) + { + return obj is Schema && this == (Schema)obj; + } + public override int GetHashCode() + { + return Constructors.GetHashCode() ^ Methods.GetHashCode(); + } + public static bool operator ==(Schema x, Schema y) + { + return Enumerable.SequenceEqual(x.Methods.OrderBy(t=>t.MethodName),y.Methods.OrderBy(t => t.MethodName)) && + Enumerable.SequenceEqual(x.Constructors.OrderBy(t => t.ConstructorName), y.Constructors.OrderBy(t => t.ConstructorName)); + } + public static bool operator !=(Schema x, Schema y) + { + return !(x == y); + } } } diff --git a/TeleSharp.Generator/Program.cs b/TeleSharp.Generator/Program.cs index aa0ca72..9f0444e 100644 --- a/TeleSharp.Generator/Program.cs +++ b/TeleSharp.Generator/Program.cs @@ -147,8 +147,7 @@ namespace TeleSharp.Generator Console.WriteLine("Try `TeleSharp.Generator --help' for more information."); return; } - - + inputPath = extra[0]; if (!forceFormat) { @@ -189,24 +188,25 @@ namespace TeleSharp.Generator #region Translate to C# Schema schema = JsonConvert.DeserializeObject(Json); - foreach (var c in schema.constructors) + foreach (var c in schema.Constructors) { - interfacesList.Add(c.type); - classesList.Add(c.predicate); + interfacesList.Add(c.BaseType); + classesList.Add(c.ConstructorName); } Console.WriteLine("Implementing abstract classes..."); - var abstractParams = new Dictionary>(); - foreach (var c in schema.constructors) + var abstractParams = new Dictionary>(); + #region Abstract classes + foreach (var c in schema.Constructors) { - var list = schema.constructors.Where(x => x.type == c.type); // check if there is a dependence on this type (it is an abstract class) + var list = schema.Constructors.Where(x => x.BaseType == c.BaseType); // check if there is a dependence on base type of this class (base type is an abstract class) if (list.Count() > 1) { - string path = Path.Combine(outputPath, GetNameSpace(c.type, TargetNamespace).Replace(TargetNamespace, @"TL\").Replace(".", ""), GetNameofClass(c.type, true) + ".cs").Replace(@"\\", @"\"); + string path = Path.Combine(outputPath, GetNameSpace(c.BaseType, TargetNamespace).Replace(TargetNamespace, @"TL\").Replace(".", ""), GetNameofClass(c.BaseType, true) + ".cs").Replace(@"\\", @"\"); FileStream classFile = MakeFile(path); using (StreamWriter writer = new StreamWriter(classFile)) { - string temp = AbsStyle.Replace("/* NAMESPACE */", GetNameSpace(c.type, TargetNamespace).TrimEnd('.')); - temp = temp.Replace("/* NAME */", GetNameofClass(c.type, true)); + string temp = AbsStyle.Replace("/* NAMESPACE */", GetNameSpace(c.BaseType, TargetNamespace).TrimEnd('.')); + temp = temp.Replace("/* NAME */", GetNameofClass(c.BaseType, true)); writer.Write(temp); writer.Close(); classFile.Close(); @@ -214,70 +214,70 @@ namespace TeleSharp.Generator } else { - interfacesList.Remove(list.First().type); - list.First().type = "himself"; + interfacesList.Remove(list.First().BaseType); + list.First().BaseType = "himself"; } } + #endregion + Console.WriteLine("Implementing types..."); - foreach (var c in schema.constructors) + #region Constructors + foreach (var c in schema.Constructors) { - string path = Path.Combine(outputPath, GetNameSpace(c.predicate, TargetNamespace).Replace(TargetNamespace, @"TL\").Replace(".", ""), GetNameofClass(c.predicate, false) + ".cs").Replace(@"\\", @"\"); + string path = Path.Combine(outputPath, GetNameSpace(c.ConstructorName, TargetNamespace).Replace(TargetNamespace, @"TL\").Replace(".", ""), GetNameofClass(c.ConstructorName, false) + ".cs").Replace(@"\\", @"\"); FileStream classFile = MakeFile(path); using (StreamWriter writer = new StreamWriter(classFile)) { #region About Class - string temp = NormalStyle.Replace("/* NAMESPACE */", GetNameSpace(c.predicate, TargetNamespace).TrimEnd('.')); - temp = (c.type == "himself") ? + string temp = NormalStyle.Replace("/* NAMESPACE */", GetNameSpace(c.ConstructorName, TargetNamespace).TrimEnd('.')); + temp = (c.BaseType == "himself") ? temp.Replace("/* PARENT */", "TLObject") : - temp.Replace("/* PARENT */", GetNameofClass(c.type, true)); - temp = temp.Replace("/*Constructor*/", c.id.ToString()); - temp = temp.Replace("/* NAME */", GetNameofClass(c.predicate, false)); + temp.Replace("/* PARENT */", GetNameofClass(c.BaseType, true)); + temp = temp.Replace("/*Constructor*/", c.Id.ToString()); + temp = temp.Replace("/* NAME */", GetNameofClass(c.ConstructorName, false)); #endregion #region Fields /* Note: Fields were mostly moved to abstract classes to provide maximum polymorphism usability. */ //string fields = ""; - string parent_name = GetNameofClass(c.type, true); - if (c.type != "himself") + string parent_name = GetNameofClass(c.BaseType, true); + + if (c.BaseType != "himself") { foreach (var tmp in c.Params) { - Property field = new Property - { - Type = CheckForFlagBase(tmp.type, GetTypeName(tmp.type)), - Name = CheckForKeyword(tmp.name) - }; - if (!abstractParams.ContainsKey(c.type)) - abstractParams.Add(c.type, new List()); - else if (!abstractParams[c.type].Contains(field)) - abstractParams[c.type].Add(field); + Param field = new Param {Name = tmp.Name, Type = tmp.Type}; + if (abstractParams.All(item => item.Key != c.BaseType)) + abstractParams.Add(c.BaseType, new List()); + else if (!abstractParams[c.BaseType].Any(f => f.Name == field.Name && f.Type == field.Type)) + abstractParams[c.BaseType].Add(field); } } else { string fields = ""; - foreach (var tmp in c.Params) + foreach (var param in c.Params) { - fields += $" public {CheckForFlagBase(tmp.type, GetTypeName(tmp.type))} {CheckForKeyword(tmp.name)} " + "{ get; set; }" + Environment.NewLine; + fields += $" public {CheckForFlagBase(param.Type, GetTypeName(param.Type))} {CheckForKeyword(param.Name)} " + "{ get; set; }" + Environment.NewLine; } temp = temp.Replace("/* PARAMS */", fields); } #endregion #region ComputeFlagFunc - if (!c.Params.Any(x => x.name == "flags")) temp = temp.Replace("/* COMPUTE */", ""); + if (c.Params.All(x => x.Name != "flags")) temp = temp.Replace("/* COMPUTE */", ""); else { var compute = "flags = 0;" + Environment.NewLine; - foreach (var param in c.Params.Where(x => IsFlagBase(x.type))) + foreach (var param in c.Params.Where(x => IsFlagBase(x.Type))) { - if (IsTrueFlag(param.type)) + if (IsTrueFlag(param.Type)) { - compute += $"flags = {CheckForKeyword(param.name)} ? (flags | {GetBitMask(param.type)}) : (flags & ~{GetBitMask(param.type)});" + Environment.NewLine; + compute += $"flags = {CheckForKeyword(param.Name)} ? (flags | {GetBitMask(param.Type)}) : (flags & ~{GetBitMask(param.Type)});" + Environment.NewLine; } else { - compute += $"flags = {CheckForKeyword(param.name)} != null ? (flags | {GetBitMask(param.type)}) : (flags & ~{GetBitMask(param.type)});" + Environment.NewLine; + compute += $"flags = {CheckForKeyword(param.Name)} != null ? (flags | {GetBitMask(param.Type)}) : (flags & ~{GetBitMask(param.Type)});" + Environment.NewLine; } } temp = temp.Replace("/* COMPUTE */", compute); @@ -286,9 +286,9 @@ namespace TeleSharp.Generator #region SerializeFunc var serialize = ""; - if (c.Params.Any(x => x.name == "flags")) + if (c.Params.Any(x => x.Name == "flags")) serialize += "ComputeFlags();" + Environment.NewLine + "bw.Write(flags);" + Environment.NewLine; - foreach (var p in c.Params.Where(x => x.name != "flags")) + foreach (var p in c.Params.Where(x => x.Name != "flags")) { serialize += WriteWriteCode(p) + Environment.NewLine; } @@ -308,42 +308,45 @@ namespace TeleSharp.Generator classFile.Close(); } } + #endregion + Console.WriteLine("Implementing methods..."); - foreach (var c in schema.methods) + #region Methods + foreach (var c in schema.Methods) { - string path = Path.Combine(outputPath, GetNameSpace(c.method, TargetNamespace).Replace(TargetNamespace, @"TL\").Replace(".", ""), GetNameofClass(c.method, false, true) + ".cs").Replace(@"\\", @"\"); + string path = Path.Combine(outputPath, GetNameSpace(c.MethodName, TargetNamespace).Replace(TargetNamespace, @"TL\").Replace(".", ""), GetNameofClass(c.MethodName, false, true) + ".cs").Replace(@"\\", @"\"); FileStream classFile = MakeFile(path); using (StreamWriter writer = new StreamWriter(classFile)) { #region About Class - string temp = MethodStyle.Replace("/* NAMESPACE */", GetNameSpace(c.method, TargetNamespace).TrimEnd('.')); + string temp = MethodStyle.Replace("/* NAMESPACE */", GetNameSpace(c.MethodName, TargetNamespace).TrimEnd('.')); temp = temp.Replace("/* PARENT */", "TLMethod"); - temp = temp.Replace("/*Constructor*/", c.id.ToString()); - temp = temp.Replace("/* NAME */", GetNameofClass(c.method, false, true)); + temp = temp.Replace("/*Constructor*/", c.Id.ToString()); + temp = temp.Replace("/* NAME */", GetNameofClass(c.MethodName, false, true)); #endregion #region Fields string fields = ""; foreach (var tmp in c.Params) { - fields += $" public {CheckForFlagBase(tmp.type, GetTypeName(tmp.type))} {CheckForKeyword(tmp.name)} " + "{get;set;}" + Environment.NewLine; + fields += $" public {CheckForFlagBase(tmp.Type, GetTypeName(tmp.Type))} {CheckForKeyword(tmp.Name)} " + "{ get; set; }" + Environment.NewLine; } - fields += $" public {CheckForFlagBase(c.type, GetTypeName(c.type))} Response" + "{ get; set;}" + Environment.NewLine; + fields += $" public {CheckForFlagBase(c.ResponseType, GetTypeName(c.ResponseType))} Response" + "{ get; set; }" + Environment.NewLine; temp = temp.Replace("/* PARAMS */", fields); #endregion #region ComputeFlagFunc - if (!c.Params.Any(x => x.name == "flags")) temp = temp.Replace("/* COMPUTE */", ""); + if (!c.Params.Any(x => x.Name == "flags")) temp = temp.Replace("/* COMPUTE */", ""); else { var compute = "flags = 0;" + Environment.NewLine; - foreach (var param in c.Params.Where(x => IsFlagBase(x.type))) + foreach (var param in c.Params.Where(x => IsFlagBase(x.Type))) { - if (IsTrueFlag(param.type)) + if (IsTrueFlag(param.Type)) { - compute += $"flags = {CheckForKeyword(param.name)} ? (flags | {GetBitMask(param.type)}) : (flags & ~{GetBitMask(param.type)});" + Environment.NewLine; + compute += $"flags = {CheckForKeyword(param.Name)} ? (flags | {GetBitMask(param.Type)}) : (flags & ~{GetBitMask(param.Type)});" + Environment.NewLine; } else { - compute += $"flags = {CheckForKeyword(param.name)} != null ? (flags | {GetBitMask(param.type)}) : (flags & ~{GetBitMask(param.type)});" + Environment.NewLine; + compute += $"flags = {CheckForKeyword(param.Name)} != null ? (flags | {GetBitMask(param.Type)}) : (flags & ~{GetBitMask(param.Type)});" + Environment.NewLine; } } temp = temp.Replace("/* COMPUTE */", compute); @@ -352,8 +355,8 @@ namespace TeleSharp.Generator #region SerializeFunc var serialize = ""; - if (c.Params.Any(x => x.name == "flags")) serialize += "ComputeFlags();" + Environment.NewLine + "bw.Write(flags);" + Environment.NewLine; - foreach (var p in c.Params.Where(x => x.name != "flags")) + if (c.Params.Any(x => x.Name == "flags")) serialize += "ComputeFlags();" + Environment.NewLine + "bw.Write(flags);" + Environment.NewLine; + foreach (var p in c.Params.Where(x => x.Name != "flags")) { serialize += WriteWriteCode(p) + Environment.NewLine; } @@ -370,7 +373,7 @@ namespace TeleSharp.Generator #endregion #region DeSerializeRespFunc var deserializeResp = ""; - Param p2 = new Param() { name = "Response", type = c.type }; + Param p2 = new Param() { Name = "Response", Type = c.ResponseType }; deserializeResp += WriteReadCode(p2) + Environment.NewLine; temp = temp.Replace("/* DESERIALIZEResp */", deserializeResp); #endregion @@ -379,28 +382,28 @@ namespace TeleSharp.Generator classFile.Close(); } } + #endregion + Console.WriteLine("Adding fields to abstract classes..."); // add fields to abstract classes - foreach (KeyValuePair> absClass in abstractParams) + foreach (KeyValuePair> absClass in abstractParams) { - if(absClass.Key == "himself") - throw new InvalidOperationException("ARGH! It was a class without a parent, why it came into the list? :|"); string path = Path.Combine(outputPath, GetNameSpace(absClass.Key, TargetNamespace).Replace(TargetNamespace, @"TL\").Replace(".", ""), GetNameofClass(absClass.Key, true) + ".cs").Replace(@"\\", @"\"); string tmp = File.ReadAllText(path); tmp = tmp.Replace("/* PARAMS */", ConvertPropertyList(absClass.Value)); File.WriteAllText(path, tmp); } - #endregion + Console.WriteLine("Done."); } - public static string ConvertPropertyList(List list) + public static string ConvertPropertyList(List list) { string output = ""; - foreach (var property in list) + foreach (var param in list) { - output += $" public {property.Type} {property.Name} {{ get; set; }}" + Environment.NewLine; + output += $" public {CheckForFlagBase(param.Type, GetTypeName(param.Type))} {CheckForKeyword(param.Name)} " + "{ get; set; }" + Environment.NewLine; } return output; } @@ -470,13 +473,16 @@ namespace TeleSharp.Generator { if (type.IndexOf('?') == -1) return result; - else - { - string innerType = type.Split('?')[1]; - if (innerType == "true") return result; - else if ((new string[] { "bool", "int", "uint", "long", "double" }).Contains(result)) return result + "?"; - else return result; - } + + string innerType = type.Split('?')[1]; + + if (innerType == "true") + return result; + + if ((new string[] { "bool", "int", "uint", "long", "BigMath.Int128", "BigMath.Int256", "double" }).Contains(result)) + return result + "?"; + + return result; } public static string GetTypeName(string type) { @@ -485,6 +491,10 @@ namespace TeleSharp.Generator case "#": case "int": return "int"; + case "int128": + return "BigMath.Int128"; + case "int256": + return "BigMath.Int256"; case "uint": return "uint"; case "long": @@ -514,7 +524,6 @@ namespace TeleSharp.Generator if (type.IndexOf('.') != -1 && type.IndexOf('?') == -1) { - if (interfacesList.Any(x => x.ToLower() == (type).ToLower())) return FormatName(type.Split('.')[0]) + "." + "TLAbs" + type.Split('.')[1]; else if (classesList.Any(x => x.ToLower() == (type).ToLower())) @@ -549,34 +558,34 @@ namespace TeleSharp.Generator } public static string WriteWriteCode(Param p, bool flag = false) { - switch (p.type.ToLower()) + switch (p.Type.ToLower()) { case "#": case "int": - return flag ? $"bw.Write({CheckForKeyword(p.name)}.Value);" : $"bw.Write({CheckForKeyword(p.name)});"; + return flag ? $"bw.Write({CheckForKeyword(p.Name)}.Value);" : $"bw.Write({CheckForKeyword(p.Name)});"; case "long": - return flag ? $"bw.Write({CheckForKeyword(p.name)}.Value);" : $"bw.Write({CheckForKeyword(p.name)});"; + return flag ? $"bw.Write({CheckForKeyword(p.Name)}.Value);" : $"bw.Write({CheckForKeyword(p.Name)});"; case "string": - return $"StringUtil.Serialize({CheckForKeyword(p.name)},bw);"; + return $"StringUtil.Serialize({CheckForKeyword(p.Name)},bw);"; case "bool": - return flag ? $"BoolUtil.Serialize({CheckForKeyword(p.name)}.Value,bw);" : $"BoolUtil.Serialize({CheckForKeyword(p.name)},bw);"; + return flag ? $"BoolUtil.Serialize({CheckForKeyword(p.Name)}.Value,bw);" : $"BoolUtil.Serialize({CheckForKeyword(p.Name)},bw);"; case "true": - return $"BoolUtil.Serialize({CheckForKeyword(p.name)},bw);"; + return $"BoolUtil.Serialize({CheckForKeyword(p.Name)},bw);"; case "bytes": - return $"BytesUtil.Serialize({CheckForKeyword(p.name)},bw);"; + return $"BytesUtil.Serialize({CheckForKeyword(p.Name)},bw);"; case "double": - return flag ? $"bw.Write({CheckForKeyword(p.name)}.Value);" : $"bw.Write({CheckForKeyword(p.name)});"; + return flag ? $"bw.Write({CheckForKeyword(p.Name)}.Value);" : $"bw.Write({CheckForKeyword(p.Name)});"; default: - if (!IsFlagBase(p.type)) - return $"ObjectUtils.SerializeObject({CheckForKeyword(p.name)},bw);"; + if (!IsFlagBase(p.Type)) + return $"ObjectUtils.SerializeObject({CheckForKeyword(p.Name)},bw);"; else { - if (IsTrueFlag(p.type)) + if (IsTrueFlag(p.Type)) return $""; else { - Param p2 = new Param() { name = p.name, type = p.type.Split('?')[1] }; - return $"if ((flags & {GetBitMask(p.type).ToString()}) != 0)" + Environment.NewLine + + Param p2 = new Param() { Name = p.Name, Type = p.Type.Split('?')[1] }; + return $"if ((flags & {GetBitMask(p.Type).ToString()}) != 0)" + Environment.NewLine + WriteWriteCode(p2, true); } } @@ -584,42 +593,42 @@ namespace TeleSharp.Generator } public static string WriteReadCode(Param p) { - switch (p.type.ToLower()) + switch (p.Type.ToLower()) { case "#": case "int": - return $"{CheckForKeyword(p.name)} = br.ReadInt32();"; + return $"{CheckForKeyword(p.Name)} = br.ReadInt32();"; case "long": - return $"{CheckForKeyword(p.name)} = br.ReadInt64();"; + return $"{CheckForKeyword(p.Name)} = br.ReadInt64();"; case "string": - return $"{CheckForKeyword(p.name)} = StringUtil.Deserialize(br);"; + return $"{CheckForKeyword(p.Name)} = StringUtil.Deserialize(br);"; case "bool": case "true": - return $"{CheckForKeyword(p.name)} = BoolUtil.Deserialize(br);"; + return $"{CheckForKeyword(p.Name)} = BoolUtil.Deserialize(br);"; case "bytes": - return $"{CheckForKeyword(p.name)} = BytesUtil.Deserialize(br);"; + return $"{CheckForKeyword(p.Name)} = BytesUtil.Deserialize(br);"; case "double": - return $"{CheckForKeyword(p.name)} = br.ReadDouble();"; + return $"{CheckForKeyword(p.Name)} = br.ReadDouble();"; default: - if (!IsFlagBase(p.type)) + if (!IsFlagBase(p.Type)) { - if (p.type.ToLower().Contains("vector")) + if (p.Type.ToLower().Contains("vector")) { - return $"{CheckForKeyword(p.name)} = ({GetTypeName(p.type)})ObjectUtils.DeserializeVector<{GetTypeName(p.type).Replace("TLVector<", "").Replace(">", "")}>(br);"; + return $"{CheckForKeyword(p.Name)} = ({GetTypeName(p.Type)})ObjectUtils.DeserializeVector<{GetTypeName(p.Type).Replace("TLVector<", "").Replace(">", "")}>(br);"; } - else return $"{CheckForKeyword(p.name)} = ({GetTypeName(p.type)})ObjectUtils.DeserializeObject(br);"; + else return $"{CheckForKeyword(p.Name)} = ({GetTypeName(p.Type)})ObjectUtils.DeserializeObject(br);"; } else { - if (IsTrueFlag(p.type)) - return $"{CheckForKeyword(p.name)} = (flags & {GetBitMask(p.type).ToString()}) != 0;"; + if (IsTrueFlag(p.Type)) + return $"{CheckForKeyword(p.Name)} = (flags & {GetBitMask(p.Type).ToString()}) != 0;"; else { - Param p2 = new Param() { name = p.name, type = p.type.Split('?')[1] }; - return $"if ((flags & {GetBitMask(p.type).ToString()}) != 0)" + Environment.NewLine + + Param p2 = new Param() { Name = p.Name, Type = p.Type.Split('?')[1] }; + return $"if ((flags & {GetBitMask(p.Type).ToString()}) != 0)" + Environment.NewLine + WriteReadCode(p2) + Environment.NewLine + "else" + Environment.NewLine + - $"{CheckForKeyword(p.name)} = null;" + Environment.NewLine; + $"{CheckForKeyword(p.Name)} = null;" + Environment.NewLine; } } } diff --git a/TeleSharp.Generator/TL2JSON.cs b/TeleSharp.Generator/TL2JSON.cs index ac11b6a..96f2fc8 100644 --- a/TeleSharp.Generator/TL2JSON.cs +++ b/TeleSharp.Generator/TL2JSON.cs @@ -9,7 +9,6 @@ namespace TeleSharp.Generator { static class TL2JSON { - public static string RemoveComments(string input) { var blockComments = @"/\*(.*?)\*/"; @@ -28,84 +27,82 @@ namespace TeleSharp.Generator RegexOptions.Singleline); } public static string RemoveEmptyLines(string input) => - Regex.Replace(input, @"^\s+$[\r\n]*", "", RegexOptions.Multiline); + input.Replace("\r\n\r\n", "\r\n").Replace("\n\n","\n"); - public static string ParseTypeLine(string line) + public static string ParseLine(string line, BlockType blockType) { List convertedParamsList = new List(); - var regex = @"([a-zA-Z0-9.]+)#([0-9a-fA-F]+)([a-zA-Z0-9_:<>. !]+)= ([a-zA-Z0-9 .!]+);"; // 0 = type name, 1 = constructor code, 2 = params, 3 = base type name + var regex = @"([a-zA-Z0-9._]+)#([0-9a-fA-F]+) ((([{])([A-z0-9_.:\(\)]+)([}]))?)([a-zA-Z0-9_:<>.#? !\(\)]+)?(([ #\[]+)([A-z0-9._ ]+)([\]]))?([ ]?)=([ ]?)([a-zA-Z0-9 .!_<>\(\)]+)([ ]?);"; + // 1 = type name, 2 = constructor code, 3 = template parameters, 8 = params, 11 = Don't know what it is (seen in vector definition , 15 = base type + // template parameters are ignored in json schema + var match = Regex.Match(line, regex); if (!match.Success) throw new FormatException($"Cannot parse line: \"{line}\""); // now parse the params to json - - string[] paramslist = Regex.Replace(match.Groups[3].Value, "[ ]{2,}", " ", RegexOptions.None).Split(' '); - - foreach (var param in paramslist) + + string paramsline = Regex.Replace(match.Groups[8].Value, "[ ]{2,}", " ", RegexOptions.None); + MatchCollection paramslist = + Regex.Matches( + paramsline, + @"([A-z0-9._]+):(([A-z0-9._?<>]+)|(\([\(A-z0-9._<> \)]+\)))" + ); + foreach (var param in paramslist.Cast()) { - string[] param_split = param.Split(':'); // 0=name,1=type - if(param_split.Length == 2) - convertedParamsList.Add($"{{\"name\": \"{param_split[0]}\", \"type\": \"{param_split[1]}\"}}"); + convertedParamsList.Add($"{{\"name\":\"{param.Groups[1].Value.Trim()}\", \"type\":\"{param.Groups[2].Value.TrimStart('(').TrimEnd(')').Trim(' ')}\"}}"); } string convertedParams = $"[{string.Join(",", convertedParamsList)}]"; // [ {"name":"NAME","type":"TYPE"}, {"name":"NAME","type":"TYPE"} ] // now, make the final object return $"{{" + $"\"id\": \"{Convert.ToInt32("0x"+match.Groups[2].Value, 16)}\"" + "," + - $"\"predicate\": \"{match.Groups[1].Value}\"" + "," + + $"\"{(blockType == BlockType.Class ? "predicate" : "method")}\": \"{match.Groups[1].Value}\"" + "," + $"\"params\": {convertedParams}" + "," + - $"\"type\": \"{match.Groups[4].Value}\"" + + $"\"type\": \"{match.Groups[15].Value}\"" + $"}}"; } - - public static string ParseMethodLine(string line) - { - List convertedParamsList = new List(); - var regex = @"([a-zA-Z0-9.]+)#([0-9a-fA-F]+)([a-zA-Z0-9_:<>. !]+)= ([a-zA-Z0-9 .!]+);"; // 0 = method name, 1 = method code, 2 = params, 3 = base type name - var match = Regex.Match(line, regex); - if (!match.Success) - throw new FormatException($"Cannot parse line: \"{line}\""); - // now parse the params to json - - string[] paramslist = Regex.Replace(match.Groups[3].Value, "[ ]{2,}", " ", RegexOptions.None).Split(' '); - - foreach (var param in paramslist) - { - string[] param_split = param.Split(':'); // 0=name,1=type - if (param_split.Length == 2) - convertedParamsList.Add($"{{\"name\": \"{param_split[0]}\", \"type\": \"{param_split[1]}\"}}"); - } - string convertedParams = $"[{string.Join(",", convertedParamsList)}]"; // [ {"name":"NAME","type":"TYPE"}, {"name":"NAME","type":"TYPE"} ] - - // now, make the final object - return $"{{" + - $"\"id\": \"{Convert.ToInt32("0x" + match.Groups[2].Value, 16)}\"" + "," + - $"\"method\": \"{match.Groups[1].Value}\"" + "," + - $"\"params\": {convertedParams}" + "," + - $"\"type\": \"{match.Groups[4].Value}\"" + - $"}}"; - } - + public static string ParseToJson(string input) { List convertedTypesList = new List(); List convertedMethodsList = new List(); - string[] lines = RemoveEmptyLines(RemoveComments(input)).Replace("\r\n","\n").Split('\n'); - - int functions_splitter = Array.IndexOf(lines, "---functions---"); - string[] typeLines = lines.Take(functions_splitter - 1).ToArray(); - string[] methodLines = lines.Skip(functions_splitter + 1).ToArray(); - foreach (var line in typeLines) + string[] lines = RemoveEmptyLines(RemoveComments(input)).Replace("\r\n","\n").Trim('\n').Split('\n'); + + //int functions_splitter = Array.IndexOf(lines, "---functions---"); + //string[] typeLines = lines.Take(functions_splitter - 1).ToArray(); + //string[] methodLines = lines.Skip(functions_splitter + 1).ToArray(); + + BlockType blockType = BlockType.Class; + foreach (var line in lines) { - convertedTypesList.Add(ParseTypeLine(line)); - } - foreach (var line in typeLines) - { - convertedMethodsList.Add(ParseMethodLine(line)); + if (line == "") + continue; + + if (line == "---functions---") + { + blockType = BlockType.Method; + continue; + } + if (line == "---types---") + { + blockType = BlockType.Class; + continue; + } + + if (blockType == BlockType.Class) + convertedTypesList.Add(ParseLine(line, blockType)); + else + convertedMethodsList.Add(ParseLine(line, blockType)); } return $"{{\"constructors\":[{string.Join(",", convertedTypesList)}], \"methods\": [{string.Join(",", convertedMethodsList)}]}}"; // { "constructors": [ OBJECT, OBJECT ], "methods": [ OBJECT, OBJECT ] } } + + public enum BlockType + { + Class, + Method + } } } From d195584f5dcaa9d37dfa9c2df05082cc0b415f46 Mon Sep 17 00:00:00 2001 From: Mohammad Hadi Hosseinpour Date: Fri, 7 Apr 2017 05:59:09 +0430 Subject: [PATCH 7/8] Remove Unused Struct "Property" I'm so sorry, I forgot to remove the unused struct in previous commit. --- TeleSharp.Generator/Program.cs | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/TeleSharp.Generator/Program.cs b/TeleSharp.Generator/Program.cs index 9f0444e..f279e67 100644 --- a/TeleSharp.Generator/Program.cs +++ b/TeleSharp.Generator/Program.cs @@ -642,23 +642,4 @@ namespace TeleSharp.Generator return File.OpenWrite(path); } } - - struct Property - { - public Property(string type, string name){ - Type = type; - Name = name; - } - public string Type; - public string Name; - public override bool Equals(object obj) - { - if (obj.GetType() == typeof (Property)) - { - return ((Property) obj).Type == Type && ((Property)obj).Name == Name; - } - return false; - } - } - } From 4cb286aab1cc136683c2ab714ec2fd57711cfd52 Mon Sep 17 00:00:00 2001 From: Mohammad Hadi Hosseinpour Date: Fri, 7 Apr 2017 06:17:42 +0430 Subject: [PATCH 8/8] Fixed a missing dot in namespaces in generated code --- TeleSharp.Generator/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TeleSharp.Generator/Program.cs b/TeleSharp.Generator/Program.cs index 9f0444e..502ec78 100644 --- a/TeleSharp.Generator/Program.cs +++ b/TeleSharp.Generator/Program.cs @@ -465,7 +465,7 @@ namespace TeleSharp.Generator public static string GetNameSpace(string type, string targetns) { if (type.IndexOf('.') != -1) - return targetns + FormatName(type.Split('.')[0]); + return targetns + "." + FormatName(type.Split('.')[0]); else return targetns; }