Improved TL scheme transcompiler functionality

This commit is contained in:
Mohammad Hadi Hosseinpour 2017-04-04 04:38:06 +04:30
parent 1697db9d7f
commit fcfdaa6b5e
6 changed files with 1494 additions and 37 deletions

View file

@ -17,9 +17,7 @@ namespace /* NAMESPACE */
return /*Constructor*/;
}
}
/* PARAMS */
/* OVERRIDE_PARAMS */
public void ComputeFlags()
{
/* COMPUTE */

View file

@ -9,5 +9,6 @@ namespace /* NAMESPACE */
{
public abstract class /* NAME */ : TLObject
{
/* PARAMS */
}
}

File diff suppressed because it is too large Load diff

View file

@ -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<String> keywords = new List<string>(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<String> interfacesList = new List<string>();
static List<String> classesList = new List<string>();
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<string> 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<Schema>(Json);
foreach (var c in schema.constructors)
{
interfacesList.Add(c.type);
classesList.Add(c.predicate);
}
Console.WriteLine("Implementing abstract classes...");
var abstractParams = new Dictionary<string, List<Property>>();
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<Property>());
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<string, List<Property>> 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<Property> 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<T>";
}
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;
}
}
}

View file

@ -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<string> convertedParamsList = new List<string>();
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<string> convertedParamsList = new List<string>();
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<string> convertedTypesList = new List<string>();
List<string> convertedMethodsList = new List<string>();
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 ] }
}
}
}

View file

@ -12,6 +12,21 @@
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<PublishUrl>publish\</PublishUrl>
<Install>true</Install>
<InstallFrom>Disk</InstallFrom>
<UpdateEnabled>false</UpdateEnabled>
<UpdateMode>Foreground</UpdateMode>
<UpdateInterval>7</UpdateInterval>
<UpdateIntervalUnits>Days</UpdateIntervalUnits>
<UpdatePeriodically>false</UpdatePeriodically>
<UpdateRequired>false</UpdateRequired>
<MapFileExtensions>true</MapFileExtensions>
<ApplicationRevision>0</ApplicationRevision>
<ApplicationVersion>1.0.0.%2a</ApplicationVersion>
<IsWebBootstrapper>false</IsWebBootstrapper>
<UseApplicationTrust>false</UseApplicationTrust>
<BootstrapperEnabled>true</BootstrapperEnabled>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
@ -47,9 +62,11 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Options.cs" />
<Compile Include="Models.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="TL2JSON.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
@ -69,6 +86,18 @@
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<BootstrapperPackage Include=".NETFramework,Version=v4.5">
<Visible>False</Visible>
<ProductName>Microsoft .NET Framework 4.5 %28x86 and x64%29</ProductName>
<Install>true</Install>
</BootstrapperPackage>
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
<Visible>False</Visible>
<ProductName>.NET Framework 3.5 SP1</ProductName>
<Install>false</Install>
</BootstrapperPackage>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.