Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for generating LuaCATS definitions for the Lua API #3755

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 162 additions & 0 deletions src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using BizHawk.Common;
using NLua;

namespace BizHawk.Client.Common;

/// <summary>
/// Generates API definitions in the LuaCATS format.
/// </summary>
/// <remarks>
/// See https://luals.github.io/wiki/annotations
/// </remarks>
internal class LuaCatsGenerator
{
private static readonly Dictionary<Type, string> TypeConversions = new()
{
[typeof(object)] = "any",
[typeof(int)] = "integer",
[typeof(uint)] = "integer",
[typeof(short)] = "integer",
[typeof(ushort)] = "integer",
[typeof(long)] = "integer",
[typeof(ulong)] = "integer",
[typeof(float)] = "number",
[typeof(double)] = "number",
[typeof(string)] = "string",
[typeof(bool)] = "boolean",
[typeof(LuaFunction)] = "function",
[typeof(LuaTable)] = "table",
[typeof(System.Drawing.Color)] = "color",
};

private const string Preamble = @"---@meta _

---@class color : userdata

---A color in one of the following formats:
--- - Number in the format `0xAARRGGBB`
--- - String in the format `""#RRGGBB""` or `""#AARRGGBB""`
--- - A CSS3/X11 color name e.g. `""blue""`, `""palegoldenrod""`
--- - Color created with `forms.createcolor`
---@alias luacolor integer | string | color
";

public string Generate(LuaDocumentation docs)
{
var sb = new StringBuilder();

sb.AppendLine($"--Generated with BizHawk {VersionInfo.MainVersion}");

sb.AppendLine(Preamble);

foreach (var libraryGroup in docs.GroupBy(func => func.Library).OrderBy(group => group.Key))
{
string library = libraryGroup.Key;
string libraryDescription = libraryGroup.First().LibraryDescription;

if (!string.IsNullOrEmpty(libraryDescription))
sb.AppendLine(FormatDescription(libraryDescription));
sb.AppendLine($"---@class {library}");
sb.AppendLine($"{library} = {{}}");
sb.AppendLine();

foreach (var func in libraryGroup.OrderBy(func => func.Name))
{
if (!string.IsNullOrEmpty(func.Description))
sb.AppendLine(FormatDescription(func.Description));

if (func.IsDeprecated)
sb.AppendLine("---@deprecated");

foreach (var parameter in func.Method.GetParameters())
{
if (IsParams(parameter))
{
sb.Append("---@vararg");
}
else
{
sb.Append($"---@param {parameter.Name}");
if (parameter.IsOptional || IsNullable(parameter.ParameterType))
sb.Append('?');
}

sb.Append(' ');
sb.AppendLine(GetLuaType(parameter));
}

if (func.Method.ReturnType != typeof(void))
{
sb.Append("---@return ");
sb.AppendLine(GetLuaType(func.Method.ReturnType));
}

sb.Append($"function {library}.{func.Name}(");

foreach (var parameter in func.Method.GetParameters())
{
if (parameter.Position > 0)
sb.Append(", ");
sb.Append(IsParams(parameter) ? "..." : parameter.Name);
}

sb.AppendLine(") end");
sb.AppendLine();
}
sb.AppendLine();
}
return sb.ToString();
}

private static string FormatDescription(string description)
{
// prefix every line with ---
description = Regex.Replace(description, "^", "---", RegexOptions.Multiline);
// replace {{wiki markup}} with `markdown`
description = Regex.Replace(description, @"{{(.+?)}}", "`$1`");
// replace wiki image markup with markdown
description = Regex.Replace(description, @"\[(?<url>.+?)\|alt=(?<alt>.+?)\]", "![${alt}](${url})");
return description;
}

private static string GetLuaType(ParameterInfo parameter)
{
if (parameter.ParameterType == typeof(object) && parameter.GetCustomAttribute<LuaColorParamAttribute>() is not null)
return "luacolor"; // see Preamble

// no [] array modifier for varargs
if (parameter.ParameterType.IsArray && IsParams(parameter))
return GetLuaType(parameter.ParameterType.GetElementType());

// technically any string parameter can be passed a number, but let's just focus on the ones where it's commonly used
// like `gui.text` and `forms.settext` instead of polluting the entire API surface
if (parameter.ParameterType == typeof(string) && parameter.Name is "message" or "caption")
return "string | number";

return GetLuaType(parameter.ParameterType);
}

private static string GetLuaType(Type type)
{
if (type.IsArray)
return GetLuaType(type.GetElementType()) + "[]";

if (IsNullable(type))
type = type.GetGenericArguments()[0];

if (TypeConversions.TryGetValue(type, out string luaType))
return luaType;
else
throw new NotSupportedException($"Unknown type {type.FullName} used in API. Generator must be updated to handle this.");
}

private static bool IsNullable(Type type) => type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);

private static bool IsParams(ParameterInfo parameter) => parameter.GetCustomAttribute<ParamArrayAttribute>() is not null;
}
6 changes: 6 additions & 0 deletions src/BizHawk.Client.Common/lua/LuaDocumentation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,12 @@ public string ToNotepadPlusPlusAutoComplete()
{
return ""; // TODO
}

public string ToLuaLanguageServerDefinitions()
{
var generator = new LuaCatsGenerator();
return generator.Generate(this);
}
}

public class LibraryFunction
Expand Down
10 changes: 9 additions & 1 deletion src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1186,6 +1186,18 @@ private void RegisterNotePadMenuItem_Click(object sender, EventArgs e)
_luaAutoInstaller.InstallBizLua(LuaAutocompleteInstaller.TextEditors.NotePad, LuaImp.Docs);
}

private void GenerateLuaCatsDefinitionMenuItem_Click(object sender, EventArgs e)
{
string initDir = !string.IsNullOrWhiteSpace(LuaImp.ScriptList.Filename)
? Path.GetDirectoryName(LuaImp.ScriptList.Filename)
: Config!.PathEntries.LuaAbsolutePath();

if (this.ShowFileSaveDialog(initDir, initFileName: "BizHawk.lua", fileExt: ".lua", filter: JustScriptsFSFilterSet) is string path)
{
File.WriteAllText(path, LuaImp.Docs.ToLuaLanguageServerDefinitions());
}
}

private void FunctionsListMenuItem_Click(object sender, EventArgs e)
{
new LuaFunctionsForm(LuaImp.Docs).Show();
Expand Down