Skip to content

Instantly share code, notes, and snippets.

@TinkerWorX
Created June 6, 2019 12:04
Show Gist options
  • Save TinkerWorX/16acf7355cb08dca6c3a4d16b67b3402 to your computer and use it in GitHub Desktop.
Save TinkerWorX/16acf7355cb08dca6c3a4d16b67b3402 to your computer and use it in GitHub Desktop.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.IO;
namespace TinkerWorX.JassToTypeScript
{
internal class TypeDefinition
{
public string Name { get; set; }
public string Parent { get; set; }
}
internal class ArgumentDefinition
{
public string Type { get; set; }
public string Name { get; set; }
}
internal class NativeDefinition
{
public string Name { get; set; }
public List<ArgumentDefinition> Arguments { get; set; }
public string ReturnType { get; set; }
}
internal class GlobalDefinition
{
public bool IsConstant { get; set; }
public string Type { get; set; }
public bool IsArray { get; set; }
public string Name { get; set; }
public string Value { get; set; }
}
internal class FunctionDefinition
{
public string Name { get; set; }
public List<ArgumentDefinition> Arguments { get; set; }
public string ReturnType { get; set; }
}
internal class LibraryDefinition
{
public List<TypeDefinition> Types { get; } = new List<TypeDefinition>();
public List<NativeDefinition> Natives { get; } = new List<NativeDefinition>();
public List<GlobalDefinition> Globals { get; } = new List<GlobalDefinition>();
public List<FunctionDefinition> Functions { get; } = new List<FunctionDefinition>();
}
internal class Program
{
private const string TYPE_DEFINITION = @"type\s+(?<name>\w+)\s+extends\s+(?<parent>\w+)";
private const string NATIVE_DEFINITION = @"native\s+(?<name>\w+)\s+takes\s+(?<prototype>.+)";
private const string GLOBAL_DEFINITION = @"(?<constant>constant)?\s*(?<type>\w+)(\s+(?<array>array))?\s+(?<name>\w+)(\s+=\s(?<value>.+))?";
private const string FUNCTION_DEFINITION = @"function\s+(?<name>\w+)\s+takes\s+(?<prototype>.+)";
private static string Clean(string input)
{
input = input.Trim();
while (input.IndexOf(" ") >= 0)
input = input.Replace(" ", " ");
if (input.IndexOf("//") >= 0)
input = input.Substring(0, input.IndexOf("//"));
while (input.IndexOf(" ") >= 0)
input = input.Replace(" ", " ");
return input;
}
private static void ParseLines(string[] lines, LibraryDefinition library)
{
bool inGlobals = false;
foreach (var rawLine in lines)
{
var line = Clean(rawLine);
if (line.StartsWith("//"))
continue;
if (inGlobals)
{
inGlobals = !line.Contains("endglobals");
if (!inGlobals)
continue;
var globalDefinition = Regex.Match(line, GLOBAL_DEFINITION);
if (globalDefinition.Success)
{
var type = globalDefinition.Groups["type"].Value;
var value = globalDefinition.Groups["value"].Value;
//if (value.Length == 6 && value[0] == '\'' && value[5] == '\'')
// type = "string";
library.Globals.Add(new GlobalDefinition
{
IsConstant = !String.IsNullOrWhiteSpace(globalDefinition.Groups["constant"].Value),
Type = type,
IsArray = !String.IsNullOrWhiteSpace(globalDefinition.Groups["array"].Value),
Name = globalDefinition.Groups["name"].Value,
Value = value,
});
continue;
}
}
else
{
inGlobals = line.Contains("globals");
if (inGlobals)
continue;
var typeDefinition = Regex.Match(line, TYPE_DEFINITION);
if (typeDefinition.Success)
{
library.Types.Add(new TypeDefinition
{
Name = typeDefinition.Groups["name"].Value,
Parent = typeDefinition.Groups["parent"].Value
});
continue;
}
var nativeDefinition = Regex.Match(line, NATIVE_DEFINITION);
if (nativeDefinition.Success)
{
var name = nativeDefinition.Groups["name"].Value;
var prototype = nativeDefinition.Groups["prototype"].Value;
var takes = Clean(prototype.Split(new string[] { "returns" }, StringSplitOptions.None)[0]);
var returns = Clean(prototype.Split(new string[] { "returns" }, StringSplitOptions.None)[1]);
library.Natives.Add(new NativeDefinition
{
Name = name,
Arguments = takes.Equals("nothing") ? new List<ArgumentDefinition>() : takes.Split(',').Select(s => s.Trim()).Select(s => new ArgumentDefinition
{
Type = s.Split(' ')[0],
Name = s.Split(' ')[1],
}).ToList(),
ReturnType = returns,
});
continue;
}
var functionDefinition = Regex.Match(line, FUNCTION_DEFINITION);
if (functionDefinition.Success)
{
var name = functionDefinition.Groups["name"].Value;
var prototype = functionDefinition.Groups["prototype"].Value;
var takes = Clean(prototype.Split(new string[] { "returns" }, StringSplitOptions.None)[0]);
var returns = Clean(prototype.Split(new string[] { "returns" }, StringSplitOptions.None)[1]);
library.Functions.Add(new FunctionDefinition
{
Name = name,
Arguments = takes.Equals("nothing") ? new List<ArgumentDefinition>() : takes.Split(',').Select(s => s.Trim()).Select(s => new ArgumentDefinition
{
Type = s.Split(' ')[0],
Name = s.Split(' ')[1],
}).ToList(),
ReturnType = returns,
});
continue;
}
}
}
}
private static void ParseFile(string path, LibraryDefinition library)
{
ParseLines(File.ReadAllLines(path), library);
}
private static string FixType(string type)
{
switch (type)
{
case "real":
case "integer":
type = "number";
break;
case "nothing":
type = "void";
break;
case "code":
type = "() => void";
break;
//case "boolexpr":
//case "conditionfunc":
//case "filterfunc":
//type = "() => boolean";
//break;
}
return type;
}
private static void Magic(NativeDefinition native)
{
switch (native.Name)
{
case "Condition":
case "Filter":
native.Arguments[0].Type = "() => boolean";
break;
}
}
private static int Main(string[] args)
{
if (args.Length < 2)
{
Console.WriteLine("Usage: TinkerWorX.JassToTypeScript.exe input1.j [input2.j...] output.d.ts");
return 1;
}
var inputFiles = args.Reverse().Skip(1).Reverse();
var outputFile = args.Reverse().First();
var library = new LibraryDefinition();
foreach (var inputFile in inputFiles)
{
Console.WriteLine($"Parsing: {inputFile}");
ParseFile(inputFile, library);
}
Console.WriteLine($"Writing: {outputFile}");
using (var stream = File.Open(outputFile, FileMode.Create, FileAccess.Write, FileShare.Write))
using (var writer = new StreamWriter(stream))
{
writer.WriteLine("/** @noSelfInFile **/");
writer.WriteLine();
foreach (var type in library.Types)
{
writer.WriteLine($"declare abstract class {type.Name} extends {type.Parent} {{ __{type.Name}: never; }}");
}
writer.WriteLine();
foreach (var native in library.Natives)
{
Magic(native);
writer.Write($"declare function {native.Name}(");
if (native.Arguments.Any())
writer.Write($"{native.Arguments.Select(arg => $"{arg.Name}: {FixType(arg.Type)}").Aggregate((a, b) => a + ", " + b)}");
writer.WriteLine($"): {FixType(native.ReturnType)}");
}
writer.WriteLine();
foreach (var global in library.Globals)
{
writer.Write("declare");
if (global.IsConstant)
writer.Write(" const");
else
writer.Write(" var");
writer.Write($" {global.Name}");
writer.WriteLine($": {FixType(global.Type)}");
}
writer.WriteLine();
foreach (var function in library.Functions)
{
writer.Write($"declare function {function.Name}(");
if (function.Arguments.Any())
writer.Write($"{function.Arguments.Select(arg => $"{arg.Name}: {FixType(arg.Type)}").Aggregate((a, b) => a + ", " + b)}");
writer.WriteLine($"): {FixType(function.ReturnType)}");
}
}
return 0;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment