asynchronousai commited on
Commit
1ae2e8e
·
verified ·
1 Parent(s): 918b834

Upload 33 files

Browse files
Files changed (33) hide show
  1. roblox-cs-master/.github/FUNDING.yml +4 -0
  2. roblox-cs-master/.github/workflows/publish.yml +50 -0
  3. roblox-cs-master/.github/workflows/tests.yml +26 -0
  4. roblox-cs-master/.gitignore +5 -0
  5. roblox-cs-master/README.md +13 -0
  6. roblox-cs-master/RobloxCS.CLI/Program.cs +178 -0
  7. roblox-cs-master/RobloxCS.CLI/RobloxCS.CLI.csproj +25 -0
  8. roblox-cs-master/RobloxCS.Tests/CodeGenerator_Should.cs +290 -0
  9. roblox-cs-master/RobloxCS.Tests/DebugTransformer_Should.cs +51 -0
  10. roblox-cs-master/RobloxCS.Tests/GlobalUsings.cs +4 -0
  11. roblox-cs-master/RobloxCS.Tests/MainTransformer_Should.cs +21 -0
  12. roblox-cs-master/RobloxCS.Tests/RobloxCS.Tests.csproj +30 -0
  13. roblox-cs-master/RobloxCS/CodeGenerator.cs +2097 -0
  14. roblox-cs-master/RobloxCS/CompiledFile.cs +14 -0
  15. roblox-cs-master/RobloxCS/ConfigReader.cs +74 -0
  16. roblox-cs-master/RobloxCS/ConfigTemplate.cs +38 -0
  17. roblox-cs-master/RobloxCS/Constants.cs +143 -0
  18. roblox-cs-master/RobloxCS/FileManager.cs +86 -0
  19. roblox-cs-master/RobloxCS/Include/RuntimeLib.lua +321 -0
  20. roblox-cs-master/RobloxCS/Logger.cs +109 -0
  21. roblox-cs-master/RobloxCS/MemberCollector.cs +45 -0
  22. roblox-cs-master/RobloxCS/README.md +30 -0
  23. roblox-cs-master/RobloxCS/RobloxCS.csproj +18 -0
  24. roblox-cs-master/RobloxCS/RojoReader.cs +186 -0
  25. roblox-cs-master/RobloxCS/SymbolAnalyzer.cs +110 -0
  26. roblox-cs-master/RobloxCS/Transformers/BaseTransformer.cs +40 -0
  27. roblox-cs-master/RobloxCS/Transformers/BuiltInTransformers.cs +29 -0
  28. roblox-cs-master/RobloxCS/Transformers/DebugTransformer.cs +91 -0
  29. roblox-cs-master/RobloxCS/Transformers/MainTransformer.cs +137 -0
  30. roblox-cs-master/RobloxCS/Transpiler.cs +128 -0
  31. roblox-cs-master/RobloxCS/TranspilerUtility.cs +118 -0
  32. roblox-cs-master/RobloxCS/Utility.cs +255 -0
  33. roblox-cs-master/roblox-cs.sln +45 -0
roblox-cs-master/.github/FUNDING.yml ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ # These are supported funding model platforms
2
+
3
+ github: R-unic
4
+ ko_fi: runic_
roblox-cs-master/.github/workflows/publish.yml ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Publish
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - master
7
+
8
+ jobs:
9
+ publish:
10
+ runs-on: ubuntu-latest
11
+
12
+ steps:
13
+ - name: Checkout code
14
+ uses: actions/checkout@v4
15
+
16
+ - name: Set up .NET Core
17
+ uses: actions/[email protected]
18
+ with:
19
+ dotnet-version: '8.0.x'
20
+
21
+ - name: Install xmllint
22
+ run: sudo apt-get update && sudo apt-get install -y libxml2-utils
23
+
24
+ - name: Add GitHub source to NuGet
25
+ run: dotnet nuget add source --username roblox-csharp --password ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text --name github "https://nuget.pkg.github.com/roblox-csharp/index.json"
26
+
27
+ - name: Extract version from .csproj
28
+ id: extract_version
29
+ run: |
30
+ VERSION=$(xmllint --xpath "string(//Project/PropertyGroup/Version)" RobloxCS/RobloxCS.csproj)
31
+ echo "PACKAGE_VERSION=$VERSION" >> $GITHUB_ENV
32
+
33
+ - name: Package the project
34
+ run: dotnet pack --configuration Release
35
+
36
+ - name: Check if version is already published
37
+ id: check_version
38
+ run: |
39
+ VERSION_EXISTS=$(dotnet nuget search roblox-cs --source "github" | grep -c "${{ env.PACKAGE_VERSION }}" || true)
40
+ echo "VERSION_EXISTS=$VERSION_EXISTS" >> $GITHUB_ENV
41
+
42
+ - name: Find and publish the latest package
43
+ if: env.VERSION_EXISTS == '0'
44
+ run: |
45
+ PACKAGE_PATH=$(find ./RobloxCS/bin/Release -name "*.nupkg" -type f -print0 | xargs -0 ls -1t | head -n 1)
46
+ dotnet nuget push "$PACKAGE_PATH" --api-key ${{ secrets.GITHUB_TOKEN }} --source "github" --skip-duplicate
47
+
48
+ - name: Version already published
49
+ if: env.VERSION_EXISTS != '0'
50
+ run: echo "The version is already published. Skipping publish step."
roblox-cs-master/.github/workflows/tests.yml ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: CI
2
+ on:
3
+ push:
4
+ branches: [ "master" ]
5
+ pull_request:
6
+ branches: [ "master" ]
7
+
8
+ jobs:
9
+ build:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - name: Setup .NET
14
+ uses: actions/setup-dotnet@v4
15
+ with:
16
+ dotnet-version: 8.0.x
17
+ - name: Add GitHub source to NuGet
18
+ run: dotnet nuget add source --username roblox-csharp --password ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text --name github "https://nuget.pkg.github.com/roblox-csharp/index.json"
19
+ - name: Restore dependencies
20
+ run: dotnet restore
21
+ - name: Build (Release)
22
+ run: dotnet build -c Release --no-restore
23
+ - name: Build (Debug)
24
+ run: dotnet build -c Debug --no-restore
25
+ - name: Test
26
+ run: dotnet test --no-build --verbosity normal
roblox-cs-master/.gitignore ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ bin/
2
+ obj/
3
+ .vs/
4
+ include/
5
+ TestProject/
roblox-cs-master/README.md ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # roblox-cs
2
+
3
+ ![CI](https://github.com/roblox-csharp/roblox-cs/actions/workflows/tests.yml/badge.svg)
4
+ Roblox CSharp to Lua compiler
5
+
6
+ ## Contributing
7
+ ### This section only applies for the `rewrite` branch currently, I will not accept PRs on the `master` branch!
8
+
9
+ 1. [Fork it](https://github.com/roblox-csharp/roblox-cs/fork)
10
+ 2. Commit your changes (`git commit -m 'feat: add some feature'`)
11
+ 3. Test your code (`dotnet test` or Ctrl + R, A in Visual Studio)
12
+ 4. Push your changes (`git push`)
13
+ 5. Create a pull request
roblox-cs-master/RobloxCS.CLI/Program.cs ADDED
@@ -0,0 +1,178 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using System.Reflection;
2
+ using LibGit2Sharp;
3
+ using CommandLine;
4
+ using Spectre.Console;
5
+ using DotNet = Microsoft.DotNet.Cli.Utils;
6
+
7
+ namespace RobloxCS.CLI
8
+ {
9
+ public static class Program
10
+ {
11
+ private const string _exampleProjectRepo = "https://github.com/roblox-csharp/example-project.git";
12
+ private static readonly Assembly _compilerAssembly = Assembly.Load("RobloxCS");
13
+ private static readonly System.Version _version = _compilerAssembly.GetName().Version!;
14
+
15
+ public class Options
16
+ {
17
+ [Option('v', "version", Required = false, HelpText = "Return the compiler version.")]
18
+ public bool Version { get; set; }
19
+
20
+ [Option('w', "watch", Required = false, HelpText = "Watches the directory and re-compiles when changes are detected.")]
21
+ public bool WatchMode { get; set; }
22
+
23
+ [Option("init", Required = false, HelpText = "Initialize a new roblox-cs project.")]
24
+ public bool Init { get; set; }
25
+
26
+ [Value(0, Required = false, HelpText = "Project directory path", MetaName = "Path")]
27
+ public string? Path { get; set; }
28
+ }
29
+
30
+ public static void Main(string[] args)
31
+ {
32
+ Parser.Default.ParseArguments<Options>(args)
33
+ .WithParsed(options =>
34
+ {
35
+ var path = options.Path ?? ".";
36
+ if (options.Version)
37
+ {
38
+ Console.WriteLine($"roblox-cs {_version.Major}.{_version.Minor}.{_version.Build}");
39
+ }
40
+ else if (options.Init)
41
+ {
42
+ InitializeProject(path);
43
+ }
44
+ else if (options.WatchMode)
45
+ {
46
+ StartWatchMode(path);
47
+ }
48
+ else
49
+ {
50
+ new Transpiler(path).Transpile();
51
+ }
52
+ });
53
+ }
54
+
55
+ private static void StartWatchMode(string path)
56
+ {
57
+ var transpiler = new Transpiler(path);
58
+ var watcher = new FileSystemWatcher
59
+ {
60
+ Path = path,
61
+ NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.Size,
62
+ Filter = "*.*",
63
+ IncludeSubdirectories = true
64
+ };
65
+
66
+ watcher.Changed += (sender, e) => OnFileChanged(path, e);
67
+ watcher.Created += (sender, e) => OnFileChanged(path, e);
68
+ watcher.Renamed += (sender, e) => OnFileChanged(path, e);
69
+ watcher.Deleted += (sender, e) => OnFileChanged(path, e);
70
+ watcher.EnableRaisingEvents = true;
71
+
72
+ AnsiConsole.MarkupLine("[yellow]Watching for file changes. Press \"enter\" to exit.[/]");
73
+ Console.WriteLine();
74
+ Console.ReadLine();
75
+ }
76
+
77
+ private static void OnFileChanged(string path, FileSystemEventArgs e)
78
+ {
79
+ HashSet<WatcherChangeTypes> validChangeTypes = [WatcherChangeTypes.Changed, WatcherChangeTypes.Created, WatcherChangeTypes.Renamed];
80
+ if (!validChangeTypes.Contains(e.ChangeType)) return;
81
+
82
+ var filePath = Utility.FixPathSep(e.FullPath);
83
+ var extension = Path.GetExtension(filePath);
84
+ var transpiler = new Transpiler(path);
85
+ var isValidLuaFile = extension == ".lua"
86
+ && !filePath.EndsWith("include/RuntimeLib.lua")
87
+ && !filePath.Contains($"/{transpiler.Config.OutputFolder}/");
88
+
89
+ if (extension == ".cs" || isValidLuaFile || extension == ".yml")
90
+ {
91
+ AnsiConsole.MarkupLine($"[yellow]{(e.ChangeType == WatcherChangeTypes.Renamed ? "Changed" : e.ChangeType)}: {filePath}[/]");
92
+ transpiler.Transpile();
93
+ }
94
+ }
95
+
96
+ private static void InitializeProject(string path)
97
+ {
98
+ var projectName = Path.GetDirectoryName(path) == "" ? Path.GetFileName(path) : Path.GetDirectoryName(path) ?? "Example";
99
+ try
100
+ {
101
+ Repository.Clone(_exampleProjectRepo, path);
102
+ AnsiConsole.MarkupLine($"[cyan]Repository cloned to {Path.GetFullPath(path)}[/]");
103
+ }
104
+ catch (Exception ex)
105
+ {
106
+ AnsiConsole.MarkupLine($"[red]Failed to clone example project repository: {ex.Message}[/]");
107
+ Environment.Exit(1);
108
+ }
109
+
110
+ DeleteDirectoryManual(Path.Combine(path, ".git"));
111
+ DotNet.Command.Create("dotnet", ["restore", path]).Execute();
112
+ AnsiConsole.MarkupLine("[cyan]Successfully restored .NET project.[/]");
113
+
114
+ if (projectName != "Example")
115
+ {
116
+ var projectFolder = path;
117
+ var projectSourceFolder = Path.Combine(projectFolder, projectName, "src");
118
+ var projectFile = Path.Combine(projectSourceFolder, $"{projectName}.csproj");
119
+ var rojoManifestFile = Path.Combine(projectFolder, projectName, "default.project.json");
120
+
121
+ AnsiConsole.MarkupLine("[yellow]Renaming project...[/]");
122
+ File.Delete(Path.Combine(path, "Example.sln"));
123
+ DotNet.Command.Create("dotnet", ["new", "sln", "-n", projectName, "-o", path]).Execute();
124
+ Directory.Move(Path.Combine(path, "Example"), Path.Combine(projectFolder, projectName));
125
+ File.Move(Path.Combine(projectSourceFolder, "Example.csproj"), projectFile);
126
+
127
+ // Dynamicallty replace <Title>...</Title> inside of the csproj file. This sucks, and we must edit the solution to not lose the reference to it.
128
+ var lines = File.ReadAllText(projectFile);
129
+ var start = lines.IndexOf("<Title>", StringComparison.InvariantCulture) + "<Title>".Length;
130
+ var end = lines.IndexOf("</Title>", StringComparison.InvariantCulture);
131
+ lines = lines.Remove(start, end - start);
132
+ lines = lines.Insert(start, projectName);
133
+ File.WriteAllText(projectFile, lines);
134
+
135
+ DotNet.Command.Create("dotnet", ["sln", Path.Combine(path, $"{projectName}.sln"), "add", projectFile]).Execute();
136
+ AnsiConsole.MarkupLine("[cyan]Successfully renamed project.[/]");
137
+
138
+ AnsiConsole.MarkupLine("[yellow]Modifying rojo manifest...[/]");
139
+
140
+ var rojoManifest = File.ReadAllText(rojoManifestFile);
141
+ // Match for the name JSON key and its end and replace the name of it, horrible, but works.
142
+ var idxOfStart = rojoManifest.IndexOf("\"name\": \"", StringComparison.InvariantCulture) + "\"name\": \"".Length;
143
+ var idxOfEnd = rojoManifest.IndexOf("\",", idxOfStart, StringComparison.InvariantCulture);
144
+ rojoManifest = rojoManifest.Remove(idxOfStart, idxOfEnd - idxOfStart);
145
+ rojoManifest = rojoManifest.Insert(idxOfStart, projectName);
146
+ File.WriteAllText(rojoManifestFile, rojoManifest);
147
+
148
+ }
149
+
150
+ Console.WriteLine($"Configuration:");
151
+ var initRepo = AnsiConsole.Confirm("\t[yellow]Do you want to initialize a Git repository?[/]", true);
152
+ if (initRepo)
153
+ {
154
+ Repository.Init(path);
155
+ AnsiConsole.MarkupLine("[cyan]Successfully initialized Git repository.[/]");
156
+ }
157
+ }
158
+
159
+ private static void DeleteDirectoryManual(string path)
160
+ {
161
+ if (Directory.Exists(path))
162
+ {
163
+ foreach (string file in Directory.GetFiles(path))
164
+ {
165
+ File.SetAttributes(file, FileAttributes.Normal); // Ensure file is not read-only
166
+ File.Delete(file);
167
+ }
168
+
169
+ foreach (string subdir in Directory.GetDirectories(path))
170
+ {
171
+ DeleteDirectoryManual(subdir);
172
+ }
173
+
174
+ Directory.Delete(path, recursive: true);
175
+ }
176
+ }
177
+ }
178
+ }
roblox-cs-master/RobloxCS.CLI/RobloxCS.CLI.csproj ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <Project Sdk="Microsoft.NET.Sdk">
2
+
3
+ <PropertyGroup>
4
+ <OutputType>Exe</OutputType>
5
+ <OutputPath>../bin/</OutputPath>
6
+ <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
7
+ <AssemblyName>rbxcsc</AssemblyName>
8
+ <RootNamespace>RobloxCS</RootNamespace>
9
+ <TargetFramework>net8.0</TargetFramework>
10
+ <ImplicitUsings>enable</ImplicitUsings>
11
+ <Nullable>enable</Nullable>
12
+ </PropertyGroup>
13
+
14
+ <ItemGroup>
15
+ <ProjectReference Include="..\RobloxCS\RobloxCS.csproj" />
16
+ </ItemGroup>
17
+
18
+ <ItemGroup>
19
+ <PackageReference Include="CommandLineParser" Version="2.9.1" />
20
+ <PackageReference Include="LibGit2Sharp" Version="0.30.0" />
21
+ <PackageReference Include="Microsoft.DotNet.Cli.Utils" Version="2.0.0" />
22
+ <PackageReference Include="Spectre.Console" Version="0.49.1" />
23
+ </ItemGroup>
24
+
25
+ </Project>
roblox-cs-master/RobloxCS.Tests/CodeGenerator_Should.cs ADDED
@@ -0,0 +1,290 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ namespace RobloxCS.Tests
2
+ {
3
+ public class CodeGenerator_Should
4
+ {
5
+ [Fact]
6
+ public void NativeAttribute_GeneratesLuauAttribute()
7
+ {
8
+ var cleanedLua = GetCleanLua("using Roblox; [Native] void doSomething() { }");
9
+ var lines = GetLines(cleanedLua);
10
+ var expectedLines = new List<string>
11
+ {
12
+ "@native",
13
+ "local function doSomething(): nil",
14
+ "return nil :: any",
15
+ "end"
16
+ };
17
+
18
+ AssertEqualLines(lines, expectedLines);
19
+ }
20
+
21
+ [Fact]
22
+ public void IfStatements_GeneratesIf()
23
+ {
24
+ var cleanedLua = GetCleanLua("using static Roblox.Globals; var x = 1; if (x == 4) print(\"x is 4\"); else if (x == \"abc\") print(\"x is abc\"); else print(\"x is unknown\");", 1);
25
+ var lines = GetLines(cleanedLua);
26
+ var expectedLines = new List<string>
27
+ {
28
+ "if x == 4 then",
29
+ "print(\"x is 4\")",
30
+ "elseif x == \"abc\" then",
31
+ "print(\"x is abc\")",
32
+ "else",
33
+ "print(\"x is unknown\")",
34
+ "end"
35
+ };
36
+
37
+ AssertEqualLines(lines, expectedLines);
38
+ }
39
+
40
+ [Theory]
41
+ [InlineData("var @abc = 1")]
42
+ [InlineData("var @hello_brah = 1")]
43
+ [InlineData("var @SIGMA = 1")]
44
+ public void IdentifierWithAtSymbol_HasAtSymbolRemoved(string expression)
45
+ {
46
+ var cleanedLua = GetCleanLua(expression);
47
+ Assert.Equal(expression.Replace("@", "").Replace("var", "local"), cleanedLua);
48
+ }
49
+
50
+ [Theory]
51
+ [InlineData("ToNumber(\"69\")")]
52
+ [InlineData("ToFloat(\"69\")")]
53
+ [InlineData("ToDouble(\"69\")")]
54
+ [InlineData("ToInt(\"69\")")]
55
+ [InlineData("ToUInt(\"69\")")]
56
+ [InlineData("ToShort(\"69\")")]
57
+ [InlineData("ToUShort(\"69\")")]
58
+ [InlineData("ToByte(\"69\")")]
59
+ [InlineData("ToSByte(\"69\")")]
60
+ public void NumberParsingMethods_MacroToToNumber(string methodCall)
61
+ {
62
+ var cleanedLua = GetCleanLua(methodCall);
63
+ Assert.Equal("tonumber(\"69\")", cleanedLua);
64
+ }
65
+
66
+ [Fact]
67
+ public void NamespaceDeclaration_GeneratesRuntimeCalls()
68
+ {
69
+
70
+ var cleanedLua = GetCleanLua("namespace Test { }");
71
+ var expectedLua = "CS.namespace(\"Test\", @native function(namespace: CS.Namespace)\nend)";
72
+ Assert.Equal(expectedLua, cleanedLua);
73
+ }
74
+
75
+ [Fact]
76
+ public void NestedNamespaceDeclaration_GeneratesRuntimeCalls()
77
+ {
78
+
79
+ var cleanedLua = GetCleanLua("namespace Test.Nested { }");
80
+ var lines = GetLines(cleanedLua);
81
+ var expectedLines = new List<string>
82
+ {
83
+ "CS.namespace(\"Test\", @native function(namespace: CS.Namespace)",
84
+ "namespace:namespace(\"Nested\", @native function(namespace: CS.Namespace)",
85
+ "end)",
86
+ "end)"
87
+ };
88
+
89
+ AssertEqualLines(lines, expectedLines);
90
+ }
91
+
92
+ [Fact]
93
+ public void ClassDeclaration_GeneratesRuntimeCalls()
94
+ {
95
+ var cleanedLua = GetCleanLua("namespace Test { class HelloWorld { } }");
96
+ var lines = GetLines(cleanedLua);
97
+ var expectedLines = new List<string>
98
+ {
99
+ "CS.namespace(\"Test\", @native function(namespace: CS.Namespace)",
100
+ "namespace:class(\"HelloWorld\", @native function(namespace: CS.Namespace)",
101
+ "local class = CS.classDef(\"HelloWorld\", namespace)",
102
+ "",
103
+ "function class.new()",
104
+ "local mt = {}",
105
+ "local self = CS.classInstance(class, mt, namespace)",
106
+ "",
107
+ "",
108
+ "return self",
109
+ "end",
110
+ "",
111
+ "return class",
112
+ "end)",
113
+ "end)"
114
+ };
115
+
116
+ AssertEqualLines(lines, expectedLines);
117
+ }
118
+
119
+ [Theory]
120
+ [InlineData("var x = 5;", "local x = 5")]
121
+ [InlineData("char f = 'f'", "local f: string = \"f\"")]
122
+ [InlineData("object a = 123", "local a: any = 123")]
123
+ public void VariableDeclaration_GeneratesLocal(string input, string expected)
124
+ {
125
+ var cleanedLua = GetCleanLua(input);
126
+ Assert.Equal(expected, cleanedLua);
127
+ }
128
+
129
+ [Theory]
130
+ [InlineData("(1 + 2) * 4")]
131
+ [InlineData("(44 / (4 % 6) * 12) - 2")]
132
+ public void Parentheses_GenerateParentheses(string input)
133
+ {
134
+ var cleanedLua = GetCleanLua(input);
135
+ Assert.Equal(input, cleanedLua);
136
+ }
137
+
138
+ [Theory]
139
+ [InlineData("69", "69")]
140
+ [InlineData("420.0f", "420")]
141
+ [InlineData("'h'", "\"h\"")]
142
+ [InlineData("\"abcefg\"", "\"abcefg\"")]
143
+ [InlineData("true", "true")]
144
+ [InlineData("false", "false")]
145
+ [InlineData("null", "nil")]
146
+ public void Literal_GeneratesLiteral(string input, string expected)
147
+ {
148
+ var cleanedLua = GetCleanLua(input);
149
+ Assert.Equal(expected, cleanedLua);
150
+ }
151
+
152
+ [Fact]
153
+ public void StringInterpolation_GeneratesInterpolation()
154
+ {
155
+ var cleanedLua = GetCleanLua("int count = 6; $\"count: {count}\"", 1);
156
+ var expectedLua = "`count: {count}`";
157
+ Assert.Equal(expectedLua, cleanedLua);
158
+ }
159
+
160
+ [Theory]
161
+ [InlineData("Vector3")]
162
+ [InlineData("NumberRange")]
163
+ [InlineData("BrickColor")]
164
+ [InlineData("Instance")]
165
+ public void RobloxType_DoesNotGenerateGetAssemblyTypeCall(string robloxType)
166
+ {
167
+
168
+ var cleanedLua = GetCleanLua($"using {Utility.RuntimeAssemblyName}; {robloxType}.a;");
169
+ Assert.Equal(robloxType + ".a", cleanedLua);
170
+ }
171
+
172
+ [Theory]
173
+ [InlineData("Instance")]
174
+ [InlineData($"{Utility.RuntimeAssemblyName}.Instance")]
175
+ public void InstanceCreate_Macros(string instanceClassPath)
176
+ {
177
+ var cleanedLua = GetCleanLua($"using {Utility.RuntimeAssemblyName}; var part = {instanceClassPath}.Create<Part>()");
178
+ Assert.Equal("local part = Instance.new(\"Part\")", cleanedLua);
179
+ }
180
+
181
+ [Fact]
182
+ public void InstanceIsA_Macros()
183
+ {
184
+ var cleanedLua = GetCleanLua($"using {Utility.RuntimeAssemblyName}; var part = Instance.Create<Part>(); part.IsA<Frame>();", 1);
185
+ Assert.Equal("part:IsA(\"Frame\")", cleanedLua);
186
+ }
187
+
188
+ [Theory]
189
+ [InlineData("using static Roblox.Globals; print")]
190
+ [InlineData("Roblox.Globals.print")]
191
+ public void ConsoleMethods_Macro(string fullMethodPath)
192
+ {
193
+ var cleanedLua = GetCleanLua($"{fullMethodPath}(\"hello world\")");
194
+ Assert.Equal("print(\"hello world\")", cleanedLua);
195
+ }
196
+
197
+ [Theory]
198
+ [InlineData("game")]
199
+ [InlineData("script")]
200
+ [InlineData("os")]
201
+ [InlineData("task")]
202
+ public void StaticClass_NoFullQualification(string memberName)
203
+ {
204
+ var cleanedLua = GetCleanLua($"{Utility.RuntimeAssemblyName}.Globals.{memberName}");
205
+ Assert.Equal(memberName, cleanedLua);
206
+ }
207
+
208
+ [Theory]
209
+ [InlineData("object obj; obj?.Name;")]
210
+ [InlineData("object a; a.b?.c;")]
211
+ public void SafeNavigation_GeneratesIfStatement(string source)
212
+ {
213
+ var cleanedLua = GetCleanLua(source, 1);
214
+ switch (source)
215
+ {
216
+ case "object obj; obj?.Name;":
217
+ Assert.Equal("if obj == nil then nil else obj.Name", cleanedLua);
218
+ break;
219
+ case "object a; a.b?.c;":
220
+ Assert.Equal("if a.b == nil then nil else a.b.c", cleanedLua);
221
+ break;
222
+ }
223
+ }
224
+
225
+ [Fact]
226
+ public void NullCoalescing_GeneratesIfStatement()
227
+ {
228
+
229
+ var cleanedLua = GetCleanLua("int? x; int? y; x ?? y", 2);
230
+ Assert.Equal("if x == nil then y else x", cleanedLua);
231
+ }
232
+
233
+ [Fact]
234
+ public void TupleExpression_GeneratesTable()
235
+ {
236
+ var cleanedLua = GetCleanLua("var tuple = (1, 2, 3)");
237
+ Assert.Equal("local tuple = {1, 2, 3}", cleanedLua);
238
+ }
239
+
240
+ [Fact]
241
+ public void TupleIndexing_GeneratesTableIndexing()
242
+ {
243
+ var cleanedLua = GetCleanLua("var tuple = (1, 2, 3);\ntuple.Item1;\ntuple.Item2;\ntuple.Item3;\n", 1);
244
+ var lines = GetLines(cleanedLua);
245
+ Assert.Equal("tuple[1]", lines[0]);
246
+ Assert.Equal("tuple[2]", lines[1]);
247
+ Assert.Equal("tuple[3]", lines[2]);
248
+ }
249
+
250
+ [Fact]
251
+ public void CollectionInitializer_GeneratesTable()
252
+ {
253
+ var cleanedLua = GetCleanLua("int[] nums = [1, 2, 3]");
254
+ Assert.Equal("local nums: { number } = {1, 2, 3}", cleanedLua);
255
+ }
256
+
257
+ [Theory]
258
+ [InlineData("int[] nums = [1, 2, 3]; nums[0]", "nums[1]", 1)]
259
+ [InlineData("int[] nums = [1, 2, 3]; int i = 4; nums[i]", "nums[i + 1]", 2)]
260
+ public void CollectionIndexing_AddsOneToNumericalIndices(string input, string expected, int removeLines)
261
+ {
262
+ var cleanedLua = GetCleanLua(input, removeLines);
263
+ Assert.Equal(expected, cleanedLua);
264
+ }
265
+
266
+ private static void AssertEqualLines(List<string> lines, List<string> expectedLines)
267
+ {
268
+ foreach (var line in lines)
269
+ {
270
+ var expectedLine = expectedLines.ElementAt(lines.IndexOf(line));
271
+ Assert.Equal(expectedLine, line);
272
+ }
273
+ }
274
+
275
+ private List<string> GetLines(string cleanLua)
276
+ {
277
+ return cleanLua.Split('\n').Select(line => line.Trim()).ToList();
278
+ }
279
+
280
+ private string GetCleanLua(string source, int extraLines = 0)
281
+ {
282
+ var cleanTree = TranspilerUtility.ParseTree(source);
283
+ var transformedTree = TranspilerUtility.TransformTree(cleanTree, [BuiltInTransformers.Main()]);
284
+ var compiler = TranspilerUtility.GetCompiler([transformedTree]);
285
+ var memberCollector = new MemberCollector([cleanTree]);
286
+ var generatedLua = TranspilerUtility.GenerateLua(transformedTree, compiler, memberCollector.Collect());
287
+ return TranspilerUtility.CleanUpLuaForTests(generatedLua, extraLines);
288
+ }
289
+ }
290
+ }
roblox-cs-master/RobloxCS.Tests/DebugTransformer_Should.cs ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ namespace RobloxCS.Tests
2
+ {
3
+ public class DebugTransformer_Should
4
+ {
5
+ private const string _testFileName = "TestFile.client.cs";
6
+
7
+ [Theory]
8
+ [InlineData("print(\"hello baby\")")]
9
+ [InlineData("warn(\"hello baby\")")]
10
+ public void FileInfo_PrependsArgument(string input)
11
+ {
12
+ var cleanTree = TranspilerUtility.ParseTree(input);
13
+ var transformedTree = TranspilerUtility.TransformTree(cleanTree, [BuiltInTransformers.Main(), BuiltInTransformers.Get("Debug")]);
14
+ var cleanRoot = cleanTree.GetRoot();
15
+ var cleanInvocation = cleanRoot.DescendantNodes().OfType<InvocationExpressionSyntax>().First();
16
+ var transformedRoot = transformedTree.GetRoot();
17
+ var transformedInvocation = transformedRoot.DescendantNodes().OfType<InvocationExpressionSyntax>().First();
18
+ var cleanArgs = cleanInvocation.ArgumentList.Arguments;
19
+ var transformedArgs = transformedInvocation.ArgumentList.Arguments;
20
+ var fileInfoArg = transformedArgs.FirstOrDefault();
21
+ Assert.True(cleanArgs.Count == 1);
22
+ Assert.True(transformedArgs.Count == 2);
23
+ Assert.NotNull(fileInfoArg);
24
+
25
+ var fileInfoLiteral = (LiteralExpressionSyntax)fileInfoArg.Expression;
26
+ Assert.Equal($"[{_testFileName}:1:51]:", fileInfoLiteral.Token.ValueText);
27
+ }
28
+
29
+ [Theory]
30
+ [InlineData("error(\"hello baby\")")]
31
+ public void FileInfo_ConcatenatesLiteral(string input)
32
+ {
33
+ var cleanTree = TranspilerUtility.ParseTree(input);
34
+ var transformedTree = TranspilerUtility.TransformTree(cleanTree, [BuiltInTransformers.Main(), BuiltInTransformers.Get("Debug")]);
35
+ var cleanRoot = cleanTree.GetRoot();
36
+ var cleanInvocation = cleanRoot.DescendantNodes().OfType<InvocationExpressionSyntax>().First();
37
+ var transformedRoot = transformedTree.GetRoot();
38
+ var transformedInvocation = transformedRoot.DescendantNodes().OfType<InvocationExpressionSyntax>().First();
39
+ var cleanArgs = cleanInvocation.ArgumentList.Arguments;
40
+ var transformedArgs = transformedInvocation.ArgumentList.Arguments;
41
+ var fullMessageArg = transformedArgs.FirstOrDefault();
42
+ Assert.True(cleanArgs.Count == 1);
43
+ Assert.True(transformedArgs.Count == 1);
44
+ Assert.NotNull(fullMessageArg);
45
+
46
+ var fullMessageBinary= (BinaryExpressionSyntax)fullMessageArg.Expression;
47
+ var fileInfoLiteral = (LiteralExpressionSyntax)fullMessageBinary.Left;
48
+ Assert.Equal($"[{_testFileName}:1:51]: ", fileInfoLiteral.Token.ValueText);
49
+ }
50
+ }
51
+ }
roblox-cs-master/RobloxCS.Tests/GlobalUsings.cs ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ global using Xunit;
2
+ global using Microsoft.CodeAnalysis;
3
+ global using Microsoft.CodeAnalysis.CSharp;
4
+ global using Microsoft.CodeAnalysis.CSharp.Syntax;
roblox-cs-master/RobloxCS.Tests/MainTransformer_Should.cs ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ namespace RobloxCS.Tests
2
+ {
3
+ public class MainTransformer_Should
4
+ {
5
+ [Theory]
6
+ [InlineData("obj?.Name;")]
7
+ [InlineData("hello?.World;")]
8
+ [InlineData("a.b?.c;")]
9
+ public void SafeNavigation_TransformsWhenNotNull(string source)
10
+ {
11
+ var cleanTree = TranspilerUtility.ParseTree(source);
12
+ var transformedTree = TranspilerUtility.TransformTree(cleanTree, [BuiltInTransformers.Main()]);
13
+ var cleanRoot = cleanTree.GetRoot();
14
+ var cleanTernary = cleanRoot.DescendantNodes().OfType<ConditionalAccessExpressionSyntax>().First();
15
+ var transformedRoot = transformedTree.GetRoot();
16
+ var transformedTernary = transformedRoot.DescendantNodes().OfType<ConditionalAccessExpressionSyntax>().First();
17
+ Assert.True(cleanTernary.WhenNotNull.IsKind(SyntaxKind.MemberBindingExpression));
18
+ Assert.True(transformedTernary.WhenNotNull.IsKind(SyntaxKind.SimpleMemberAccessExpression));
19
+ }
20
+ }
21
+ }
roblox-cs-master/RobloxCS.Tests/RobloxCS.Tests.csproj ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <Project Sdk="Microsoft.NET.Sdk">
2
+
3
+ <PropertyGroup>
4
+ <TargetFramework>net8.0</TargetFramework>
5
+ <ImplicitUsings>enable</ImplicitUsings>
6
+ <Nullable>enable</Nullable>
7
+
8
+ <IsPackable>false</IsPackable>
9
+ <IsTestProject>true</IsTestProject>
10
+ </PropertyGroup>
11
+
12
+ <ItemGroup>
13
+ <ProjectReference Include="..\RobloxCS\RobloxCS.csproj" />
14
+ <PackageReference Include="Microsoft.CodeAnalysis" Version="4.10.0" />
15
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
16
+ <PackageReference Include="MSTest.TestAdapter" Version="3.0.4" />
17
+ <PackageReference Include="MSTest.TestFramework" Version="3.0.4" />
18
+ <PackageReference Include="coverlet.collector" Version="6.0.0" />
19
+ <PackageReference Include="xunit" Version="2.9.0" />
20
+ <PackageReference Include="xunit.analyzers" Version="1.15.0">
21
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
22
+ <PrivateAssets>all</PrivateAssets>
23
+ </PackageReference>
24
+ <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
25
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
26
+ <PrivateAssets>all</PrivateAssets>
27
+ </PackageReference>
28
+ </ItemGroup>
29
+
30
+ </Project>
roblox-cs-master/RobloxCS/CodeGenerator.cs ADDED
@@ -0,0 +1,2097 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using System.Text;
2
+ using System.Collections.Immutable;
3
+ using System.Text.RegularExpressions;
4
+ using Microsoft.CodeAnalysis;
5
+ using Microsoft.CodeAnalysis.CSharp;
6
+ using Microsoft.CodeAnalysis.CSharp.Syntax;
7
+ using System.Linq;
8
+
9
+ namespace RobloxCS
10
+ {
11
+ internal class CodeGeneratorFlags
12
+ {
13
+ public bool ShouldCallGetAssemblyType { get; set; }
14
+ public bool ClientEntryPointDefined { get; set; }
15
+ public bool ServerEntryPointDefined { get; set; }
16
+ }
17
+
18
+ internal sealed class CodeGenerator : CSharpSyntaxWalker
19
+ {
20
+ private readonly SyntaxTree _tree;
21
+ private readonly ConfigData _config;
22
+ private readonly MemberCollectionResult _members;
23
+ private readonly string _inputDirectory;
24
+ private readonly CSharpCompilation _compiler;
25
+ private readonly SemanticModel _semanticModel;
26
+ private readonly SymbolAnalyzerResults _symbolAnalysis;
27
+ private readonly INamespaceSymbol _globalNamespace;
28
+ private readonly INamespaceSymbol _runtimeLibNamespace;
29
+ private readonly RojoProject? _rojoProject;
30
+ private readonly int _indentSize;
31
+ private readonly CodeGeneratorFlags _flags = new CodeGeneratorFlags
32
+ {
33
+ ShouldCallGetAssemblyType = true,
34
+ ClientEntryPointDefined = false,
35
+ ServerEntryPointDefined = false
36
+ };
37
+
38
+ private readonly StringBuilder _output = new StringBuilder();
39
+ private int _indent = 0;
40
+
41
+ public CodeGenerator(
42
+ SyntaxTree tree,
43
+ CSharpCompilation compiler,
44
+ RojoProject? rojoProject,
45
+ MemberCollectionResult members,
46
+ ConfigData config,
47
+ string inputDirectory,
48
+ int indentSize = 4
49
+ )
50
+ {
51
+ _tree = tree;
52
+ _config = config;
53
+ _members = members;
54
+ _inputDirectory = inputDirectory;
55
+ _compiler = compiler;
56
+ _semanticModel = compiler.GetSemanticModel(tree);
57
+
58
+ var symbolAnalyzer = new SymbolAnalyzer(tree, _semanticModel);
59
+ _symbolAnalysis = symbolAnalyzer.Analyze();
60
+ _globalNamespace = compiler.GlobalNamespace;
61
+ _runtimeLibNamespace = _globalNamespace.GetNamespaceMembers().FirstOrDefault(ns => ns.Name == Utility.RuntimeAssemblyName)!;
62
+ _rojoProject = rojoProject;
63
+ _indentSize = indentSize;
64
+ }
65
+
66
+ public string GenerateLua()
67
+ {
68
+ WriteHeader();
69
+ Visit(_tree.GetRoot());
70
+ WriteFooter();
71
+ return _output.ToString().Trim();
72
+ }
73
+
74
+ private void WriteHeader()
75
+ {
76
+ Write($"local CS = ");
77
+ WriteRequire(GetLuaRuntimeLibPath());
78
+ WriteLine();
79
+
80
+ foreach (var (namespaceName, declaringFiles) in GetNamespaceFilePaths())
81
+ {
82
+ var symbol = _tree.GetRoot()
83
+ .DescendantNodes()
84
+ .Select(node => _semanticModel.GetTypeInfo(node).Type)
85
+ .OfType<INamespaceOrTypeSymbol>()
86
+ .Where(namespaceSymbol => namespaceSymbol.ContainingAssembly != null && namespaceSymbol.ContainingAssembly.Name == _config.CSharpOptions.AssemblyName)
87
+ .FirstOrDefault(symbol => symbol.Name == namespaceName);
88
+
89
+ var namespaceSymbol = symbol == null ? null : (symbol.IsNamespace ? symbol : symbol.ContainingNamespace);
90
+ if (namespaceSymbol == null || !_symbolAnalysis.TypeHasMemberUsedAsValue(namespaceSymbol))
91
+ {
92
+ continue;
93
+ }
94
+
95
+ WriteLine($"-- using {namespaceName};");
96
+ foreach (var csharpFilePath in declaringFiles)
97
+ {
98
+ WriteLine($"require({GetRequirePath(csharpFilePath)})");
99
+ }
100
+ WriteLine();
101
+ }
102
+ }
103
+
104
+ private void WriteFooter()
105
+ {
106
+ if (!_tree.FilePath.EndsWith(".client.cs") && !_tree.FilePath.EndsWith(".server.cs"))
107
+ {
108
+ WriteLine("return {}");
109
+ }
110
+ }
111
+
112
+ private string GetLuaRuntimeLibPath()
113
+ {
114
+ if (_rojoProject == null)
115
+ {
116
+ return $"\"{Utility.LuaRuntimeModuleName}\"";
117
+ }
118
+ else
119
+ {
120
+ return RojoReader.ResolveInstancePath(_rojoProject, $"include/{Utility.LuaRuntimeModuleName}.lua")!;
121
+ }
122
+ }
123
+
124
+ private string GetRequirePath(string longCSharpFilePath)
125
+ {
126
+ var inputDirectory = _inputDirectory.EndsWith('/') ? _inputDirectory : _inputDirectory + '/';
127
+ var csharpFilePath = longCSharpFilePath.Replace(inputDirectory, "").Replace(_config.SourceFolder, _config.OutputFolder);
128
+ if (csharpFilePath.StartsWith('/'))
129
+ {
130
+ csharpFilePath = csharpFilePath.Substring(1);
131
+ }
132
+
133
+ if (_rojoProject == null)
134
+ {
135
+ return "\"./" + csharpFilePath.Replace(".cs", "") + '"';
136
+ }
137
+ else
138
+ {
139
+ return RojoReader.ResolveInstancePath(_rojoProject, csharpFilePath)!;
140
+ }
141
+ }
142
+
143
+ private Dictionary<string, HashSet<string>> GetNamespaceFilePaths()
144
+ {
145
+ var globalNamespaceSymbols = _tree.GetRoot()
146
+ .DescendantNodes()
147
+ .Select(node => _semanticModel.GetTypeInfo(node).Type)
148
+ .OfType<INamedTypeSymbol>()
149
+ .Where(namespaceSymbol => namespaceSymbol.ContainingAssembly != null && namespaceSymbol.ContainingAssembly.Name == _config.CSharpOptions.AssemblyName);
150
+
151
+ return new Dictionary<string, HashSet<string>>(
152
+ Utility.FilterDuplicates(globalNamespaceSymbols, SymbolEqualityComparer.Default)
153
+ .OfType<INamedTypeSymbol>()
154
+ .Select(namespaceSymbol => KeyValuePair.Create(namespaceSymbol.Name, GetPathsForSymbolDeclarations(namespaceSymbol)))
155
+ .Where(pair => !string.IsNullOrEmpty(pair.Key) && !pair.Value.Contains(_tree.FilePath))
156
+ );
157
+ }
158
+
159
+ private HashSet<string> GetPathsForSymbolDeclarations(ISymbol symbol)
160
+ {
161
+ var filePaths = new HashSet<string>();
162
+ foreach (var syntaxReference in symbol.DeclaringSyntaxReferences)
163
+ {
164
+ var syntaxTree = syntaxReference.SyntaxTree;
165
+ var filePath = syntaxTree.FilePath;
166
+ if (!string.IsNullOrEmpty(filePath))
167
+ {
168
+ filePaths.Add(filePath);
169
+ }
170
+ }
171
+
172
+ return filePaths;
173
+ }
174
+
175
+ public override void VisitRefExpression(RefExpressionSyntax node)
176
+ {
177
+ Logger.UnsupportedError(node, "Refs");
178
+ }
179
+
180
+ public override void VisitUnsafeStatement(UnsafeStatementSyntax node)
181
+ {
182
+ Logger.UnsupportedError(node, "Unsafe contexts");
183
+ }
184
+
185
+ public override void VisitUsingStatement(UsingStatementSyntax node)
186
+ {
187
+ Logger.UnsupportedError(node, "Using statements");
188
+ }
189
+
190
+ public override void VisitAttribute(AttributeSyntax node)
191
+ {
192
+ if (GetName(node) == "Native")
193
+ {
194
+ WriteLine("@native");
195
+ }
196
+ }
197
+
198
+ public override void VisitAttributeList(AttributeListSyntax node)
199
+ {
200
+ base.VisitAttributeList(node);
201
+ }
202
+
203
+ public override void VisitLocalFunctionStatement(LocalFunctionStatementSyntax node)
204
+ {
205
+ foreach (var attributeList in node.AttributeLists)
206
+ {
207
+ Visit(attributeList);
208
+ }
209
+ Write($"local function {GetName(node)}");
210
+ Visit(node.ParameterList);
211
+ _indent++;
212
+
213
+ Visit(node.Body);
214
+ WriteDefaultReturn(node.Body);
215
+
216
+ _indent--;
217
+ WriteLine("end");
218
+ }
219
+
220
+ public override void VisitCastExpression(CastExpressionSyntax node)
221
+ {
222
+ // ignore
223
+ Visit(node.Expression);
224
+ }
225
+
226
+ public override void VisitThrowStatement(ThrowStatementSyntax node)
227
+ {
228
+ Throw(node.Expression);
229
+ }
230
+
231
+ public override void VisitThrowExpression(ThrowExpressionSyntax node)
232
+ {
233
+ Throw(node.Expression);
234
+ }
235
+
236
+ private void Throw(ExpressionSyntax? exception)
237
+ {
238
+ if (exception == null)
239
+ {
240
+ WriteLine("_catch_rethrow()");
241
+ }
242
+ else
243
+ {
244
+ Visit(exception);
245
+ Write(":Throw(");
246
+ Write((exception.Parent?.Parent is TryStatementSyntax).ToString().ToLower());
247
+ WriteLine(')');
248
+ }
249
+ }
250
+
251
+ public override void VisitTryStatement(TryStatementSyntax node)
252
+ {
253
+ WriteLine("CS.try(function()");
254
+ _indent++;
255
+
256
+ Visit(node.Block);
257
+
258
+ _indent--;
259
+ Write("end, ");
260
+ if (node.Finally != null)
261
+ {
262
+ WriteLine("function()");
263
+ _indent++;
264
+
265
+ Visit(node.Finally);
266
+
267
+ _indent--;
268
+ Write("end");
269
+ }
270
+ else
271
+ {
272
+ Write("nil");
273
+ }
274
+ WriteLine(", {");
275
+ _indent++;
276
+
277
+ foreach (var catchBlock in node.Catches)
278
+ {
279
+ WriteLine('{');
280
+ _indent++;
281
+
282
+ Write("exceptionClass = ");
283
+ if (catchBlock.Declaration != null)
284
+ {
285
+ Write('"');
286
+ Write(catchBlock.Declaration.Type.ToString());
287
+ Write('"');
288
+ WriteLine(", ");
289
+ }
290
+ Write("block = function(");
291
+ Write(catchBlock.Declaration != null ? (GetName(catchBlock.Declaration) + ": CS.Exception") : "_");
292
+ WriteLine(", _catch_rethrow: () -> nil)");
293
+ _indent++;
294
+
295
+ Visit(catchBlock.Block);
296
+
297
+ _indent--;
298
+ WriteLine("end");
299
+
300
+ _indent--;
301
+ WriteLine('}');
302
+ }
303
+
304
+ _indent--;
305
+ WriteLine("})");
306
+ }
307
+
308
+ public override void VisitForEachVariableStatement(ForEachVariableStatementSyntax node)
309
+ {
310
+ Visit(node.Variable);
311
+ }
312
+
313
+ public override void VisitForEachStatement(ForEachStatementSyntax node)
314
+ {
315
+ Write("for _, ");
316
+ Write(GetName(node));
317
+ Write(" in ");
318
+ Visit(node.Expression);
319
+ WriteLine(" do");
320
+ _indent++;
321
+
322
+ Visit(node.Statement);
323
+
324
+ _indent--;
325
+ WriteLine("end");
326
+ }
327
+
328
+ public override void VisitForStatement(ForStatementSyntax node)
329
+ {
330
+ var hasDeclaration = node.Declaration != null;
331
+ if (hasDeclaration)
332
+ {
333
+ WriteLine("do");
334
+ _indent++;
335
+ Visit(node.Declaration);
336
+ }
337
+ foreach (var initializer in node.Initializers)
338
+ {
339
+ Visit(initializer);
340
+ }
341
+ Write("while ");
342
+ if (node.Condition != null)
343
+ {
344
+ Visit(node.Condition);
345
+ }
346
+ else
347
+ {
348
+ Write("true");
349
+ }
350
+ WriteLine(" do");
351
+ _indent++;
352
+
353
+
354
+ Visit(node.Statement);
355
+ foreach (var incrementor in node.Incrementors)
356
+ {
357
+ Visit(incrementor);
358
+ }
359
+
360
+ _indent--;
361
+ WriteLine("end");
362
+
363
+ if (hasDeclaration)
364
+ {
365
+ _indent--;
366
+ WriteLine("end");
367
+ }
368
+ }
369
+
370
+ public override void VisitLabeledStatement(LabeledStatementSyntax node)
371
+ {
372
+ Logger.UnsupportedError(node, "Labels & goto statements");
373
+ }
374
+
375
+ public override void VisitGotoStatement(GotoStatementSyntax node)
376
+ {
377
+ Logger.UnsupportedError(node, "Labels & goto statements");
378
+ }
379
+
380
+ public override void VisitDoStatement(DoStatementSyntax node)
381
+ {
382
+ WriteLine("repeat");
383
+ _indent++;
384
+
385
+ Visit(node.Statement);
386
+
387
+ _indent--;
388
+ Write("until ");
389
+ Visit(node.Condition);
390
+ WriteLine();
391
+ }
392
+
393
+ public override void VisitWhileStatement(WhileStatementSyntax node)
394
+ {
395
+ Write("while ");
396
+ Visit(node.Condition);
397
+ WriteLine(" do");
398
+ _indent++;
399
+
400
+ Visit(node.Statement);
401
+
402
+ _indent--;
403
+ WriteLine("end");
404
+ }
405
+
406
+ public override void VisitIfStatement(IfStatementSyntax node)
407
+ {
408
+ Write("if ");
409
+ Visit(node.Condition);
410
+ WriteLine(" then");
411
+ _indent++;
412
+
413
+ WritePatternDeclarations(node.Condition);
414
+ Visit(node.Statement);
415
+
416
+ _indent--;
417
+ if (node.Else != null)
418
+ {
419
+ var isElseIf = node.Else.Statement.IsKind(SyntaxKind.IfStatement);
420
+
421
+ Write("else");
422
+ if (!isElseIf)
423
+ {
424
+ WriteLine();
425
+ _indent++;
426
+ }
427
+
428
+ Visit(node.Else);
429
+ if (!isElseIf)
430
+ {
431
+ _indent--;
432
+ WriteLine("end");
433
+ }
434
+ }
435
+ else
436
+ {
437
+ WriteLine("end");
438
+ }
439
+ }
440
+
441
+ public override void VisitSwitchStatement(SwitchStatementSyntax node)
442
+ {
443
+ var condition = node.Expression;
444
+ WriteLine("repeat");
445
+ _indent++;
446
+
447
+ var checkNoFallthrough = (StatementSyntax statement) =>
448
+ !statement.IsKind(SyntaxKind.BreakStatement)
449
+ && !statement.IsKind(SyntaxKind.ReturnStatement)
450
+ && !statement.DescendantNodes().All(descendant => !descendant.IsKind(SyntaxKind.BreakStatement) && !descendant.IsKind(SyntaxKind.ReturnStatement));
451
+
452
+ if (node.Sections.Count > 0 && !node.Sections.Any(section => section.Statements.Any(checkNoFallthrough))) // TODO: check for break/return
453
+ {
454
+ WriteLine("local _fallthrough = false");
455
+ }
456
+ foreach (var section in node.Sections)
457
+ {
458
+ var statementKinds = section.Statements.Select(stmt => stmt.Kind());
459
+ HashSet<SyntaxKind> supportedPatterns = [
460
+ SyntaxKind.VarPattern,
461
+ SyntaxKind.DeclarationPattern
462
+ ];
463
+
464
+ var caseLabels = section.Labels.Where(label =>
465
+ !label.IsKind(SyntaxKind.DefaultSwitchLabel)
466
+ && !(label is CasePatternSwitchLabelSyntax casePattern
467
+ && supportedPatterns.Contains(casePattern.Pattern.Kind()))
468
+ );
469
+
470
+ foreach (var label in caseLabels)
471
+ {
472
+ Write("if ");
473
+ if (label != caseLabels.FirstOrDefault())
474
+ {
475
+ Write("_fallthrough or ");
476
+ }
477
+
478
+ var expressions = label.ChildNodes();
479
+ var expression = expressions.First();
480
+ Write('(');
481
+ Visit(condition);
482
+
483
+ var op = expression.IsKind(SyntaxKind.NotPattern) ?
484
+ "~="
485
+ : expression is RelationalPatternSyntax relationalPattern ?
486
+ Utility.GetMappedOperator(relationalPattern.OperatorToken.Text)
487
+ : "==";
488
+
489
+ Write($" {op} ");
490
+ Visit(expression);
491
+ if (label is CasePatternSwitchLabelSyntax casePattern && casePattern.WhenClause != null)
492
+ {
493
+ Write(" and ");
494
+ Visit(casePattern.WhenClause.Condition);
495
+ }
496
+ WriteLine(") then");
497
+ _indent++;
498
+
499
+ if (label != caseLabels.Last())
500
+ {
501
+ WriteLine("_fallthrough = true");
502
+ }
503
+ else
504
+ {
505
+ foreach (var statement in section.Statements)
506
+ {
507
+ Visit(statement);
508
+ if (statement == section.Statements.Last() && !statement.IsKind(SyntaxKind.ReturnStatement))
509
+ {
510
+ WriteLine("break");
511
+ }
512
+ }
513
+ }
514
+
515
+ _indent--;
516
+ WriteLine("end");
517
+ }
518
+ }
519
+
520
+ void visitDefaultStatements(SwitchLabelSyntax defaultLabel)
521
+ {
522
+ var section = (SwitchSectionSyntax)defaultLabel.Parent!;
523
+ foreach (var statement in section.Statements)
524
+ {
525
+ Visit(statement);
526
+ }
527
+ }
528
+
529
+ var casePatternLabels = node.Sections.SelectMany(section => section.Labels.Where(label => label.IsKind(SyntaxKind.CasePatternSwitchLabel)));
530
+ foreach (var casePatternLabel in casePatternLabels)
531
+ {
532
+ var pattern = casePatternLabel.ChildNodes().FirstOrDefault();
533
+ if (pattern.IsKind(SyntaxKind.VarPattern) || pattern.IsKind(SyntaxKind.DeclarationPattern))
534
+ {
535
+ Visit(pattern);
536
+ Write(" = ");
537
+ Visit(condition);
538
+ WriteLine();
539
+ visitDefaultStatements(casePatternLabel);
540
+ }
541
+ }
542
+
543
+ var defaultLabel = node.Sections.SelectMany(section => section.Labels.Where(label => label.IsKind(SyntaxKind.DefaultSwitchLabel))).FirstOrDefault();
544
+ if (defaultLabel != null)
545
+ {
546
+ visitDefaultStatements(defaultLabel);
547
+ }
548
+
549
+ _indent--;
550
+ WriteLine("until true");
551
+ }
552
+
553
+ public override void VisitParenthesizedLambdaExpression(ParenthesizedLambdaExpressionSyntax node)
554
+ {
555
+ Write("function");
556
+ Visit(node.ParameterList);
557
+ WriteLine();
558
+ _indent++;
559
+
560
+ if (node.Block != null || node.ExpressionBody.IsKind(SyntaxKind.SimpleAssignmentExpression))
561
+ {
562
+ Visit(node.Body);
563
+ }
564
+ else
565
+ {
566
+ Write("return ");
567
+ Visit(node.ExpressionBody);
568
+ WriteLine();
569
+ }
570
+
571
+ _indent--;
572
+ Write("end");
573
+ }
574
+
575
+ public override void VisitSimpleLambdaExpression(SimpleLambdaExpressionSyntax node)
576
+ {
577
+ Write("function(");
578
+ Visit(node.Parameter);
579
+ Write(')');
580
+ WriteLine();
581
+ _indent++;
582
+
583
+ if (node.Block != null || node.ExpressionBody.IsKind(SyntaxKind.SimpleAssignmentExpression))
584
+ {
585
+ Visit(node.Body);
586
+ }
587
+ else
588
+ {
589
+ Write("return ");
590
+ Visit(node.ExpressionBody);
591
+ WriteLine();
592
+ }
593
+
594
+ _indent--;
595
+ Write("end");
596
+ }
597
+
598
+ public override void VisitBracketedArgumentList(BracketedArgumentListSyntax node)
599
+ {
600
+ Write('[');
601
+ if (node.Arguments.Count > 1)
602
+ {
603
+ Logger.CodegenError(node.Arguments.Last(), "Cannot have more than one argument between brackets.");
604
+ }
605
+
606
+ foreach (var argument in node.Arguments)
607
+ {
608
+ if (argument.Expression is LiteralExpressionSyntax numericLiteral && numericLiteral.IsKind(SyntaxKind.NumericLiteralExpression))
609
+ {
610
+ int.TryParse(numericLiteral.Token.ValueText, out var indexValue);
611
+ Write((indexValue + 1).ToString());
612
+ }
613
+ else
614
+ {
615
+ Visit(argument);
616
+
617
+ var typeSymbol = _semanticModel.GetTypeInfo(argument.Expression).Type;
618
+ if (typeSymbol != null && Constants.INTEGER_TYPES.Contains(typeSymbol.Name))
619
+ {
620
+ Write(" + 1");
621
+ }
622
+ }
623
+ }
624
+ Write(']');
625
+ }
626
+
627
+ public override void VisitConditionalAccessExpression(ConditionalAccessExpressionSyntax node)
628
+ {
629
+ Write("if ");
630
+ Visit(node.Expression);
631
+ Write(" == nil then nil else ");
632
+ Visit(node.WhenNotNull);
633
+ }
634
+
635
+ public override void VisitConditionalExpression(ConditionalExpressionSyntax node)
636
+ {
637
+ Write("if ");
638
+ Visit(node.Condition);
639
+ Write(" then ");
640
+ Visit(node.WhenTrue);
641
+ Write(" else ");
642
+ Visit(node.WhenFalse);
643
+ }
644
+
645
+ public override void VisitParenthesizedExpression(ParenthesizedExpressionSyntax node)
646
+ {
647
+ Write('(');
648
+ Visit(node.Expression);
649
+ Write(')');
650
+ }
651
+
652
+ public override void VisitPostfixUnaryExpression(PostfixUnaryExpressionSyntax node)
653
+ {
654
+ Visit(node.Operand);
655
+ if (node.IsKind(SyntaxKind.SuppressNullableWarningExpression))
656
+ {
657
+ var typeSymbol = _semanticModel.GetTypeInfo(node.Operand).Type;
658
+ if (typeSymbol == null)
659
+ {
660
+ Write(" :: any");
661
+ }
662
+ return;
663
+ }
664
+
665
+ var mappedOperator = Utility.GetMappedOperator(node.OperatorToken.Text);
666
+ WriteLine($" {mappedOperator} 1");
667
+ }
668
+
669
+ public override void VisitPrefixUnaryExpression(PrefixUnaryExpressionSyntax node)
670
+ {
671
+ var mappedOperator = Utility.GetMappedOperator(node.OperatorToken.Text);
672
+ var bit32Method = Utility.GetBit32MethodName(mappedOperator);
673
+ if (bit32Method != mappedOperator)
674
+ {
675
+ Write($"bit32.{bit32Method}(");
676
+ Visit(node.Operand);
677
+ Write(")");
678
+ return;
679
+ }
680
+
681
+ Write(mappedOperator);
682
+ Visit(node.Operand);
683
+ }
684
+
685
+ public override void VisitBinaryExpression(BinaryExpressionSyntax node)
686
+ {
687
+ var operatorText = node.OperatorToken.Text;
688
+ var leftType = _semanticModel.GetTypeInfo(node.Left).Type;
689
+ var rightType = _semanticModel.GetTypeInfo(node.Right).Type;
690
+ var perTypeOperators = Constants.PER_TYPE_BINARY_OPERATOR_MAP;
691
+ if (
692
+ (leftType != null && perTypeOperators.Any(pair => pair.Key.Contains(leftType.Name)))
693
+ || (rightType != null && perTypeOperators.Any(pair => pair.Key.Contains(rightType.Name)))
694
+ )
695
+ {
696
+ var typeList = perTypeOperators.FirstOrDefault(pair => pair.Key.Contains(leftType!.Name) || pair.Key.Contains(rightType!.Name)).Key;
697
+ var operatorMap = perTypeOperators[typeList];
698
+ if (operatorText == operatorMap.Item1)
699
+ {
700
+ operatorText = operatorMap.Item2;
701
+ }
702
+ }
703
+
704
+ if (Constants.IGNORED_BINARY_OPERATORS.Contains(operatorText))
705
+ {
706
+ Visit(node.Left);
707
+ return;
708
+ }
709
+
710
+ var mappedOperator = Utility.GetMappedOperator(operatorText);
711
+ switch (mappedOperator)
712
+ {
713
+ case "??":
714
+ Write("if ");
715
+ Visit(node.Left);
716
+ Write(" == nil then ");
717
+ Visit(node.Right);
718
+ Write(" else ");
719
+ Visit(node.Left);
720
+ return;
721
+ }
722
+
723
+ var bit32Method = Utility.GetBit32MethodName(mappedOperator);
724
+ if (bit32Method != mappedOperator)
725
+ {
726
+ Write($"bit32.{bit32Method}(");
727
+ Visit(node.Left);
728
+ Write(", ");
729
+ Visit(node.Right);
730
+ Write(")");
731
+
732
+ if ((leftType == null || rightType == null) || (!Constants.UNSUPPORTED_BITWISE_TYPES.Contains(leftType.Name) && !Constants.UNSUPPORTED_BITWISE_TYPES.Contains(rightType.Name)))
733
+ return;
734
+
735
+ Logger.CodegenWarning(node, "Using 128/64 bit integers when performing binary operations on Luau may result in undefined behaviour.");
736
+ WriteLine();
737
+ Write("--> rbxcsc warn: long/Int64 or Int128/UInt128 bit operations may lead to undefined behaviour (above this warning).");
738
+
739
+ return;
740
+ }
741
+
742
+ Visit(node.Left);
743
+ Write($" {mappedOperator} ");
744
+ Visit(node.Right);
745
+ }
746
+
747
+ public override void VisitTupleExpression(TupleExpressionSyntax node)
748
+ {
749
+ WriteListTable(node.Arguments.Select(arg => arg.Expression).ToList());
750
+ }
751
+
752
+ public override void VisitCollectionExpression(CollectionExpressionSyntax node)
753
+ {
754
+ Write('{');
755
+ foreach (var element in node.Elements)
756
+ {
757
+ var children = element.ChildNodes();
758
+ foreach (var child in children)
759
+ {
760
+ Visit(child);
761
+ }
762
+ if (element != node.Elements.Last())
763
+ {
764
+ Write(", ");
765
+ }
766
+ }
767
+ Write('}');
768
+ }
769
+
770
+ public override void VisitArrayCreationExpression(ArrayCreationExpressionSyntax node)
771
+ {
772
+ Visit(node.Initializer);
773
+ }
774
+
775
+ public override void VisitImplicitArrayCreationExpression(ImplicitArrayCreationExpressionSyntax node)
776
+ {
777
+ Visit(node.Initializer);
778
+ }
779
+
780
+ public override void VisitInitializerExpression(InitializerExpressionSyntax node)
781
+ {
782
+ WriteListTable(node.Expressions.ToList());
783
+ }
784
+
785
+ public override void VisitAnonymousObjectMemberDeclarator(AnonymousObjectMemberDeclaratorSyntax node)
786
+ {
787
+ if (node.NameEquals != null)
788
+ {
789
+ Write(GetName(node.NameEquals));
790
+ Write(" = ");
791
+ }
792
+ Visit(node.Expression);
793
+ }
794
+
795
+ public override void VisitAnonymousObjectCreationExpression(AnonymousObjectCreationExpressionSyntax node)
796
+ {
797
+ WriteLine('{');
798
+ _indent++;
799
+
800
+ foreach (var initializer in node.Initializers)
801
+ {
802
+ Visit(initializer);
803
+ if (initializer != node.Initializers.Last())
804
+ {
805
+ Write(", ");
806
+ }
807
+ WriteLine();
808
+ }
809
+
810
+ _indent--;
811
+ WriteLine('}');
812
+ }
813
+
814
+ public override void VisitObjectCreationExpression(ObjectCreationExpressionSyntax node)
815
+ {
816
+ Visit(node.Type);
817
+ Write(".new");
818
+ Visit(node.ArgumentList);
819
+ }
820
+
821
+ public override void VisitUsingDirective(UsingDirectiveSyntax node)
822
+ {
823
+ // do nothing (for now)
824
+ }
825
+
826
+ public override void VisitContinueStatement(ContinueStatementSyntax node)
827
+ {
828
+ Write("continue");
829
+ }
830
+
831
+ public override void VisitReturnStatement(ReturnStatementSyntax node)
832
+ {
833
+ Write("return ");
834
+ Visit(node.Expression);
835
+ WriteLine();
836
+ }
837
+
838
+ public override void VisitExpressionStatement(ExpressionStatementSyntax node)
839
+ {
840
+ base.VisitExpressionStatement(node);
841
+ WriteLine();
842
+ }
843
+
844
+ public override void VisitArgumentList(ArgumentListSyntax node)
845
+ {
846
+ Write('(');
847
+ foreach (var argument in node.Arguments)
848
+ {
849
+ Visit(argument);
850
+ if (argument != node.Arguments.Last())
851
+ {
852
+ Write(", ");
853
+ }
854
+ }
855
+ Write(')');
856
+ }
857
+
858
+ public override void VisitInvocationExpression(InvocationExpressionSyntax node)
859
+ {
860
+
861
+ if (node.Expression is MemberAccessExpressionSyntax memberAccess)
862
+ {
863
+ var objectType = _semanticModel.GetTypeInfo(memberAccess.Expression).Type;
864
+ var name = GetName(memberAccess.Name);
865
+
866
+ switch (name)
867
+ {
868
+ case "ToString":
869
+ Write("tostring(");
870
+ Visit(memberAccess.Expression);
871
+ Write(')');
872
+ return;
873
+ case "Equals":
874
+ Visit(memberAccess.Expression);
875
+ Write(" == ");
876
+
877
+ var value = node.ArgumentList.Arguments.FirstOrDefault();
878
+ if (value != null)
879
+ {
880
+ Visit(value);
881
+ }
882
+ else
883
+ {
884
+ Write("nil");
885
+ }
886
+ return;
887
+ case "Length":
888
+ if (objectType == null || !Constants.LENGTH_READABLE_TYPES.Contains(objectType.Name)) return;
889
+ Write('#');
890
+ Visit(memberAccess.Expression);
891
+ return;
892
+ case "ToLower":
893
+ case "ToUpper":
894
+ case "Replace":
895
+ case "Split":
896
+ if (objectType == null || objectType.Name.ToLower() != "string") return;
897
+ Write('(');
898
+ Visit(memberAccess.Expression);
899
+ Write(')');
900
+ Write(':');
901
+
902
+ var methodName = GetName(memberAccess.Name);
903
+ var mappedMethodName = Constants.MAPPED_STRING_METHODS.GetValueOrDefault(methodName, methodName);
904
+ Write(mappedMethodName);
905
+ Visit(node.ArgumentList);
906
+ return;
907
+ case "Create":
908
+ {
909
+ if (objectType == null || objectType.Name != "Instance") break;
910
+
911
+ var symbol = _semanticModel.GetSymbolInfo(node).Symbol;
912
+ if (symbol is IMethodSymbol methodSymbol)
913
+ {
914
+ if (!methodSymbol.IsGenericMethod)
915
+ Logger.CompilerError("Attempt to macro Instance.Create<T>() but it is not generic");
916
+
917
+ var arguments = node.ArgumentList.Arguments;
918
+ var instanceType = methodSymbol.TypeArguments.First();
919
+ Visit(memberAccess.Expression);
920
+ Write($".new(\"{instanceType.Name}\"");
921
+ if (arguments.Count > 0)
922
+ {
923
+ Write(", ");
924
+ foreach (var argument in arguments)
925
+ {
926
+ Visit(argument.Expression);
927
+ if (argument != arguments.Last())
928
+ {
929
+ Write(", ");
930
+ }
931
+ }
932
+ }
933
+
934
+ Write(')');
935
+ return;
936
+ }
937
+ break;
938
+ }
939
+ case "GetService":
940
+ case "FindFirstChildOfClass":
941
+ case "FindFirstChildWhichIsA":
942
+ case "FindFirstAncestorOfClass":
943
+ case "FindFirstAncestorWhichIsA":
944
+ case "IsA":
945
+ {
946
+ if (objectType == null) return;
947
+
948
+ var superclasses = objectType.AllInterfaces.ToList();
949
+ if (objectType.Name != "Instance" && !superclasses.Select(@interface => @interface.Name).Contains("Instance")) return;
950
+
951
+ var symbol = _semanticModel.GetSymbolInfo(node).Symbol;
952
+ var methodSymbol = (IMethodSymbol)symbol!;
953
+ if (!methodSymbol.IsGenericMethod)
954
+ Logger.CompilerError($"Attempt to macro {objectType.Name}.{name}<T>() but it is not generic");
955
+
956
+ var arguments = node.ArgumentList.Arguments;
957
+ var instanceType = methodSymbol.TypeArguments.First();
958
+ Visit(memberAccess.Expression);
959
+ Write($":{name}(\"{instanceType.Name}\"");
960
+ if (arguments.Count > 0)
961
+ {
962
+ Write(", ");
963
+ foreach (var argument in arguments)
964
+ {
965
+ Visit(argument.Expression);
966
+ if (argument != arguments.Last())
967
+ {
968
+ Write(", ");
969
+ }
970
+ }
971
+ }
972
+ else
973
+ {
974
+ Write(')');
975
+ }
976
+ return;
977
+ }
978
+ }
979
+ }
980
+ else if (node.Expression is IdentifierNameSyntax || node.Expression is GenericNameSyntax)
981
+ {
982
+ var name = GetName(node.Expression);
983
+ switch (name)
984
+ {
985
+ case "TypeOf":
986
+ Write("typeof");
987
+ Visit(node.ArgumentList);
988
+ return;
989
+
990
+ case "ToNumber":
991
+ case "ToFloat":
992
+ case "ToDouble":
993
+ case "ToInt":
994
+ case "ToUInt":
995
+ case "ToShort":
996
+ case "ToUShort":
997
+ case "ToByte":
998
+ case "ToSByte":
999
+ Write("tonumber");
1000
+ Visit(node.ArgumentList);
1001
+ return;
1002
+
1003
+ case "nameof":
1004
+ Write('"');
1005
+ Write(_semanticModel.GetConstantValue(node).Value?.ToString() ?? $"??? nameof({node}) ???");
1006
+ Write('"');
1007
+ return;
1008
+ }
1009
+ }
1010
+
1011
+ Visit(node.Expression);
1012
+ Visit(node.ArgumentList);
1013
+ }
1014
+
1015
+ public override void VisitThisExpression(ThisExpressionSyntax node)
1016
+ {
1017
+ Write("self");
1018
+ }
1019
+
1020
+ public override void VisitMemberAccessExpression(MemberAccessExpressionSyntax node)
1021
+ {
1022
+ var leftIsLiteral = node.Expression is LiteralExpressionSyntax;
1023
+ var objectSymbol = _semanticModel.GetSymbolInfo(node.Expression).Symbol?.OriginalDefinition;
1024
+ var objectType = _semanticModel.GetTypeInfo(node.Expression).Type;
1025
+ var nameType = _semanticModel.GetSymbolInfo(node.Name).Symbol?.OriginalDefinition;
1026
+ var operatorText = '.';
1027
+ if (objectSymbol != null)
1028
+ {
1029
+ if (objectType is INamedTypeSymbol objectDefinitionSymbol && (objectDefinitionSymbol.Name == "Services" || objectDefinitionSymbol.AllInterfaces.Select(@interface => @interface.Name).Contains("Services")))
1030
+ {
1031
+ Write("game:GetService(\"");
1032
+ Visit(node.Name);
1033
+ Write("\")");
1034
+ return;
1035
+ }
1036
+
1037
+ if (nameType is IMethodSymbol methodSymbol)
1038
+ {
1039
+ operatorText = methodSymbol.IsStatic ? '.' : ':';
1040
+ }
1041
+ }
1042
+
1043
+ var usings = GetUsings();
1044
+ var containingNamespace = objectSymbol?.ContainingNamespace;
1045
+ if (objectSymbol != null && (objectSymbol.Kind == SymbolKind.Namespace || (objectSymbol.Kind == SymbolKind.NamedType && objectSymbol.IsStatic)))
1046
+ {
1047
+ var namespaceName = objectSymbol.ToDisplayString();
1048
+ var filePathsContainingType = objectSymbol.Locations
1049
+ .Where(location => location.SourceTree != null && location.SourceTree.FilePath != _tree.FilePath)
1050
+ .Select(location => location.SourceTree!.FilePath);
1051
+
1052
+ var isNoFullQualificationType = Constants.NO_FULL_QUALIFICATION_TYPES.Contains(namespaceName) || (containingNamespace != null ? Constants.NO_FULL_QUALIFICATION_TYPES.Contains(containingNamespace.Name) : false);
1053
+ var noFullQualification = isNoFullQualificationType && (objectType == null || !Constants.GLOBAL_LIBRARIES.Contains(objectType.Name));
1054
+ var typeIsImported = usings.Any(usingDirective => usingDirective.Name != null && Utility.GetNamesFromNode(usingDirective).Any(name => namespaceName.StartsWith(name)));
1055
+ if (noFullQualification && namespaceName != "System")
1056
+ {
1057
+ if (node.Expression is IdentifierNameSyntax identifier)
1058
+ {
1059
+ // TODO: check parent classes of parent class
1060
+ var parentClass = FindFirstAncestor<ClassDeclarationSyntax>(node);
1061
+ var parentClassSymbol = parentClass != null ? _semanticModel.GetDeclaredSymbol(parentClass) : null;
1062
+ if (parentClass != null && SymbolEqualityComparer.Default.Equals(objectType, parentClassSymbol))
1063
+ {
1064
+ Write("class");
1065
+ }
1066
+ else
1067
+ {
1068
+ if (GetName(node.Name) == "Globals") return;
1069
+ Visit(node.Name);
1070
+ }
1071
+ }
1072
+ else if (node.Expression is GenericNameSyntax genericName)
1073
+ {
1074
+ Visit(genericName);
1075
+ }
1076
+ else if (node.Expression is MemberAccessExpressionSyntax memberAccess)
1077
+ {
1078
+ if (namespaceName == Utility.RuntimeAssemblyName && Utility.GetNamesFromNode(memberAccess.Expression).LastOrDefault() != "Globals")
1079
+ {
1080
+ Visit(memberAccess.Name);
1081
+ Write('.');
1082
+ }
1083
+ Visit(node.Name);
1084
+ }
1085
+ else
1086
+ {
1087
+ throw new NotSupportedException("Unsupported node.Expression type for a NO_FULL_QUALIFICATION_TYPES member");
1088
+ }
1089
+ return;
1090
+ }
1091
+ }
1092
+
1093
+ if (objectSymbol?.OriginalDefinition is ILocalSymbol typeSymbol)
1094
+ {
1095
+ switch (typeSymbol.Type.Name)
1096
+ {
1097
+ case "ValueTuple":
1098
+ var name = GetName(node.Name);
1099
+ if (name.StartsWith("Item"))
1100
+ {
1101
+ var itemIndex = name.Split("Item").Last();
1102
+ if (leftIsLiteral)
1103
+ {
1104
+ Write('(');
1105
+ }
1106
+ Visit(node.Expression);
1107
+ if (leftIsLiteral)
1108
+ {
1109
+ Write(')');
1110
+ }
1111
+ Write($"[{itemIndex}]");
1112
+ return;
1113
+ }
1114
+ break;
1115
+ }
1116
+ }
1117
+
1118
+ if (leftIsLiteral)
1119
+ {
1120
+ Write('(');
1121
+ }
1122
+ Visit(node.Expression);
1123
+ if (leftIsLiteral)
1124
+ {
1125
+ Write(')');
1126
+ }
1127
+ Write(operatorText);
1128
+ Visit(node.Name);
1129
+ }
1130
+
1131
+ private List<UsingDirectiveSyntax> GetUsings()
1132
+ {
1133
+ var root = _tree.GetRoot();
1134
+ var usings = root.DescendantNodes().OfType<UsingDirectiveSyntax>().ToList();
1135
+ var compilationUnit = root as CompilationUnitSyntax;
1136
+ if (compilationUnit != null)
1137
+ {
1138
+ foreach (var usingDirective in compilationUnit.Usings)
1139
+ {
1140
+ usings.Add(usingDirective);
1141
+ }
1142
+ }
1143
+
1144
+ return usings;
1145
+ }
1146
+
1147
+ private void FullyQualifyMemberAccess(INamespaceSymbol? namespaceType, List<UsingDirectiveSyntax> usings)
1148
+ {
1149
+ if (namespaceType == null) return;
1150
+
1151
+ var typeIsImported = usings.Any(usingDirective => namespaceType.ContainingNamespace != null && usingDirective.Name != null && Utility.GetNamesFromNode(usingDirective).Contains(namespaceType.ContainingNamespace.Name));
1152
+ Write($"CS.getAssemblyType(\"{namespaceType.Name}\").");
1153
+ _flags.ShouldCallGetAssemblyType = false;
1154
+ }
1155
+
1156
+ public override void VisitIsPatternExpression(IsPatternExpressionSyntax node)
1157
+ {
1158
+ var objectType = _semanticModel.GetTypeInfo(node.Expression).Type;
1159
+ var superclasses = objectType == null ? [] : objectType.AllInterfaces.ToList();
1160
+ var pattern = node.Pattern;
1161
+
1162
+ void writeTypePattern(TypeSyntax type)
1163
+ {
1164
+ var typeSymbol = _semanticModel.GetTypeInfo(type).Type;
1165
+ var mappedType = Utility.GetMappedType(type.ToString());
1166
+ HashSet<TypeKind> valueTypes = [TypeKind.Class, TypeKind.Struct, TypeKind.Enum];
1167
+ var isValueType = typeSymbol != null && valueTypes.Contains(typeSymbol.TypeKind);
1168
+ if (!isValueType)
1169
+ {
1170
+ Write('"');
1171
+ }
1172
+ Write(mappedType);
1173
+ if (!isValueType)
1174
+ {
1175
+ Write('"');
1176
+ }
1177
+ }
1178
+
1179
+ (bool, Action) getPatternWriter()
1180
+ {
1181
+ if (pattern is TypePatternSyntax typePattern)
1182
+ {
1183
+ return (true, () => writeTypePattern(typePattern.Type));
1184
+ }
1185
+ else if (pattern is DeclarationPatternSyntax declarationPattern)
1186
+ {
1187
+ return (true, () => writeTypePattern(declarationPattern.Type));
1188
+ }
1189
+ else if (pattern is VarPatternSyntax varPattern)
1190
+ {
1191
+ return (true, () => Write("true"));
1192
+ }
1193
+ else
1194
+ {
1195
+ return (false, () => Visit(pattern));
1196
+ }
1197
+ }
1198
+
1199
+ var (willBeHandled, writePattern) = getPatternWriter();
1200
+ if (!willBeHandled && pattern.IsKind(SyntaxKind.NotPattern))
1201
+ {
1202
+ Write("not ");
1203
+ }
1204
+ Write("CS.is(");
1205
+ Visit(node.Expression);
1206
+ Write(", ");
1207
+ writePattern();
1208
+ Write(')');
1209
+ }
1210
+
1211
+ public override void VisitDeclarationPattern(DeclarationPatternSyntax node)
1212
+ {
1213
+ Visit(node.Designation);
1214
+ WriteTypeAnnotation(node.Type);
1215
+ }
1216
+
1217
+ public override void VisitDiscardDesignation(DiscardDesignationSyntax node)
1218
+ {
1219
+ // do nothing (discard)
1220
+ }
1221
+
1222
+ public override void VisitSingleVariableDesignation(SingleVariableDesignationSyntax node)
1223
+ {
1224
+ Write($"local {GetName(node)}");
1225
+ }
1226
+
1227
+ public override void VisitLocalDeclarationStatement(LocalDeclarationStatementSyntax node)
1228
+ {
1229
+ Visit(node.Declaration);
1230
+ }
1231
+
1232
+ public override void VisitVariableDeclaration(VariableDeclarationSyntax node)
1233
+ {
1234
+ foreach (var declarator in node.Variables)
1235
+ {
1236
+ Visit(declarator);
1237
+ }
1238
+ }
1239
+
1240
+ public override void VisitVariableDeclarator(VariableDeclaratorSyntax node)
1241
+ {
1242
+ var localDeclaration = FindFirstAncestor<LocalDeclarationStatementSyntax>(node);
1243
+ var classDeclaration = FindFirstAncestor<ClassDeclarationSyntax>(node);
1244
+ if (
1245
+ localDeclaration != null
1246
+ && classDeclaration != null
1247
+ && !IsDescendantOf<MethodDeclarationSyntax>(node)
1248
+ && !IsDescendantOf<PropertyDeclarationSyntax>(node))
1249
+ {
1250
+ var isStatic = HasSyntax(classDeclaration.Modifiers, SyntaxKind.StaticKeyword) || HasSyntax(localDeclaration.Modifiers, SyntaxKind.StaticKeyword);
1251
+ Write(isStatic ? "class" : "self");
1252
+ Write(".");
1253
+ }
1254
+ else
1255
+ {
1256
+ Write("local ");
1257
+ }
1258
+
1259
+ Write($"{GetName(node)}");
1260
+ var parent = node.Parent;
1261
+ if (parent is VariableDeclarationSyntax declaration)
1262
+ {
1263
+ WriteTypeAnnotation(declaration.Type);
1264
+ }
1265
+
1266
+ if (node.Initializer != null)
1267
+ {
1268
+ Write(" = ");
1269
+ Visit(node.Initializer);
1270
+ if (node.Initializer.Value.IsKind(SyntaxKind.IsPatternExpression))
1271
+ {
1272
+ WriteLine();
1273
+ }
1274
+ WritePatternDeclarations(node.Initializer.Value);
1275
+ }
1276
+ WriteLine();
1277
+ }
1278
+
1279
+ public override void VisitAssignmentExpression(AssignmentExpressionSyntax node)
1280
+ {
1281
+ Visit(node.Left);
1282
+ Write(" = ");
1283
+ Visit(node.Right);
1284
+ if (node.Right.IsKind(SyntaxKind.IsPatternExpression))
1285
+ {
1286
+ WriteLine();
1287
+ }
1288
+ WritePatternDeclarations(node.Right);
1289
+ }
1290
+
1291
+ public override void VisitGenericName(GenericNameSyntax node)
1292
+ {
1293
+ WriteName(node, node.Identifier);
1294
+ }
1295
+
1296
+ public override void VisitIdentifierName(IdentifierNameSyntax node)
1297
+ {
1298
+ WriteName(node, node.Identifier);
1299
+ }
1300
+
1301
+ public override void VisitInterpolatedStringText(InterpolatedStringTextSyntax node)
1302
+ {
1303
+ Write(node.TextToken.Text);
1304
+ }
1305
+
1306
+ public override void VisitInterpolation(InterpolationSyntax node)
1307
+ {
1308
+ Write('{');
1309
+ Visit(node.Expression);
1310
+ Write('}');
1311
+ }
1312
+
1313
+ public override void VisitInterpolatedStringExpression(InterpolatedStringExpressionSyntax node)
1314
+ {
1315
+ Write('`');
1316
+ foreach (var content in node.Contents)
1317
+ {
1318
+ Visit(content);
1319
+ }
1320
+ Write('`');
1321
+ }
1322
+
1323
+ public override void VisitLiteralExpression(LiteralExpressionSyntax node)
1324
+ {
1325
+ switch (node.Kind())
1326
+ {
1327
+ case SyntaxKind.StringLiteralExpression:
1328
+ case SyntaxKind.Utf8StringLiteralExpression:
1329
+ case SyntaxKind.CharacterLiteralExpression:
1330
+ Write($"\"{node.Token.ValueText}\"");
1331
+ break;
1332
+
1333
+ case SyntaxKind.TrueLiteralExpression:
1334
+ case SyntaxKind.FalseLiteralExpression:
1335
+ case SyntaxKind.NumericLiteralExpression:
1336
+ Write(node.Token.ValueText);
1337
+ break;
1338
+
1339
+ case SyntaxKind.DefaultLiteralExpression:
1340
+ var typeSymbol = _semanticModel.GetTypeInfo(node).Type;
1341
+ if (typeSymbol == null) break;
1342
+
1343
+ if (Constants.INTEGER_TYPES.Contains(typeSymbol.Name) || Constants.DECIMAL_TYPES.Contains(typeSymbol.Name))
1344
+ {
1345
+ Write("0");
1346
+ break;
1347
+ }
1348
+
1349
+ switch (typeSymbol.Name)
1350
+ {
1351
+ case "char":
1352
+ case "Char":
1353
+ case "string":
1354
+ case "String":
1355
+ Write("\"\"");
1356
+ break;
1357
+ case "bool":
1358
+ case "Boolean":
1359
+ Write("false");
1360
+ break;
1361
+ default:
1362
+ Write("nil");
1363
+ break;
1364
+ }
1365
+ break;
1366
+ case SyntaxKind.NullLiteralExpression:
1367
+ Write("nil");
1368
+ break;
1369
+ }
1370
+
1371
+ base.VisitLiteralExpression(node);
1372
+ }
1373
+
1374
+ public override void VisitParameter(ParameterSyntax node)
1375
+ {
1376
+ if (node.Modifiers.Any(modifier => modifier.IsKind(SyntaxKind.ParamsKeyword)))
1377
+ {
1378
+ Write("...");
1379
+ if (node.Type != null)
1380
+ WriteTypeAnnotation(node.Type);
1381
+ return;
1382
+ }
1383
+ Write(GetName(node));
1384
+ if (node.Type != null)
1385
+ {
1386
+ WriteTypeAnnotation(node.Type);
1387
+ }
1388
+ }
1389
+
1390
+ public override void VisitParameterList(ParameterListSyntax node)
1391
+ {
1392
+ var callable = node.Parent;
1393
+ Write('(');
1394
+ {
1395
+ if (callable is MethodDeclarationSyntax method && !HasSyntax(method.Modifiers, SyntaxKind.StaticKeyword))
1396
+ {
1397
+ Write('_');
1398
+ if (node.Parameters.Count > 0)
1399
+ {
1400
+ Write(", ");
1401
+ }
1402
+ }
1403
+ }
1404
+ ParameterSyntax? vaargParameter = null;
1405
+ foreach (var parameter in node.Parameters)
1406
+ {
1407
+ Visit(parameter);
1408
+ if (parameter != node.Parameters.Last())
1409
+ {
1410
+ Write(", ");
1411
+ }
1412
+ else
1413
+ {
1414
+ if (parameter.Modifiers.Any(modifier => modifier.IsKind(SyntaxKind.ParamsKeyword))) vaargParameter = parameter;
1415
+ }
1416
+ }
1417
+ Write(')');
1418
+ switch (callable)
1419
+ {
1420
+ case ConstructorDeclarationSyntax constructor:
1421
+ WriteLine($": {GetName(constructor)}");
1422
+ break;
1423
+ case MethodDeclarationSyntax method:
1424
+ WriteTypeAnnotation(method.ReturnType, true);
1425
+ break;
1426
+ case LocalFunctionStatementSyntax localFunction:
1427
+ WriteTypeAnnotation(localFunction.ReturnType, true);
1428
+ break;
1429
+ default:
1430
+ WriteLine();
1431
+ break;
1432
+ }
1433
+ _indent++;
1434
+
1435
+ if (vaargParameter != null)
1436
+ {
1437
+ WriteLine("--> rbxcsc: vararg parameter conversion");
1438
+ Write($"local {GetName(vaargParameter)} = {{ ... }}");
1439
+ WriteLine();
1440
+ }
1441
+
1442
+
1443
+ foreach (var parameter in node.Parameters)
1444
+ {
1445
+ if (parameter.Default == null) continue;
1446
+
1447
+ var name = GetName(parameter);
1448
+ Write(name);
1449
+ Write(" = ");
1450
+ Write($"if {name} == nil then ");
1451
+ Visit(parameter.Default);
1452
+ WriteLine($" else {name}");
1453
+ }
1454
+
1455
+ _indent--;
1456
+ }
1457
+
1458
+ public override void VisitNamespaceDeclaration(NamespaceDeclarationSyntax node)
1459
+ {
1460
+ var isWithinNamespace = IsDescendantOf<NamespaceDeclarationSyntax>(node);
1461
+ var allNames = Utility.GetNamesFromNode(node);
1462
+ var firstName = allNames.First();
1463
+ allNames.Remove(firstName);
1464
+
1465
+ var nativeAttribute = _config.EmitNativeAttributeOnClassOrNamespaceCallbacks ? "@native " : "";
1466
+ WriteLine($"{(isWithinNamespace ? "namespace:" : "CS.")}namespace(\"{firstName}\", {nativeAttribute}function(namespace: CS.Namespace)");
1467
+ _indent++;
1468
+
1469
+ if (allNames.Count > 0)
1470
+ {
1471
+ foreach (var name in allNames)
1472
+ {
1473
+ WriteLine($"namespace:namespace(\"{name}\", {nativeAttribute}function(namespace: CS.Namespace)");
1474
+ _indent++;
1475
+
1476
+ foreach (var member in node.Members)
1477
+ {
1478
+ Visit(member);
1479
+ }
1480
+
1481
+ _indent--;
1482
+ WriteLine("end)");
1483
+ }
1484
+ }
1485
+ else
1486
+ {
1487
+ foreach (var member in node.Members)
1488
+ {
1489
+ Visit(member);
1490
+ }
1491
+ }
1492
+
1493
+ _indent--;
1494
+ WriteLine("end)");
1495
+ WriteLine();
1496
+ }
1497
+
1498
+ public override void VisitEnumDeclaration(EnumDeclarationSyntax node)
1499
+ {
1500
+ var isWithinNamespace = IsDescendantOf<NamespaceDeclarationSyntax>(node);
1501
+ Write($"CS.enum(\"{GetName(node)}\", {{");
1502
+ if (node.Members.Count > 0)
1503
+ {
1504
+ WriteLine();
1505
+ _indent++;
1506
+
1507
+ var firstValue = node.Members.FirstOrDefault()?.EqualsValue?.Value;
1508
+ var lastIndex = firstValue != null ? (firstValue as LiteralExpressionSyntax)?.Token.Value as int? ?? -1 : -1;
1509
+ foreach (var enumMember in node.Members)
1510
+ {
1511
+ Write(GetName(enumMember));
1512
+ Write(" = ");
1513
+ if (enumMember.EqualsValue != null)
1514
+ {
1515
+ Visit(enumMember.EqualsValue);
1516
+ lastIndex = (int)((LiteralExpressionSyntax)enumMember.EqualsValue.Value).Token.Value!;
1517
+ }
1518
+ else
1519
+ {
1520
+ var index = (enumMember.EqualsValue?.Value is LiteralExpressionSyntax literal ? literal.Token.Value as int? : null) ?? lastIndex + 1;
1521
+ lastIndex = index;
1522
+ Write(index.ToString());
1523
+ }
1524
+ WriteLine(enumMember != node.Members.Last() ? ", " : "");
1525
+ }
1526
+
1527
+ _indent--;
1528
+ }
1529
+ WriteLine($"}}, {(isWithinNamespace ? "namespace" : "nil")})");
1530
+ }
1531
+
1532
+ public override void VisitInterfaceDeclaration(InterfaceDeclarationSyntax node)
1533
+ {
1534
+ foreach (var member in node.Members)
1535
+ {
1536
+ if (HasSyntax(member.Modifiers, SyntaxKind.VirtualKeyword))
1537
+ {
1538
+ Logger.UnsupportedError(node, "Virtual methods on interfaces");
1539
+ }
1540
+ }
1541
+ }
1542
+
1543
+ public override void VisitClassDeclaration(ClassDeclarationSyntax node)
1544
+ {
1545
+ if (HasSyntax(node.Modifiers, SyntaxKind.AbstractKeyword))
1546
+ {
1547
+ Logger.UnsupportedError(node, "Abstract classes");
1548
+ return;
1549
+ }
1550
+ if (HasSyntax(node.Modifiers, SyntaxKind.PartialKeyword))
1551
+ {
1552
+ Logger.UnsupportedError(node, "Partial classes");
1553
+ return;
1554
+ }
1555
+
1556
+ var isWithinNamespace = IsDescendantOf<NamespaceDeclarationSyntax>(node);
1557
+ var isStatic = HasSyntax(node.Modifiers, SyntaxKind.StaticKeyword);
1558
+ var className = GetName(node);
1559
+ var nativeAttribute = _config.EmitNativeAttributeOnClassOrNamespaceCallbacks ? "@native " : "";
1560
+ WriteLine($"{(isWithinNamespace ? "namespace:" : "CS.")}class(\"{className}\", {nativeAttribute}function(namespace: CS.Namespace)");
1561
+ _indent++;
1562
+
1563
+ Write($"local class = CS.classDef(\"{GetName(node)}\", ");
1564
+ Write(isWithinNamespace ? "namespace" : "nil");
1565
+
1566
+ // TODO: check if superclass or mixin
1567
+ var ancestorIdentifiers = (node.BaseList?.Types ?? []).Select(ancestor =>
1568
+ {
1569
+ var typeSymbol = _semanticModel.GetTypeInfo(ancestor.Type).Type;
1570
+ return $"\"{Regex.Replace(typeSymbol?.ToString() ?? ancestor.Type.ToString(), @"<[^>]*>", "")}\"";
1571
+ });
1572
+ if (ancestorIdentifiers.Count() > 0)
1573
+ {
1574
+ Write(", ");
1575
+ }
1576
+ Write(string.Join(", ", ancestorIdentifiers));
1577
+ WriteLine(')');
1578
+ WriteLine();
1579
+ InitializeFields(
1580
+ node.Members
1581
+ .OfType<FieldDeclarationSyntax>()
1582
+ .Where(member => isStatic || HasSyntax(member.Modifiers, SyntaxKind.StaticKeyword))
1583
+ );
1584
+ InitializeProperties(
1585
+ node.Members
1586
+ .OfType<PropertyDeclarationSyntax>()
1587
+ .Where(member => isStatic || HasSyntax(member.Modifiers, SyntaxKind.StaticKeyword))
1588
+ );
1589
+
1590
+ var constructors = node.Members.OfType<ConstructorDeclarationSyntax>().ToList();
1591
+ constructors.Sort((a, b) => a.ParameterList.Parameters.Count - b.ParameterList.Parameters.Count);
1592
+
1593
+ var constructor = constructors.FirstOrDefault();
1594
+ if (!isStatic)
1595
+ {
1596
+ if (constructor == null)
1597
+ {
1598
+ CreateDefaultConstructor(node);
1599
+ }
1600
+ else
1601
+ {
1602
+ VisitConstructorDeclaration(constructor);
1603
+ }
1604
+ }
1605
+
1606
+ var methods = node.Members.OfType<MethodDeclarationSyntax>();
1607
+ var staticMethods = methods.Where(method => HasSyntax(method.Modifiers, SyntaxKind.StaticKeyword));
1608
+ foreach (var method in staticMethods)
1609
+ {
1610
+ Visit(method);
1611
+ }
1612
+
1613
+ var isEntryPointClass = GetName(node) == _config.CSharpOptions.EntryPointName;
1614
+ if (isEntryPointClass)
1615
+ {
1616
+ var filePath = Path.TrimEndingDirectorySeparator(_tree.FilePath);
1617
+ var isClientFile = filePath.EndsWith(".client.cs");
1618
+ if ((_flags.ClientEntryPointDefined && isClientFile) || (_flags.ServerEntryPointDefined && !isClientFile))
1619
+ {
1620
+ Logger.CodegenError(node, $"No more than one main method can be defined on the {(isClientFile ? "client" : "server")}.");
1621
+ }
1622
+ if (isClientFile)
1623
+ {
1624
+ _flags.ClientEntryPointDefined = true;
1625
+ }
1626
+ else
1627
+ {
1628
+ _flags.ServerEntryPointDefined = true;
1629
+ }
1630
+
1631
+ var mainMethod = methods.FirstOrDefault(method => GetName(method) == _config.CSharpOptions.MainMethodName);
1632
+ if (mainMethod == null)
1633
+ {
1634
+ Logger.CodegenError(node.Identifier, $"No main method \"{_config.CSharpOptions.MainMethodName}\" found in entry point class");
1635
+ return;
1636
+ }
1637
+ if (!HasSyntax(mainMethod.Modifiers, SyntaxKind.StaticKeyword))
1638
+ {
1639
+ Logger.CodegenError(node.Identifier, $"Main method must be static.");
1640
+ }
1641
+
1642
+ WriteLine();
1643
+ WriteLine("if namespace == nil then");
1644
+ _indent++;
1645
+ WriteLine($"class.{_config.CSharpOptions.MainMethodName}()");
1646
+ _indent--;
1647
+ WriteLine("else");
1648
+ _indent++;
1649
+ WriteLine($"namespace[\"$onLoaded\"](namespace, class.{_config.CSharpOptions.MainMethodName})");
1650
+ _indent--;
1651
+ Write("end");
1652
+ }
1653
+
1654
+ WriteLine();
1655
+ WriteLine($"return class");
1656
+
1657
+ _indent--;
1658
+ WriteLine("end)");
1659
+ }
1660
+
1661
+ public override void VisitMethodDeclaration(MethodDeclarationSyntax node)
1662
+ {
1663
+ if (HasSyntax(node.Modifiers, SyntaxKind.ExternKeyword))
1664
+ {
1665
+ Logger.UnsupportedError(node, "Extern methods", useYet: false);
1666
+ }
1667
+
1668
+ foreach (var attributeList in node.AttributeLists)
1669
+ {
1670
+ Visit(attributeList);
1671
+ }
1672
+
1673
+ var isStatic = HasSyntax(node.Modifiers, SyntaxKind.StaticKeyword);
1674
+ var isMetamethod = Constants.METAMETHODS.Contains(GetName(node));
1675
+ var objectName = "self";
1676
+ if (isStatic)
1677
+ {
1678
+ objectName = "class";
1679
+ }
1680
+ else if (isMetamethod)
1681
+ {
1682
+ objectName = "mt";
1683
+ }
1684
+
1685
+ var name = GetName(node);
1686
+ Write($"function {objectName}.{name}");
1687
+ Visit(node.ParameterList);
1688
+ _indent++;
1689
+
1690
+ Visit(node.Body);
1691
+ WriteDefaultReturn(node.Body);
1692
+
1693
+ _indent--;
1694
+ WriteLine("end");
1695
+ }
1696
+
1697
+ public override void VisitBaseExpression(BaseExpressionSyntax node)
1698
+ {
1699
+ Write("self[\"$superclass\"]");
1700
+ }
1701
+
1702
+ public override void VisitConstructorDeclaration(ConstructorDeclarationSyntax node)
1703
+ {
1704
+ // TODO: struct support?
1705
+ foreach (var attributeList in node.AttributeLists)
1706
+ {
1707
+ Visit(attributeList);
1708
+ }
1709
+ Write($"function class.new");
1710
+ Visit(node.ParameterList);
1711
+ _indent++;
1712
+
1713
+ VisitConstructorBody(FindFirstAncestor<ClassDeclarationSyntax>(node)!, node.Body, node.Initializer?.ArgumentList);
1714
+
1715
+ _indent--;
1716
+ WriteLine("end");
1717
+ }
1718
+
1719
+ private void VisitConstructorBody(ClassDeclarationSyntax parentClass, BlockSyntax? block, ArgumentListSyntax? initializerArguments)
1720
+ {
1721
+ var isWithinNamespace = IsDescendantOf<NamespaceDeclarationSyntax>(parentClass);
1722
+ WriteLine("local mt = {}");
1723
+ Write("local self = CS.classInstance(class, mt");
1724
+ if (isWithinNamespace)
1725
+ {
1726
+ Write(", namespace");
1727
+ }
1728
+ WriteLine(')'); // TODO: (when typedefs are generated for classes) $") :: {GetName(parentClass)}"
1729
+ WriteLine();
1730
+ if (initializerArguments != null)
1731
+ {
1732
+ Write("self[\"$base\"]");
1733
+ Visit(initializerArguments);
1734
+ }
1735
+
1736
+ var isNotStatic = (MemberDeclarationSyntax member) => !HasSyntax(parentClass.Modifiers, SyntaxKind.StaticKeyword) && !HasSyntax(member.Modifiers, SyntaxKind.StaticKeyword);
1737
+ var isNotAbstract = (MemberDeclarationSyntax member) => !HasSyntax(member.Modifiers, SyntaxKind.AbstractKeyword);
1738
+ var nonStaticFields = parentClass.Members
1739
+ .Where(isNotStatic)
1740
+ .Where(isNotAbstract)
1741
+ .OfType<FieldDeclarationSyntax>();
1742
+ var nonStaticProperties = parentClass.Members
1743
+ .Where(isNotStatic)
1744
+ .Where(isNotAbstract)
1745
+ .OfType<PropertyDeclarationSyntax>();
1746
+ var nonStaticMethods = parentClass.Members
1747
+ .Where(isNotStatic)
1748
+ .Where(isNotAbstract)
1749
+ .OfType<MethodDeclarationSyntax>();
1750
+
1751
+ InitializeFields(nonStaticFields);
1752
+ InitializeProperties(nonStaticProperties);
1753
+ if (block != null)
1754
+ {
1755
+ WriteLine();
1756
+ Visit(block);
1757
+ }
1758
+ if (nonStaticMethods.Count() > 0)
1759
+ {
1760
+ WriteLine();
1761
+ }
1762
+ foreach (var method in nonStaticMethods)
1763
+ {
1764
+ Visit(method);
1765
+ }
1766
+
1767
+ WriteLine();
1768
+ WriteLine("return self");
1769
+ }
1770
+
1771
+ private void CreateDefaultConstructor(ClassDeclarationSyntax node)
1772
+ {
1773
+ WriteLine($"function class.new()");
1774
+ _indent++;
1775
+
1776
+ VisitConstructorBody(node, null, null);
1777
+
1778
+ _indent--;
1779
+ WriteLine("end");
1780
+ }
1781
+
1782
+ private void InitializeFields(IEnumerable<FieldDeclarationSyntax> fields)
1783
+ {
1784
+ foreach (var field in fields)
1785
+ {
1786
+ var classDeclaration = FindFirstAncestor<ClassDeclarationSyntax>(field)!;
1787
+ var isStatic = HasSyntax(classDeclaration.Modifiers, SyntaxKind.StaticKeyword) || HasSyntax(field.Modifiers, SyntaxKind.StaticKeyword);
1788
+ foreach (var declarator in field.Declaration.Variables)
1789
+ {
1790
+ if (declarator.Initializer == null) continue;
1791
+ Write($"{(isStatic ? "class" : "self")}.{GetName(declarator)} = ");
1792
+ Visit(declarator.Initializer);
1793
+ WriteLine();
1794
+ }
1795
+ }
1796
+ }
1797
+
1798
+ private void InitializeProperties(IEnumerable<PropertyDeclarationSyntax> properties)
1799
+ {
1800
+ foreach (var property in properties)
1801
+ {
1802
+ if (property.Initializer == null) continue;
1803
+
1804
+ var classDeclaration = FindFirstAncestor<ClassDeclarationSyntax>(property)!;
1805
+ var isStatic = HasSyntax(classDeclaration.Modifiers, SyntaxKind.StaticKeyword) || HasSyntax(property.Modifiers, SyntaxKind.StaticKeyword);
1806
+ Write($"{(isStatic ? "class" : "self")}.{GetName(property)} = ");
1807
+ Visit(property.Initializer);
1808
+ WriteLine();
1809
+ }
1810
+ }
1811
+
1812
+ private void WriteName(SyntaxNode node, SyntaxToken identifier)
1813
+ {
1814
+ var identifierText = identifier.ValueText;
1815
+ var originalIdentifierName = identifier.Text;
1816
+ if (identifierText == "var") return;
1817
+ if (Constants.LUAU_KEYWORDS.Contains(identifierText))
1818
+ {
1819
+ Logger.CodegenError(node, $"Using reserved Luau keywords as identifier names is unsupported!");
1820
+ }
1821
+
1822
+ var isWithinClass = IsDescendantOf<ClassDeclarationSyntax>(node);
1823
+ var prefix = "";
1824
+ if (isWithinClass)
1825
+ {
1826
+ // Check the fields in classes that this node is a descendant of for the identifier name
1827
+ var ancestorClasses = GetAncestors<ClassDeclarationSyntax>(node);
1828
+ for (int i = 0; i < ancestorClasses.Length; i++)
1829
+ {
1830
+ var ancestorClass = ancestorClasses[i];
1831
+ var fields = ancestorClass.Members.OfType<FieldDeclarationSyntax>();
1832
+ foreach (var field in fields)
1833
+ {
1834
+ var classDeclaration = FindFirstAncestor<ClassDeclarationSyntax>(field)!;
1835
+ var isStatic = HasSyntax(classDeclaration.Modifiers, SyntaxKind.StaticKeyword) || HasSyntax(field.Modifiers, SyntaxKind.StaticKeyword);
1836
+ foreach (var declarator in field.Declaration.Variables)
1837
+ {
1838
+ var name = GetName(declarator);
1839
+ if (name != identifierText) continue;
1840
+ prefix = (isStatic ? "class" : "self") + '.';
1841
+ }
1842
+ }
1843
+ }
1844
+ }
1845
+
1846
+ if (prefix == "")
1847
+ {
1848
+ var symbol = _semanticModel.GetSymbolInfo(node).Symbol;
1849
+ var parentNamespace = FindFirstAncestor<NamespaceDeclarationSyntax>(node);
1850
+ var parentNamespaceSymbol = parentNamespace != null ? _semanticModel.GetDeclaredSymbol(parentNamespace) : null;
1851
+ var pluginClassesNamespace = _runtimeLibNamespace.GetNamespaceMembers().FirstOrDefault(ns => ns.Name == "PluginClasses");
1852
+ var runtimeNamespaceIncludesIdentifier = symbol != null ? (
1853
+ IsDescendantOfNamespaceSymbol(symbol, _runtimeLibNamespace)
1854
+ || (pluginClassesNamespace != null && IsDescendantOfNamespaceSymbol(symbol, pluginClassesNamespace))
1855
+ ) : false;
1856
+
1857
+ HashSet<SyntaxKind> fullyQualifiedParentKinds = [SyntaxKind.SimpleMemberAccessExpression, SyntaxKind.ObjectCreationExpression];
1858
+ if (
1859
+ symbol != null
1860
+ && symbol is ITypeSymbol typeSymbol
1861
+ && node.Parent != null
1862
+ && fullyQualifiedParentKinds.Contains(node.Parent.Kind())
1863
+ && typeSymbol.ContainingNamespace != null
1864
+ && (parentNamespace != null ? Utility.GetNamesFromNode(parentNamespace.Name).LastOrDefault() != typeSymbol.ContainingNamespace.Name : true)
1865
+ && !Constants.NO_FULL_QUALIFICATION_TYPES.Contains(typeSymbol.ContainingNamespace.Name)
1866
+ )
1867
+ {
1868
+ var usings = GetUsings();
1869
+ FullyQualifyMemberAccess(typeSymbol.ContainingNamespace, usings);
1870
+ }
1871
+
1872
+ var parentAccessExpression = FindFirstAncestor<MemberAccessExpressionSyntax>(node);
1873
+ var isLeftSide = parentAccessExpression == null ? true : node == parentAccessExpression.Expression;
1874
+ var parentBlocks = GetAncestors<SyntaxNode>(node);
1875
+ var localScopeIncludesIdentifier = parentBlocks.Any(block =>
1876
+ {
1877
+ var descendants = block.DescendantNodes();
1878
+ var localFunctions = descendants.OfType<LocalFunctionStatementSyntax>();
1879
+ var variableDesignations = descendants.OfType<VariableDesignationSyntax>();
1880
+ var variableDeclarators = descendants.OfType<VariableDeclaratorSyntax>();
1881
+ var forEachStatements = descendants.OfType<ForEachStatementSyntax>();
1882
+ var forStatements = descendants.OfType<ForStatementSyntax>();
1883
+ var parameters = descendants.OfType<ParameterSyntax>();
1884
+ var catchDeclarations = descendants.OfType<CatchDeclarationSyntax>();
1885
+ var checkNamePredicate = (SyntaxNode node) => TryGetName(node) == identifierText;
1886
+ return localFunctions.Any(checkNamePredicate)
1887
+ || variableDesignations.Any(checkNamePredicate)
1888
+ || variableDeclarators.Any(checkNamePredicate)
1889
+ || parameters.Any(checkNamePredicate)
1890
+ || catchDeclarations.Any(checkNamePredicate)
1891
+ || forEachStatements.Any(checkNamePredicate)
1892
+ || forStatements.Any(forStatement => forStatement.Initializers.Count() > 0);
1893
+ });
1894
+
1895
+ if (isLeftSide && !localScopeIncludesIdentifier && !runtimeNamespaceIncludesIdentifier)
1896
+ {
1897
+ var namespaceSymbol = parentNamespace != null ? _semanticModel.GetDeclaredSymbol(parentNamespace) : null;
1898
+ var namespaceIncludesIdentifier = namespaceSymbol != null && Utility.FindMember(namespaceSymbol, originalIdentifierName) != null;
1899
+ var parentClass = FindFirstAncestor<ClassDeclarationSyntax>(node);
1900
+ var classSymbol = parentClass != null ? _semanticModel.GetDeclaredSymbol(parentClass) : null;
1901
+ var classMemberSymbol = classSymbol != null ? Utility.FindMemberDeep(classSymbol, originalIdentifierName) : null;
1902
+
1903
+ if (namespaceIncludesIdentifier)
1904
+ {
1905
+ Write($"namespace[\"$getMember\"](namespace, \"{identifierText}\")");
1906
+ }
1907
+ else if (classMemberSymbol != null)
1908
+ {
1909
+ Write($"{(classMemberSymbol.IsStatic ? "class" : "self")}.{identifierText}");
1910
+ }
1911
+ else
1912
+ {
1913
+ if (_flags.ShouldCallGetAssemblyType)
1914
+ {
1915
+ Write($"CS.getAssemblyType(\"{identifierText}\")");
1916
+ }
1917
+ else
1918
+ {
1919
+ Write(identifierText);
1920
+ _flags.ShouldCallGetAssemblyType = true;
1921
+ }
1922
+ }
1923
+ }
1924
+ else
1925
+ {
1926
+ Write(identifierText);
1927
+ }
1928
+ }
1929
+ else
1930
+ {
1931
+ Write(prefix + identifierText);
1932
+ }
1933
+ }
1934
+
1935
+ private void WriteListTable(List<ExpressionSyntax> expressions)
1936
+ {
1937
+ Write('{');
1938
+ foreach (var expression in expressions)
1939
+ {
1940
+ Visit(expression);
1941
+ if (expression != expressions.Last())
1942
+ {
1943
+ Write(", ");
1944
+ }
1945
+ }
1946
+ Write('}');
1947
+ }
1948
+
1949
+ private void WriteDefaultReturn(BlockSyntax? block)
1950
+ {
1951
+ if (block != null && !block.Statements.Any(stmt => stmt.IsKind(SyntaxKind.ReturnStatement)))
1952
+ {
1953
+ WriteLine("return nil :: any");
1954
+ }
1955
+ }
1956
+
1957
+ private void WritePatternDeclarations(SyntaxNode node)
1958
+ {
1959
+ if (node is IsPatternExpressionSyntax isPattern)
1960
+ {
1961
+ void writeInitializer()
1962
+ {
1963
+ Write(" = ");
1964
+ Visit(isPattern.Expression);
1965
+ WriteLine();
1966
+ }
1967
+
1968
+ if (isPattern.Pattern is DeclarationPatternSyntax declarationPattern)
1969
+ {
1970
+ Visit(declarationPattern.Designation);
1971
+ writeInitializer();
1972
+ }
1973
+ else if (isPattern.Pattern is VarPatternSyntax varPattern)
1974
+ {
1975
+ Visit(varPattern.Designation);
1976
+ writeInitializer();
1977
+ }
1978
+ }
1979
+ }
1980
+
1981
+ private void WriteTypeAnnotation(TypeSyntax type, bool isReturnType = false)
1982
+ {
1983
+ if (!type.IsVar)
1984
+ {
1985
+ var mappedType = Utility.GetMappedType(type.ToString());
1986
+ Write($": {mappedType}");
1987
+ if (isReturnType)
1988
+ {
1989
+ WriteLine();
1990
+ }
1991
+ }
1992
+ }
1993
+
1994
+ private void WriteRequire(string path)
1995
+ {
1996
+ WriteLine($"require({path})");
1997
+ }
1998
+
1999
+ private void WriteLine()
2000
+ {
2001
+ WriteLine("");
2002
+ }
2003
+
2004
+ private void WriteLine(char text)
2005
+ {
2006
+ WriteLine(text.ToString());
2007
+ }
2008
+
2009
+ private void WriteLine(string text)
2010
+ {
2011
+ if (text == null)
2012
+ {
2013
+ _output.AppendLine();
2014
+ return;
2015
+ }
2016
+
2017
+ WriteTab();
2018
+ _output.AppendLine(text);
2019
+ }
2020
+
2021
+ private void Write(char text)
2022
+ {
2023
+ Write(text.ToString());
2024
+ }
2025
+
2026
+ private void Write(string text)
2027
+ {
2028
+ WriteTab();
2029
+ _output.Append(text);
2030
+ }
2031
+
2032
+ private void WriteTab()
2033
+ {
2034
+ _output.Append(MatchLastCharacter('\n') ? GetTabString() : "");
2035
+ }
2036
+
2037
+ private string GetTabString()
2038
+ {
2039
+ return string.Concat(Enumerable.Repeat(" ", _indentSize * _indent));
2040
+ }
2041
+
2042
+ private void RemoveLastCharacters(int amount)
2043
+ {
2044
+ _output.Remove(_output.Length - amount, amount);
2045
+ }
2046
+
2047
+ private bool HasSyntax(SyntaxTokenList tokens, SyntaxKind syntax)
2048
+ {
2049
+ return tokens.Any(token => token.IsKind(syntax));
2050
+ }
2051
+
2052
+ private bool IsDescendantOfNamespaceSymbol(ISymbol symbol, INamespaceSymbol ancestor)
2053
+ {
2054
+ var namespaceSymbol = symbol.ContainingNamespace;
2055
+ while (namespaceSymbol != null)
2056
+ {
2057
+ if (SymbolEqualityComparer.Default.Equals(namespaceSymbol, ancestor))
2058
+ {
2059
+ return true;
2060
+ }
2061
+ namespaceSymbol = namespaceSymbol.ContainingNamespace;
2062
+ }
2063
+ return false;
2064
+ }
2065
+
2066
+ private bool IsDescendantOf<T>(SyntaxNode node) where T : SyntaxNode
2067
+ {
2068
+ return FindFirstAncestor<T>(node) != null;
2069
+ }
2070
+
2071
+ private T? FindFirstAncestor<T>(SyntaxNode node) where T : SyntaxNode
2072
+ {
2073
+ return GetAncestors<T>(node).FirstOrDefault();
2074
+ }
2075
+
2076
+ private T[] GetAncestors<T>(SyntaxNode node) where T : SyntaxNode
2077
+ {
2078
+ return node.Ancestors().OfType<T>().ToArray();
2079
+ }
2080
+
2081
+ private string? TryGetName(SyntaxNode node)
2082
+ {
2083
+ return Utility.GetNamesFromNode(node).FirstOrDefault();
2084
+ }
2085
+
2086
+ private string GetName(SyntaxNode node)
2087
+ {
2088
+ return Utility.GetNamesFromNode(node).First();
2089
+ }
2090
+
2091
+ private bool MatchLastCharacter(char character)
2092
+ {
2093
+ if (_output.Length == 0) return false;
2094
+ return _output[_output.Length - 1] == character;
2095
+ }
2096
+ }
2097
+ }
roblox-cs-master/RobloxCS/CompiledFile.cs ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ namespace RobloxCS
2
+ {
3
+ internal sealed class CompiledFile
4
+ {
5
+ public readonly string Path;
6
+ public readonly string LuaSource;
7
+
8
+ public CompiledFile(string path, string luaSource)
9
+ {
10
+ Path = path;
11
+ LuaSource = luaSource;
12
+ }
13
+ }
14
+ }
roblox-cs-master/RobloxCS/ConfigReader.cs ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using YamlDotNet.Serialization;
2
+ using YamlDotNet.Serialization.NamingConventions;
3
+
4
+ namespace RobloxCS
5
+ {
6
+ public static class ConfigReader
7
+ {
8
+ public static ConfigData UnitTestingConfig
9
+ {
10
+ get
11
+ {
12
+ return new ConfigData()
13
+ {
14
+ SourceFolder = "test-src",
15
+ OutputFolder = "test-dist",
16
+ RojoProjectName = "UNIT_TESTING",
17
+ EnabledBuiltInTransformers = ["Debug"],
18
+ EmitNativeAttributeOnClassOrNamespaceCallbacks = true,
19
+ CSharpOptions = new CSharpOptions()
20
+ {
21
+ EntryPointName = "UnitTest",
22
+ MainMethodName = "Main",
23
+ AssemblyName = "UnitTesting"
24
+ }
25
+ };
26
+ }
27
+ }
28
+
29
+ private const string _fileName = "roblox-cs.yml";
30
+
31
+ public static ConfigData Read(string inputDirectory)
32
+ {
33
+ var configPath = inputDirectory + "/" + _fileName;
34
+ ConfigData? config = default;
35
+ string ymlContent = default!;
36
+
37
+ try
38
+ {
39
+ ymlContent = File.ReadAllText(configPath);
40
+ }
41
+ catch (Exception e)
42
+ {
43
+ FailToRead(e.Message);
44
+ }
45
+
46
+ var deserializer = new DeserializerBuilder()
47
+ .WithNamingConvention(PascalCaseNamingConvention.Instance)
48
+ .WithAttemptingUnquotedStringTypeDeserialization()
49
+ .WithDuplicateKeyChecking()
50
+ .Build();
51
+
52
+ try
53
+ {
54
+ config = deserializer.Deserialize<ConfigData>(ymlContent);
55
+ }
56
+ catch (Exception e)
57
+ {
58
+ FailToRead(e.ToString());
59
+ }
60
+
61
+ if (config == null || !config.IsValid())
62
+ {
63
+ FailToRead("Invalid config! Make sure it has all required fields.");
64
+ }
65
+
66
+ return config!;
67
+ }
68
+
69
+ private static void FailToRead(string message)
70
+ {
71
+ Logger.Error($"Failed to read {_fileName}!\n{message}");
72
+ }
73
+ }
74
+ }
roblox-cs-master/RobloxCS/ConfigTemplate.cs ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ namespace RobloxCS
2
+ {
3
+ #pragma warning disable CS8618
4
+ public sealed class CSharpOptions
5
+ {
6
+ public string EntryPointName { get; set; }
7
+ public string MainMethodName { get; set; }
8
+ public string AssemblyName { get; set; }
9
+ public bool EntryPointRequired { get; set; } = true;
10
+
11
+ public bool IsValid()
12
+ {
13
+ return !string.IsNullOrEmpty(EntryPointName)
14
+ && !string.IsNullOrEmpty(MainMethodName)
15
+ && !string.IsNullOrEmpty(AssemblyName);
16
+ }
17
+ }
18
+
19
+ public sealed class ConfigData
20
+ {
21
+ public string SourceFolder { get; set; }
22
+ public string OutputFolder { get; set; }
23
+ public string RojoProjectName { get; set; } = "default";
24
+ public bool EmitNativeAttributeOnClassOrNamespaceCallbacks { get; set; } = true;
25
+ public HashSet<string> EnabledBuiltInTransformers { get; set; } = ["Debug"];
26
+ public CSharpOptions CSharpOptions { get; set; }
27
+
28
+ public bool IsValid()
29
+ {
30
+ return !string.IsNullOrEmpty(SourceFolder)
31
+ && !string.IsNullOrEmpty(OutputFolder)
32
+ && !string.IsNullOrEmpty(RojoProjectName)
33
+ && CSharpOptions != null
34
+ && CSharpOptions.IsValid();
35
+ }
36
+ }
37
+ #pragma warning restore CS8618
38
+ }
roblox-cs-master/RobloxCS/Constants.cs ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using Microsoft.CodeAnalysis.CSharp;
2
+
3
+ namespace RobloxCS
4
+ {
5
+ internal static class Constants
6
+ {
7
+ public static readonly HashSet<string> UNSUPPORTED_BITWISE_TYPES =
8
+ [
9
+ "UInt128",
10
+ "ulong",
11
+ "long",
12
+ "Int128"
13
+ ];
14
+
15
+ public static readonly HashSet<string> LENGTH_READABLE_TYPES =
16
+ [
17
+ "String",
18
+ "string",
19
+ "Array"
20
+ ];
21
+
22
+ public static readonly Dictionary<string, string> MAPPED_STRING_METHODS = new Dictionary<string, string>
23
+ {
24
+ { "Replace", "gsub" },
25
+ { "Split", "split" },
26
+ { "ToLower", "lower" },
27
+ { "ToUpper", "upper" },
28
+ { "Reverse", "reverse" }
29
+ };
30
+
31
+ public static readonly HashSet<string> GLOBAL_LIBRARIES =
32
+ [
33
+ "task",
34
+ "math",
35
+ "table",
36
+ "os",
37
+ "buffer",
38
+ "coroutine",
39
+ "utf8",
40
+ "debug"
41
+ ];
42
+
43
+ public static readonly HashSet<string> METAMETHODS =
44
+ [
45
+ "__tostring",
46
+ "__add",
47
+ "__sub",
48
+ "__mul",
49
+ "__div",
50
+ "__idiv",
51
+ "__mod",
52
+ "__pow",
53
+ "__unm",
54
+ "__eq",
55
+ "__le",
56
+ "__lte",
57
+ "__len",
58
+ "__iter",
59
+ "__call",
60
+ "__concat",
61
+ "__mode",
62
+ "__index",
63
+ "__newindex",
64
+ "__metatable",
65
+ ];
66
+
67
+ public static readonly HashSet<string> LUAU_KEYWORDS =
68
+ [
69
+ "local",
70
+ "and",
71
+ "or",
72
+ "if",
73
+ "else",
74
+ "elseif",
75
+ "then",
76
+ "do",
77
+ "end",
78
+ "function",
79
+ "for",
80
+ "while",
81
+ "in",
82
+ "export",
83
+ "type",
84
+ "typeof"
85
+ ];
86
+
87
+ public static readonly HashSet<SyntaxKind> MEMBER_PARENT_SYNTAXES =
88
+ [
89
+ SyntaxKind.NamespaceDeclaration,
90
+ SyntaxKind.ClassDeclaration,
91
+ SyntaxKind.InterfaceDeclaration,
92
+ SyntaxKind.StructDeclaration
93
+ ];
94
+
95
+ public static readonly HashSet<string> NO_FULL_QUALIFICATION_TYPES =
96
+ [
97
+ "System",
98
+ "Roblox",
99
+ "Globals",
100
+ "PluginClasses"
101
+ ];
102
+
103
+ public static readonly HashSet<string> IGNORED_BINARY_OPERATORS =
104
+ [
105
+ "as"
106
+ ];
107
+
108
+ public static readonly Dictionary<List<string>, (string, string)> PER_TYPE_BINARY_OPERATOR_MAP = new Dictionary<List<string>, (string, string)>
109
+ {
110
+ { ["String", "string"], ("+", "..") }
111
+ };
112
+
113
+ public static readonly HashSet<string> DECIMAL_TYPES =
114
+ [
115
+ "float",
116
+ "double",
117
+ "Single",
118
+ "Double"
119
+ ];
120
+
121
+ public static readonly HashSet<string> INTEGER_TYPES =
122
+ [
123
+ "sbyte",
124
+ "byte",
125
+ "short",
126
+ "ushort",
127
+ "int",
128
+ "uint",
129
+ "long",
130
+ "ulong",
131
+ "SByte",
132
+ "Byte",
133
+ "Int16",
134
+ "Int32",
135
+ "Int64",
136
+ "Int128",
137
+ "UInt16",
138
+ "UInt32",
139
+ "UInt64",
140
+ "UInt128",
141
+ ];
142
+ }
143
+ }
roblox-cs-master/RobloxCS/FileManager.cs ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ namespace RobloxCS
2
+ {
3
+ internal static class FileManager
4
+ {
5
+ public static IEnumerable<string> GetSourceFiles(string sourceDirectory)
6
+ {
7
+ try
8
+ {
9
+ return Directory.GetFiles(sourceDirectory, "*.cs", SearchOption.AllDirectories)
10
+ .Where(file => !Utility.FixPathSep(file).StartsWith(Utility.FixPathSep(sourceDirectory) + "/obj"))
11
+ .Select(Utility.FixPathSep);
12
+ }
13
+ catch (Exception e)
14
+ {
15
+ Logger.Error($"Failed to read source files: {e.Message}");
16
+ return [];
17
+ }
18
+ }
19
+
20
+ public static void CopyDirectory(string sourceDirectory, string destinationDirectory)
21
+ {
22
+ var directory = new DirectoryInfo(sourceDirectory);
23
+ if (!directory.Exists)
24
+ {
25
+ throw new DirectoryNotFoundException(
26
+ "Source directory does not exist or could not be found: "
27
+ + sourceDirectory);
28
+ }
29
+
30
+ if (!Directory.Exists(destinationDirectory))
31
+ {
32
+ Directory.CreateDirectory(destinationDirectory);
33
+ }
34
+
35
+ var files = directory.GetFiles();
36
+ foreach (var file in files)
37
+ {
38
+ var tempPath = Path.Combine(destinationDirectory, file.Name);
39
+ file.CopyTo(tempPath, true);
40
+ }
41
+
42
+ var directories = directory.GetDirectories();
43
+ foreach (var subdirectory in directories)
44
+ {
45
+ var tempPath = Path.Combine(destinationDirectory, subdirectory.Name);
46
+ CopyDirectory(subdirectory.FullName, tempPath);
47
+ }
48
+ }
49
+
50
+ public static void WriteCompiledFiles(string outDirectory, List<CompiledFile> compiledFiles)
51
+ {
52
+ Logger.Info($"Compiling {compiledFiles.Count} files...");
53
+ TryCreateDirectory(outDirectory);
54
+
55
+ foreach (var compiledFile in compiledFiles)
56
+ {
57
+ var directoryParts = compiledFile.Path.Split('/').ToList();
58
+ directoryParts.Remove(directoryParts.Last());
59
+ var parentDirectory = string.Join('/', directoryParts);
60
+ TryCreateDirectory(parentDirectory);
61
+
62
+ try
63
+ {
64
+ File.WriteAllText(compiledFile.Path, compiledFile.LuaSource);
65
+ }
66
+ catch (Exception e)
67
+ {
68
+ Logger.Error($"Failed to write to \"{compiledFile.Path}\": {e.Message}");
69
+ }
70
+ Logger.Info($"Successfully wrote \"{compiledFile.Path}\"!");
71
+ }
72
+ }
73
+
74
+ private static void TryCreateDirectory(string path)
75
+ {
76
+ try
77
+ {
78
+ Directory.CreateDirectory(path);
79
+ }
80
+ catch (Exception e)
81
+ {
82
+ Logger.Error($"Failed to create directory \"{path}\": {e.Message}");
83
+ }
84
+ }
85
+ }
86
+ }
roblox-cs-master/RobloxCS/Include/RuntimeLib.lua ADDED
@@ -0,0 +1,321 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --!strict
2
+ --!native
3
+
4
+ local CS = {}
5
+ local assemblyGlobal = {}
6
+
7
+ local function chainIndex(location: table, ...: table): () -> any
8
+ local names = {...}
9
+ return function(t, k)
10
+ for _, name in names do
11
+ local tbl = location[name]
12
+ local v = tbl[k]
13
+ if v ~= nil then
14
+ return v
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ local function createArithmeticOperators(self, mt, fieldName): table
21
+ -- TODO: bitwise ops (if necessary) (or possible)
22
+ local function getNumericValue(value: table | number): number
23
+ return if typeof(value) == "table" and value.__isEnumMember then value[fieldName] else value
24
+ end
25
+
26
+ function mt:__add(other)
27
+ return self[fieldName] + getNumericValue(other)
28
+ end
29
+ function mt:__sub(other)
30
+ return self[fieldName] - getNumericValue(other)
31
+ end
32
+ function mt:__mul(other)
33
+ return self[fieldName] * getNumericValue(other)
34
+ end
35
+ function mt:__div(other)
36
+ return self[fieldName] / getNumericValue(other)
37
+ end
38
+ function mt:__idiv(other)
39
+ return self[fieldName] // getNumericValue(other)
40
+ end
41
+ function mt:__mod(other)
42
+ return self[fieldName] % getNumericValue(other)
43
+ end
44
+ function mt:__pow(other)
45
+ return self[fieldName] ^ getNumericValue(other)
46
+ end
47
+ function mt:__unm()
48
+ return -self[fieldName]
49
+ end
50
+ return mt
51
+ end
52
+
53
+ export type Class = table;
54
+ export type Namespace = {
55
+ name: string;
56
+ parent: Namespace?;
57
+ members: { Namespace | Class };
58
+ class: (self: Namespace, name: string, create: (self: Namespace) -> Class) -> nil;
59
+ }
60
+
61
+ local CSNamespace = {} do
62
+ @native
63
+ function CSNamespace.new(name, parent)
64
+ local self = {}
65
+ self.name = name
66
+ self.parent = parent
67
+ self.members = {}
68
+ self["$loadCallbacks"] = {}
69
+ if self.parent ~= nil then
70
+ self = setmetatable(self, { __index = self.parent })
71
+ end
72
+ return setmetatable(self, CSNamespace)
73
+ end
74
+
75
+ @native
76
+ function CSNamespace:__index(index)
77
+ return self.members[index] or CSNamespace[index]
78
+ end
79
+
80
+ @native
81
+ function CSNamespace:__newindex(index, value)
82
+ self.members[index] = value
83
+ end
84
+
85
+ @native
86
+ function CSNamespace:__tostring(index)
87
+ return self.name
88
+ end
89
+
90
+ CSNamespace["$getMember"] = @native function(self, name)
91
+ return self.members[name]
92
+ end
93
+
94
+ CSNamespace["$onLoaded"] = @native function(self, callback)
95
+ table.insert(self["$loadCallbacks"], callback)
96
+ end
97
+
98
+ @native
99
+ function CSNamespace:class(name, create)
100
+ CS.class(name, create, self)
101
+ end
102
+
103
+ @native
104
+ function CSNamespace:namespace(name, registerMembers)
105
+ CS.namespace(name, registerMembers, self.members, self)
106
+ end
107
+ end
108
+
109
+ @native
110
+ function CS.classInstance(class: Class, mt: table, namespace: Namespace?)
111
+ local instance = {}
112
+ instance["$className"] = class.__name
113
+
114
+ @native
115
+ local function getSuperclass()
116
+ if class.__superclass == nil then return end
117
+ if class.__superclass:match(".") == nil then
118
+ return assemblyGlobal[class.__superclass]
119
+ end
120
+
121
+ local pieces = class.__superclass:split(".");
122
+ local result = assemblyGlobal
123
+ for _, piece in pieces do
124
+ result = result[piece] or result
125
+ end
126
+ return result
127
+ end
128
+
129
+ @native
130
+ function mt.__tostring()
131
+ return class.__name
132
+ end
133
+
134
+ instance["$base"] = @native function(...)
135
+ if instance["$superclass"] ~= nil then return end
136
+ local Superclass = getSuperclass()
137
+ local superclassInstance = Superclass.new(...)
138
+ instance["$superclass"] = superclassInstance
139
+ mt.__index = superclassInstance
140
+ end
141
+
142
+ return setmetatable(instance, mt)
143
+ end
144
+
145
+ @native
146
+ function CS.classDef(name: string, namespace: Namespace?, superclass: string?, ...: string)
147
+ local mt = {}
148
+ mt.__index = chainIndex(if namespace ~= nil then namespace else assemblyGlobal, ...)
149
+
150
+ @native
151
+ function mt.__tostring()
152
+ return name
153
+ end
154
+
155
+ local class = {}
156
+ class.__name = name
157
+ class.__superclass = superclass
158
+ return setmetatable(class, mt)
159
+ end
160
+
161
+ @native
162
+ function CS.class(name: string, create: (namespace: Namespace?) -> table, namespace: Namespace?)
163
+ local location = if namespace ~= nil then namespace.members else assemblyGlobal
164
+ local class = create(namespace)
165
+ location[name] = class
166
+ end
167
+
168
+ @native
169
+ function CS.namespace(name: string, registerMembers: () -> nil, location: table?): Namespace
170
+ local parent = location
171
+ if location == nil then
172
+ location = assemblyGlobal
173
+ end
174
+
175
+ local namespaceDefinition = location[name] or CSNamespace.new(name, parent)
176
+ registerMembers(namespaceDefinition)
177
+ location[name] = namespaceDefinition
178
+
179
+ for _, callback in namespaceDefinition["$loadCallbacks"] do
180
+ callback()
181
+ end
182
+
183
+ return namespaceDefinition
184
+ end
185
+
186
+ @native
187
+ function CS.enum(name: string, definition: table, location: table): table
188
+ if location == nil then
189
+ location = assemblyGlobal
190
+ end
191
+ definition.__name = name
192
+
193
+ @native
194
+ function definition:__index(index: string | number): table
195
+ if index == "__name" then return name end
196
+ local member = {
197
+ name = index,
198
+ value = definition[index],
199
+ __isEnumMember = true
200
+ }
201
+
202
+ return setmetatable(member, createArithmeticOperators(member, {
203
+ __eq = @native function(self, other)
204
+ return typeof(other) == "table" and other.__isEnumMember and self.value == other.value
205
+ end,
206
+ __tostring = @native function(self)
207
+ return self.name
208
+ end
209
+ }, "value"))
210
+ end
211
+
212
+ @native
213
+ function definition:__eq(other: table): boolean
214
+ return self.__name == other.__name
215
+ end
216
+
217
+ @native
218
+ function definition:__tostring(): string
219
+ return self.__name
220
+ end
221
+
222
+ location[name] = location[name] or table.freeze(setmetatable({}, definition))
223
+ return location[name]
224
+ end
225
+
226
+ @native
227
+ function CS.is(object: any, class: Class | string): boolean
228
+ if typeof(class) == "table" and type(class.__name) == "string" then
229
+ return typeof(object) == "table" and type(object["className"]) == "string" and object["className"] == class.__name
230
+ end
231
+
232
+ -- metatable check
233
+ if typeof(object) == "table" then
234
+ obj = getmetatable(obj)
235
+ while object ~= nil do
236
+ if object == class then
237
+ return true
238
+ end
239
+ local mt = getmetatable(object)
240
+ if mt then
241
+ object = mt.__index
242
+ else
243
+ object = nil
244
+ end
245
+ end
246
+ end
247
+
248
+ if typeof(class) == "string" then
249
+ return if typeof(object) == "Instance" then object:IsA(class) else typeof(object) == class
250
+ end
251
+
252
+ return false
253
+ end
254
+
255
+ @native
256
+ function CS.getAssemblyType(name)
257
+ local env
258
+ if getfenv == nil then
259
+ env = _ENV
260
+ else
261
+ env = getfenv()
262
+ end
263
+ return assemblyGlobal[name] or env[name]
264
+ end
265
+
266
+ CS.class("Exception", @native function()
267
+ local class = CS.classDef("Exception")
268
+
269
+ @native
270
+ function class.new(message: string?): Exception
271
+ local mt = {}
272
+ local self = CS.classInstance(class, mt) :: Exception
273
+
274
+ if message == nil then message = "An error occurred" end
275
+ self.Message = message
276
+
277
+ @native
278
+ function mt.__tostring(): string
279
+ return `{self["$className"]}: {self.Message}`
280
+ end
281
+
282
+ @native
283
+ function self.Throw(withinTryBlock: boolean): nil
284
+ error(if withinTryBlock then self else tostring(self))
285
+ return nil
286
+ end
287
+
288
+ return self
289
+ end
290
+
291
+ return class
292
+ end)
293
+
294
+ export type Exception = {
295
+ Message: string;
296
+ Throw: () -> nil;
297
+ }
298
+
299
+ type CatchBlock = {
300
+ exceptionClass: string;
301
+ block: (ex: Exception?, rethrow: () -> nil) -> nil
302
+ }
303
+
304
+ @native
305
+ function CS.try(block: () -> nil, finallyBlock: () -> nil, catchBlocks: { CatchBlock })
306
+ local success: boolean, ex: Exception | string | nil = pcall(block)
307
+ if not success then
308
+ if typeof(ex) == "string" then
309
+ ex = CS.getAssemblyType("Exception").new(ex, false)
310
+ end
311
+ for _, catchBlock in catchBlocks do
312
+ if catchBlock.exceptionClass ~= nil and catchBlock.exceptionClass ~= ex["$className"] then continue end
313
+ catchBlock.block(ex :: Exception, (ex :: Exception).Throw)
314
+ end
315
+ end
316
+ if finallyBlock ~= nil then
317
+ finallyBlock()
318
+ end
319
+ end
320
+
321
+ return CS
roblox-cs-master/RobloxCS/Logger.cs ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using Microsoft.CodeAnalysis;
2
+
3
+ namespace RobloxCS
4
+ {
5
+ internal static class Logger
6
+ {
7
+ public static void Ok(string message)
8
+ {
9
+ Log(message, ConsoleColor.Green, "OK");
10
+ }
11
+
12
+ public static void Info(string message)
13
+ {
14
+ Log(message, ConsoleColor.Cyan, "INFO");
15
+ }
16
+
17
+ public static void Error(string message)
18
+ {
19
+ Log(message, ConsoleColor.Red, "ERROR");
20
+ Environment.Exit(1);
21
+ }
22
+
23
+ public static void CompilerError(string message)
24
+ {
25
+ Error($"{message} (roblox-cs compiler error)");
26
+ }
27
+
28
+ public static void CodegenError(SyntaxToken token, string message)
29
+ {
30
+ var lineSpan = token.GetLocation().GetLineSpan();
31
+ Error($"{message}\n\t- {Utility.FormatLocation(lineSpan)}");
32
+ }
33
+
34
+ public static void CodegenWarning(SyntaxToken token, string message)
35
+ {
36
+ var lineSpan = token.GetLocation().GetLineSpan();
37
+ Warn($"{message}\n\t- {Utility.FormatLocation(lineSpan)}");
38
+ }
39
+
40
+ public static void UnsupportedError(SyntaxNode node, string subject, bool useIs = false, bool useYet = true)
41
+ {
42
+ CodegenError(node, $"{subject} {(useIs == true ? "is" : "are")} not {(useYet ? "yet " : "")} supported, sorry!");
43
+ }
44
+
45
+ public static void CodegenError(SyntaxNode node, string message)
46
+ {
47
+ CodegenError(node.GetFirstToken(), message);
48
+ }
49
+
50
+ public static void CodegenWarning(SyntaxNode node, string message)
51
+ {
52
+ CodegenWarning(node.GetFirstToken(), message);
53
+ }
54
+
55
+ public static void HandleDiagnostic(Diagnostic diagnostic)
56
+ {
57
+ HashSet<string> ignoredCodes = ["CS7022", "CS0017" /* more than one entry point */];
58
+ if (ignoredCodes.Contains(diagnostic.Id)) return;
59
+
60
+ var lineSpan = diagnostic.Location.GetLineSpan();
61
+ var diagnosticMessage = $"{diagnostic.Id}: {diagnostic.GetMessage()}";
62
+ var location = $"\n\t- {Utility.FormatLocation(lineSpan)}";
63
+ switch (diagnostic.Severity)
64
+ {
65
+ case DiagnosticSeverity.Error:
66
+ {
67
+ Error(diagnosticMessage + location);
68
+ break;
69
+ }
70
+ case DiagnosticSeverity.Warning:
71
+ {
72
+ if (diagnostic.IsWarningAsError)
73
+ {
74
+ Error(diagnosticMessage + location);
75
+ }
76
+ else
77
+ {
78
+ Warn(diagnosticMessage + location);
79
+ }
80
+ break;
81
+ }
82
+ case DiagnosticSeverity.Info:
83
+ {
84
+ Info(diagnosticMessage);
85
+ break;
86
+ }
87
+ }
88
+
89
+ }
90
+
91
+ public static void Warn(string message)
92
+ {
93
+ Log(message, ConsoleColor.Yellow, "WARN");
94
+ }
95
+
96
+ public static void Debug(string message)
97
+ {
98
+ Log(message, ConsoleColor.Magenta, "DEBUG");
99
+ }
100
+
101
+ private static void Log(string message, ConsoleColor color, string level)
102
+ {
103
+ var originalColor = Console.ForegroundColor;
104
+ Console.ForegroundColor = color;
105
+ Console.WriteLine($"[{level}] {message}");
106
+ Console.ForegroundColor = originalColor;
107
+ }
108
+ }
109
+ }
roblox-cs-master/RobloxCS/MemberCollector.cs ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using Microsoft.CodeAnalysis;
2
+ using Microsoft.CodeAnalysis.CSharp;
3
+ using Microsoft.CodeAnalysis.CSharp.Syntax;
4
+
5
+ namespace RobloxCS
6
+ {
7
+ public sealed class MemberCollectionResult
8
+ {
9
+ public Dictionary<NamespaceDeclarationSyntax, List<MemberDeclarationSyntax>> Namespaces { get; set; } = [];
10
+ }
11
+
12
+ public sealed class MemberCollector : CSharpSyntaxWalker
13
+ {
14
+ private readonly Dictionary<NamespaceDeclarationSyntax, List<MemberDeclarationSyntax>> _namespaces = [];
15
+ private readonly List<SyntaxTree> _trees;
16
+
17
+ public MemberCollector(List<SyntaxTree> trees)
18
+ {
19
+ _trees = trees;
20
+ }
21
+
22
+ public MemberCollectionResult Collect()
23
+ {
24
+ foreach (var tree in _trees)
25
+ {
26
+ Visit(tree.GetRoot());
27
+ }
28
+
29
+ return new MemberCollectionResult()
30
+ {
31
+ Namespaces = _namespaces
32
+ };
33
+ }
34
+
35
+ public override void VisitNamespaceDeclaration(NamespaceDeclarationSyntax node)
36
+ {
37
+ var members = _namespaces.ContainsKey(node) ? _namespaces[node] : new List<MemberDeclarationSyntax>();
38
+ foreach (var member in node.Members)
39
+ {
40
+ members.Add(member);
41
+ }
42
+ _namespaces[node] = members;
43
+ }
44
+ }
45
+ }
roblox-cs-master/RobloxCS/README.md ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # RobloxCS
2
+ This project includes the compiler and transformers.
3
+
4
+ ## To-do
5
+ - Array/dictionary, number, & (the rest of) string macros/extensions
6
+ - Macro `IEnumerable<T>` (or `Array`?) methods
7
+ - Classes/structs/interfaces nested in classes/structs/interfaces
8
+ - `LuaTuple` type handling
9
+ - Compile `operator` methods as regular method declarations but onto `mt`
10
+ - Transform parameterized class declarations (i.e `class Vector4(float x = 0, float y = 0, float z = 0, float w = 0)`) into regular class declarations with a constructor
11
+ - Switch expressions
12
+ - Destructuring/parenthesized variable designation (i.e. `var (value1, value2) = tuple;`)
13
+ - Emit virtual methods of interfaces similar to a class
14
+ - Abstract classes
15
+ - Type hoisting when outside of namespace
16
+ - Macro `GetType` function to the name of the type as a string (maybe)
17
+ - Async/await
18
+ - Overloaded methods
19
+ - Full qualification of types/namespaces inside of namespaces
20
+ - Macro `new T()` with collection types to `{}`
21
+ - Test `MainTransformer` more
22
+
23
+ ## Will maybe be supported
24
+ - [Class finalizers (destructors)](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/finalizers)
25
+ - Custom get/set methods
26
+ - Named arguments
27
+ - `yield` keyword
28
+ - `out` keyword
29
+ - `partial` keyword
30
+ - `using Name = Type` expressions (type aliases)
roblox-cs-master/RobloxCS/RobloxCS.csproj ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <Project Sdk="Microsoft.NET.Sdk">
2
+
3
+ <PropertyGroup>
4
+ <TargetFramework>net8.0</TargetFramework>
5
+ <ImplicitUsings>enable</ImplicitUsings>
6
+ <Nullable>enable</Nullable>
7
+ <Title>RobloxCS</Title>
8
+ <Version>1.2.0</Version>
9
+ <PackageId>RobloxCS.Compiler</PackageId>
10
+ </PropertyGroup>
11
+
12
+ <ItemGroup>
13
+ <PackageReference Include="Microsoft.CodeAnalysis" Version="4.10.0" />
14
+ <PackageReference Include="YamlDotNet" Version="15.3.0" />
15
+ <PackageReference Include="RobloxCS.Types" Version="*" />
16
+ </ItemGroup>
17
+
18
+ </Project>
roblox-cs-master/RobloxCS/RojoReader.cs ADDED
@@ -0,0 +1,186 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using Microsoft.CodeAnalysis;
2
+ using System.Text;
3
+ using System.Text.Json;
4
+ using System.Text.Json.Serialization;
5
+
6
+ #pragma warning disable CS8618
7
+ public sealed class RojoProject
8
+ {
9
+ [JsonPropertyName("name")]
10
+ public string Name { get; set; }
11
+
12
+ [JsonPropertyName("tree")]
13
+ public InstanceDescription Tree { get; set; }
14
+
15
+ [JsonPropertyName("servePort")]
16
+ public int ServePort { get; set; } = 34872;
17
+
18
+ [JsonPropertyName("servePlaceIds")]
19
+ public List<ulong> ServePlaceIds { get; set; } = [];
20
+
21
+ [JsonPropertyName("placeId")]
22
+ public string? PlaceId { get; set; }
23
+
24
+ [JsonPropertyName("gameId")]
25
+ public string? GameId { get; set; }
26
+
27
+ [JsonPropertyName("serveAddress")]
28
+ public string? ServeAddress { get; set; }
29
+
30
+ [JsonPropertyName("globIgnorePaths")]
31
+ public List<string> GlobIgnorePaths { get; set; } = [];
32
+
33
+ [JsonPropertyName("emitLegacyScripts")]
34
+ public bool EmitLegacyScripts { get; set; } = true;
35
+
36
+ public bool IsValid()
37
+ {
38
+ return !string.IsNullOrEmpty(Name) && Tree != null;
39
+ }
40
+ }
41
+
42
+ public sealed class InstanceDescription
43
+ {
44
+ [JsonPropertyName("$className")]
45
+ public string? ClassName { get; set; }
46
+ [JsonPropertyName("$path")]
47
+ public string? Path { get; set; }
48
+ [JsonPropertyName("$properties")]
49
+ public Dictionary<string, object>? Properties { get; set; }
50
+ [JsonPropertyName("$ignoreUnknownInstances")]
51
+ public bool IgnoreUnknownInstances { get; set; } = true;
52
+ public Dictionary<string, InstanceDescription> Instances { get; set; } = [];
53
+
54
+ [JsonExtensionData]
55
+ public IDictionary<string, JsonElement> AdditionalData { get; set; } = new Dictionary<string, JsonElement>();
56
+
57
+ public void OnDeserialized()
58
+ {
59
+ foreach (var kvp in AdditionalData)
60
+ {
61
+ var childInstance = kvp.Value.Deserialize<InstanceDescription>()!;
62
+ Instances[kvp.Key] = childInstance;
63
+ childInstance.OnDeserialized();
64
+ }
65
+ }
66
+ }
67
+ #pragma warning restore CS8618
68
+
69
+ namespace RobloxCS
70
+ {
71
+ public static class RojoReader
72
+ {
73
+ private static readonly List<string> _services = ["ReplicatedStorage", "ReplicatedFirst", "ServerStorage", "ServerScriptService", "StarterPlayer", "StarterPlayerScripts"]; // things that will be converted into game:GetService("XXX")
74
+ private static readonly Dictionary<string, string> _instanceNameMap = new Dictionary<string, string>
75
+ {
76
+ { "StarterPlayer", "game:GetService(\"Players\").LocalPlayer" },
77
+ { "StarterPlayerScripts", "PlayerScripts" }
78
+ };
79
+
80
+ public static RojoProject Read(string configPath)
81
+ {
82
+ var jsonContent = "";
83
+ RojoProject? project = default;
84
+
85
+ try
86
+ {
87
+ jsonContent = File.ReadAllText(configPath);
88
+ }
89
+ catch (Exception e)
90
+ {
91
+ FailToRead(configPath, e.Message);
92
+ }
93
+
94
+ try
95
+ {
96
+ project = JsonSerializer.Deserialize<RojoProject>(jsonContent);
97
+ }
98
+ catch (Exception e)
99
+ {
100
+ FailToRead(configPath, e.ToString());
101
+ }
102
+
103
+ if (project == null || !project.IsValid())
104
+ {
105
+ FailToRead(configPath, "Invalid Rojo project! Make sure it has all required fields ('name' and 'tree').");
106
+ }
107
+
108
+ UpdateChildInstances(project!.Tree);
109
+ return project!;
110
+ }
111
+
112
+ public static string? FindProjectPath(string directoryPath, string projectName)
113
+ {
114
+ return Directory.GetFiles(directoryPath).FirstOrDefault(file => Path.GetFileName(file) == $"{projectName}.project.json");
115
+ }
116
+
117
+ public static string? ResolveInstancePath(RojoProject project, string filePath)
118
+ {
119
+ var path = TraverseInstanceTree(project.Tree, Utility.FixPathSep(filePath));
120
+ return path == null ? null : FormatInstancePath(Utility.FixPathSep(path));
121
+ }
122
+
123
+ private static string? TraverseInstanceTree(InstanceDescription instance, string filePath)
124
+ {
125
+ var instancePath = instance.Path != null ? Utility.FixPathSep(instance.Path) : null;
126
+ if (instancePath != null && filePath.StartsWith(instancePath))
127
+ {
128
+ var remainingPath = filePath.Substring(instancePath.Length + 1); // +1 to omit '/'
129
+ return Path.ChangeExtension(remainingPath, null);
130
+ }
131
+
132
+ foreach (var childInstance in instance.Instances)
133
+ {
134
+ var result = TraverseInstanceTree(childInstance.Value, filePath);
135
+ var leftName = childInstance.Key;
136
+ if (_instanceNameMap.TryGetValue(leftName, out var mappedName))
137
+ {
138
+ leftName = mappedName;
139
+ }
140
+
141
+ if (result != null)
142
+ {
143
+ return $"{leftName}/{result}";
144
+ }
145
+ }
146
+
147
+ return null;
148
+ }
149
+
150
+ private static string FormatInstancePath(string path)
151
+ {
152
+ var segments = path.Split('/');
153
+ var formattedPath = new StringBuilder();
154
+ foreach (var segment in segments)
155
+ {
156
+ var isServiceIdentifier = _services.Contains(segment);
157
+ if (segment == segments.First())
158
+ {
159
+ formattedPath.Append(isServiceIdentifier ? $"game:GetService(\"{segment}\")" : segment);
160
+ }
161
+ else
162
+ {
163
+ formattedPath.Append(formattedPath.Length > 0 ? "[\"" : "");
164
+ formattedPath.Append(segment);
165
+ formattedPath.Append("\"]");
166
+ }
167
+ }
168
+
169
+ return formattedPath.ToString();
170
+ }
171
+
172
+ private static void UpdateChildInstances(InstanceDescription instance)
173
+ {
174
+ instance.OnDeserialized();
175
+ foreach (var childInstance in instance.Instances.Values)
176
+ {
177
+ UpdateChildInstances(childInstance);
178
+ }
179
+ }
180
+
181
+ private static void FailToRead(string configPath, string message)
182
+ {
183
+ Logger.Error($"Failed to read {configPath}!\n{message}");
184
+ }
185
+ }
186
+ }
roblox-cs-master/RobloxCS/SymbolAnalyzer.cs ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using Microsoft.CodeAnalysis;
2
+ using Microsoft.CodeAnalysis.CSharp.Syntax;
3
+
4
+ namespace RobloxCS
5
+ {
6
+ internal sealed class SymbolAnalyzerResults
7
+ {
8
+ public List<INamespaceOrTypeSymbol> TypesWithMembersUsedAsValues { get; set; } = new List<INamespaceOrTypeSymbol>();
9
+
10
+ public bool TypeHasMemberUsedAsValue(INamespaceOrTypeSymbol namespaceOrType)
11
+ {
12
+ return TypesWithMembersUsedAsValues.Any(symbol => SymbolEqualityComparer.Default.Equals(symbol, namespaceOrType));
13
+ }
14
+ }
15
+
16
+ internal sealed class SymbolAnalyzer
17
+ {
18
+ private readonly SyntaxTree _tree;
19
+ private readonly SemanticModel _semanticModel;
20
+ private readonly SymbolAnalyzerResults _results = new SymbolAnalyzerResults();
21
+
22
+ public SymbolAnalyzer(SyntaxTree tree, SemanticModel semanticModel)
23
+ {
24
+ _tree = tree;
25
+ _semanticModel = semanticModel;
26
+ }
27
+
28
+ public SymbolAnalyzerResults Analyze()
29
+ {
30
+ AnalyzeUsings();
31
+ AnalyzeNamespaces();
32
+ return _results;
33
+ }
34
+
35
+ private void AnalyzeNamespaces()
36
+ {
37
+ var root = _tree.GetRoot();
38
+ var namespaceDeclarations = root.DescendantNodes().OfType<NamespaceDeclarationSyntax>();
39
+
40
+ foreach (var namespaceDeclaration in namespaceDeclarations)
41
+ {
42
+ var symbolInfo = _semanticModel.GetSymbolInfo(namespaceDeclaration.Name);
43
+ if (symbolInfo.Symbol is INamespaceOrTypeSymbol namespaceOrTypeSymbol && HasMembersUsedAsValues(namespaceOrTypeSymbol, root))
44
+ {
45
+ _results.TypesWithMembersUsedAsValues.Add(namespaceOrTypeSymbol);
46
+ }
47
+ }
48
+ }
49
+
50
+ private void AnalyzeUsings()
51
+ {
52
+ var root = _tree.GetRoot();
53
+ var usingDirectives = root.DescendantNodes().OfType<UsingDirectiveSyntax>();
54
+
55
+ foreach (var usingDirective in usingDirectives)
56
+ {
57
+ if (usingDirective.Name == null) continue;
58
+
59
+ var symbolInfo = _semanticModel.GetSymbolInfo(usingDirective.Name);
60
+ if (symbolInfo.Symbol is INamespaceOrTypeSymbol namespaceOrTypeSymbol && HasMembersUsedAsValues(namespaceOrTypeSymbol, root))
61
+ {
62
+ _results.TypesWithMembersUsedAsValues.Add(namespaceOrTypeSymbol);
63
+ }
64
+ }
65
+ }
66
+
67
+ private bool HasMembersUsedAsValues(INamespaceOrTypeSymbol namespaceOrTypeSymbol, SyntaxNode root)
68
+ {
69
+ var typeUsages = root.DescendantNodes()
70
+ .OfType<IdentifierNameSyntax>()
71
+ .Where(identifier =>
72
+ _semanticModel.GetSymbolInfo(identifier).Symbol is INamedTypeSymbol symbol
73
+ && SymbolEqualityComparer.Default.Equals(symbol.ContainingNamespace, namespaceOrTypeSymbol)
74
+ );
75
+
76
+ foreach (var usage in typeUsages)
77
+ {
78
+ if (IsUsedAsValue(usage))
79
+ {
80
+ return true;
81
+ }
82
+ }
83
+ return false;
84
+ }
85
+
86
+ private bool IsUsedAsValue(SyntaxNode node)
87
+ {
88
+ // Check for object instantiation, member access, or variable declarator
89
+ if (node.Parent is ObjectCreationExpressionSyntax ||
90
+ node.Parent is MemberAccessExpressionSyntax ||
91
+ node.Parent is VariableDeclaratorSyntax ||
92
+ node.Parent is AssignmentExpressionSyntax)
93
+ {
94
+ return true;
95
+ }
96
+
97
+ // Check if used in a method invocation
98
+ if (node.Parent is InvocationExpressionSyntax invocation)
99
+ {
100
+ var symbolInfo = _semanticModel.GetSymbolInfo(invocation);
101
+ if (symbolInfo.Symbol is IMethodSymbol methodSymbol)
102
+ {
103
+ return methodSymbol.Parameters.Any(p => SymbolEqualityComparer.Default.Equals(p.Type, _semanticModel.GetTypeInfo(node).Type));
104
+ }
105
+ }
106
+
107
+ return false;
108
+ }
109
+ }
110
+ }
roblox-cs-master/RobloxCS/Transformers/BaseTransformer.cs ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using Microsoft.CodeAnalysis;
2
+ using Microsoft.CodeAnalysis.CSharp;
3
+
4
+ namespace RobloxCS
5
+ {
6
+ public abstract class BaseTransformer : CSharpSyntaxRewriter
7
+ {
8
+ protected SyntaxNode _root;
9
+ protected readonly SyntaxTree _tree;
10
+ protected readonly ConfigData _config;
11
+
12
+ public BaseTransformer(SyntaxTree tree, ConfigData config)
13
+ {
14
+ _root = tree.GetRoot();
15
+ _tree = tree;
16
+ _config = config;
17
+ }
18
+
19
+ public SyntaxTree TransformTree()
20
+ {
21
+ return _tree.WithRootAndOptions(Visit(_root), _tree.Options);
22
+ }
23
+
24
+ protected string? TryGetName(SyntaxNode node)
25
+ {
26
+ return Utility.GetNamesFromNode(node).FirstOrDefault();
27
+ }
28
+
29
+ protected string GetName(SyntaxNode node)
30
+ {
31
+ return Utility.GetNamesFromNode(node).First();
32
+ }
33
+
34
+ protected SyntaxToken CreateIdentifierToken(string text, string? valueText = null, SyntaxTriviaList? trivia = null)
35
+ {
36
+ var triviaList = trivia ?? SyntaxFactory.TriviaList();
37
+ return SyntaxFactory.VerbatimIdentifier(triviaList, text, valueText ?? text, triviaList);
38
+ }
39
+ }
40
+ }
roblox-cs-master/RobloxCS/Transformers/BuiltInTransformers.cs ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using Microsoft.CodeAnalysis;
2
+
3
+ namespace RobloxCS
4
+ {
5
+ using TransformMethod = Func<SyntaxTree, ConfigData, SyntaxTree>;
6
+
7
+ public static partial class BuiltInTransformers
8
+ {
9
+ public static TransformMethod Main()
10
+ {
11
+ return (tree, config) => new MainTransformer(tree, config).TransformTree();
12
+ }
13
+
14
+ public static TransformMethod Get(string name)
15
+ {
16
+ return name.ToLower() switch
17
+ {
18
+ "debug" => (tree, config) => new DebugTransformer(tree, config).TransformTree(),
19
+ _ => FailedToGetTransformer(name)
20
+ };
21
+ }
22
+
23
+ private static TransformMethod FailedToGetTransformer(string name)
24
+ {
25
+ Logger.Error($"No built-in transformer \"{name}\" exists (roblox-cs.yml)");
26
+ return null!; // hack
27
+ }
28
+ }
29
+ }
roblox-cs-master/RobloxCS/Transformers/DebugTransformer.cs ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using Microsoft.CodeAnalysis;
2
+ using Microsoft.CodeAnalysis.CSharp;
3
+ using Microsoft.CodeAnalysis.CSharp.Syntax;
4
+
5
+ namespace RobloxCS
6
+ {
7
+ public sealed class DebugTransformer : BaseTransformer
8
+ {
9
+ public DebugTransformer(SyntaxTree tree, ConfigData config)
10
+ : base(tree, config)
11
+ {
12
+ }
13
+
14
+ public override SyntaxNode? VisitInvocationExpression(InvocationExpressionSyntax node)
15
+ {
16
+ if (node.Expression is IdentifierNameSyntax || node.Expression is MemberAccessExpressionSyntax)
17
+ {
18
+ {
19
+ if (node.Expression is MemberAccessExpressionSyntax memberAccess)
20
+ {
21
+ if (Utility.GetNamesFromNode(memberAccess.Expression).LastOrDefault() != "Globals")
22
+ {
23
+ goto DoNothing;
24
+ }
25
+ }
26
+ }
27
+ {
28
+ var name = GetName(node.Expression is MemberAccessExpressionSyntax memberAccess ? memberAccess.Name : node.Expression);
29
+ HashSet<string> concatMethodNames = ["error"];
30
+ HashSet<string> extraArgNames = ["print", "warn"];
31
+
32
+ var newNode = node;
33
+ if (concatMethodNames.Contains(name))
34
+ {
35
+ newNode = ConcatenateFileInfoToMessageArgument(node);
36
+ }
37
+ else if (extraArgNames.Contains(name))
38
+ {
39
+ newNode = PrependFileInfoArgument(node);
40
+ }
41
+
42
+ return base.VisitInvocationExpression(node.Expression is MemberAccessExpressionSyntax _memberAccess ? newNode.WithExpression(_memberAccess.Name) : newNode);
43
+ }
44
+ }
45
+
46
+ DoNothing:
47
+ return base.VisitInvocationExpression(node);
48
+ }
49
+
50
+ private InvocationExpressionSyntax ConcatenateFileInfoToMessageArgument(InvocationExpressionSyntax node)
51
+ {
52
+ var messageArgument = node.ArgumentList.Arguments.First().Expression;
53
+ var fileInfoLiteral = GetFileInfoLiteral(node, addSpace: true);
54
+ var emptyTrivia = SyntaxFactory.TriviaList();
55
+ var plusToken = SyntaxFactory.Token(emptyTrivia, SyntaxKind.PlusToken, "+", "", emptyTrivia);
56
+ var binaryExpression = SyntaxFactory.BinaryExpression(SyntaxKind.AddExpression, fileInfoLiteral, plusToken, messageArgument);
57
+ var newArgument = SyntaxFactory.Argument(binaryExpression);
58
+ var newArguments = SeparatedSyntaxList.Create(new ReadOnlySpan<ArgumentSyntax>(ref newArgument));
59
+ var newArgumentListNode = node.ArgumentList.WithArguments(newArguments);
60
+ return node.WithArgumentList(newArgumentListNode);
61
+ }
62
+
63
+ private InvocationExpressionSyntax PrependFileInfoArgument(InvocationExpressionSyntax node)
64
+ {
65
+ var fileInfoLiteral = GetFileInfoLiteral(node, addSpace: false);
66
+ var argument = SyntaxFactory.Argument(fileInfoLiteral);
67
+ var newArguments = SeparatedSyntaxList.Create(new ReadOnlySpan<ArgumentSyntax>(new List<ArgumentSyntax> { argument }.Concat(node.ArgumentList.Arguments).ToArray())); // good lord why do they make that so convoluted
68
+ var newArgumentListNode = node.ArgumentList.WithArguments(newArguments);
69
+ return node.WithArgumentList(newArgumentListNode);
70
+ }
71
+
72
+ private LiteralExpressionSyntax GetFileInfoLiteral(SyntaxNode node, bool addSpace)
73
+ {
74
+ var fileLocation = FormatLocation(node);
75
+ var infoText = $"[{fileLocation}]:" + (addSpace ? " " : "");
76
+ var emptyTrivia = SyntaxFactory.TriviaList();
77
+ var token = SyntaxFactory.Token(emptyTrivia, SyntaxKind.StringLiteralToken, infoText, infoText, emptyTrivia); // why is trivia required bruh
78
+ return SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, token);
79
+ }
80
+
81
+ private string FormatLocation(SyntaxNode node)
82
+ {
83
+ var location = Utility.FormatLocation(node.GetLocation().GetLineSpan())
84
+ .Replace(_config.SourceFolder + "/", "")
85
+ .Replace("..", "./")
86
+ .Replace("./", "");
87
+
88
+ return location;
89
+ }
90
+ }
91
+ }
roblox-cs-master/RobloxCS/Transformers/MainTransformer.cs ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using Microsoft.CodeAnalysis;
2
+ using Microsoft.CodeAnalysis.CSharp;
3
+ using Microsoft.CodeAnalysis.CSharp.Syntax;
4
+
5
+ namespace RobloxCS
6
+ {
7
+ internal sealed class MainTransformer : BaseTransformer
8
+ {
9
+ public MainTransformer(SyntaxTree tree, ConfigData config)
10
+ : base(tree, config)
11
+ {
12
+ }
13
+
14
+ public override SyntaxNode? VisitCompilationUnit(CompilationUnitSyntax node)
15
+ {
16
+ var usings = node.Usings;
17
+ usings = usings.Add(SyntaxFactory.UsingDirective(SyntaxFactory.IdentifierName("Roblox")));
18
+ usings = usings.Add(SyntaxFactory.UsingDirective(SyntaxFactory.Token(SyntaxKind.StaticKeyword), null, SyntaxFactory.QualifiedName(SyntaxFactory.IdentifierName("Roblox"), SyntaxFactory.IdentifierName("Globals"))));
19
+ return base.VisitCompilationUnit(node.WithUsings(usings));
20
+ }
21
+
22
+ public override SyntaxNode? VisitFileScopedNamespaceDeclaration(FileScopedNamespaceDeclarationSyntax node)
23
+ {
24
+ return VisitNamespaceDeclaration(SyntaxFactory.NamespaceDeclaration(node.AttributeLists, node.Modifiers, node.Name, node.Externs, node.Usings, node.Members));
25
+ }
26
+
27
+ public override SyntaxNode? VisitDoStatement(DoStatementSyntax node)
28
+ {
29
+ // invert condition
30
+ return base.VisitDoStatement(node.WithCondition(SyntaxFactory.PrefixUnaryExpression(SyntaxKind.LogicalNotExpression, node.Condition)));
31
+ }
32
+
33
+ public override SyntaxNode? VisitBinaryExpression(BinaryExpressionSyntax node)
34
+ {
35
+ if (node.OperatorToken.Text == "is")
36
+ {
37
+ var pattern = SyntaxFactory.TypePattern(SyntaxFactory.ParseTypeName(((IdentifierNameSyntax)node.Right).Identifier.Text));
38
+ return SyntaxFactory.IsPatternExpression(node.Left, pattern);
39
+ }
40
+ return base.VisitBinaryExpression(node);
41
+ }
42
+
43
+ public override SyntaxNode? VisitMethodDeclaration(MethodDeclarationSyntax node)
44
+ {
45
+ var newNode = node.Identifier.ValueText switch
46
+ {
47
+ "ToString" => node.WithIdentifier(CreateIdentifierToken("__tostring")),
48
+ "Equals" => node.WithIdentifier(CreateIdentifierToken("__eq")),
49
+ _ => node
50
+ };
51
+ if (node != newNode && HasSyntax(newNode.Modifiers, SyntaxKind.OverrideKeyword))
52
+ {
53
+ var newModifiers = newNode.Modifiers.RemoveAt(newNode.Modifiers.Select(token => token.Kind()).ToList().IndexOf(SyntaxKind.OverrideKeyword));
54
+ newNode = newNode.WithModifiers(newModifiers);
55
+ }
56
+ return base.VisitMethodDeclaration(newNode);
57
+ }
58
+
59
+ public override SyntaxNode? VisitIdentifierName(IdentifierNameSyntax node)
60
+ {
61
+ var identifierText = node.Identifier.Text;
62
+ if (!identifierText.Contains("@") || identifierText == "var")
63
+ {
64
+ return base.VisitIdentifierName(node);
65
+ }
66
+
67
+ var fixedIdentifierText = identifierText.Replace("@", "");
68
+ var newToken = CreateIdentifierToken(fixedIdentifierText);
69
+ return base.VisitIdentifierName(node.WithIdentifier(newToken));
70
+ }
71
+
72
+ public override SyntaxNode? VisitArgument(ArgumentSyntax node)
73
+ {
74
+ if (node.Expression.IsKind(SyntaxKind.IdentifierName))
75
+ {
76
+ var newExpression = VisitIdentifierName((IdentifierNameSyntax)node.Expression);
77
+ return base.VisitArgument(node.WithExpression((ExpressionSyntax)newExpression!));
78
+ }
79
+ return base.VisitArgument(node);
80
+ }
81
+
82
+ public override SyntaxNode? VisitConditionalAccessExpression(ConditionalAccessExpressionSyntax node)
83
+ {
84
+ var whenNotNull = ProcessWhenNotNull(node.Expression, node.WhenNotNull);
85
+ if (whenNotNull != null)
86
+ {
87
+ return base.VisitConditionalAccessExpression(node.WithWhenNotNull(whenNotNull));
88
+ }
89
+ return base.VisitConditionalAccessExpression(node);
90
+ }
91
+
92
+ private static bool HasSyntax(SyntaxTokenList tokens, SyntaxKind syntax)
93
+ {
94
+ return tokens.Any(token => token.IsKind(syntax));
95
+ }
96
+
97
+ private ExpressionSyntax? ProcessWhenNotNull(ExpressionSyntax expression, ExpressionSyntax whenNotNull)
98
+ {
99
+ if (whenNotNull == null)
100
+ {
101
+ return null;
102
+ }
103
+
104
+ switch (whenNotNull)
105
+ {
106
+ case MemberBindingExpressionSyntax memberBinding:
107
+ return SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, expression, memberBinding.Name);
108
+ case InvocationExpressionSyntax invocation:
109
+ return invocation.WithExpression((invocation.Expression switch
110
+ {
111
+ MemberAccessExpressionSyntax memberAccess => SyntaxFactory.MemberAccessExpression(
112
+ SyntaxKind.SimpleMemberAccessExpression,
113
+ expression,
114
+ memberAccess.Name
115
+ ),
116
+ ConditionalAccessExpressionSyntax nestedConditional => ProcessWhenNotNull(nestedConditional.WhenNotNull, expression),
117
+ MemberBindingExpressionSyntax memberBinding => SyntaxFactory.MemberAccessExpression(
118
+ SyntaxKind.SimpleMemberAccessExpression,
119
+ expression,
120
+ memberBinding.Name
121
+ ),
122
+ _ => SyntaxFactory.MemberAccessExpression(
123
+ SyntaxKind.SimpleMemberAccessExpression,
124
+ expression,
125
+ SyntaxFactory.IdentifierName(invocation.Expression.ToString())
126
+ )
127
+ })!);
128
+ case ConditionalAccessExpressionSyntax conditionalAccess:
129
+ return conditionalAccess
130
+ .WithExpression(ProcessWhenNotNull(expression, conditionalAccess.Expression) ?? conditionalAccess.Expression)
131
+ .WithWhenNotNull(ProcessWhenNotNull(expression, conditionalAccess.WhenNotNull) ?? conditionalAccess.WhenNotNull);
132
+ default:
133
+ return null;
134
+ };
135
+ }
136
+ }
137
+ }
roblox-cs-master/RobloxCS/Transpiler.cs ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using Microsoft.CodeAnalysis;
2
+ using Microsoft.CodeAnalysis.CSharp;
3
+ using Microsoft.CodeAnalysis.CSharp.Syntax;
4
+
5
+ namespace RobloxCS
6
+ {
7
+ public sealed class Transpiler
8
+ {
9
+ public readonly ConfigData Config;
10
+
11
+ private const string _includeFolderName = "Include";
12
+ private List<SyntaxTree> _fileTrees = new List<SyntaxTree>();
13
+ private readonly string _inputDirectory;
14
+ private readonly string _sourceDirectory;
15
+ private readonly string _outDirectory;
16
+
17
+ public Transpiler(string inputDirectory)
18
+ {
19
+ Config = ConfigReader.Read(inputDirectory);
20
+ _inputDirectory = inputDirectory;
21
+ _sourceDirectory = inputDirectory + "/" + Config.SourceFolder;
22
+ _outDirectory = inputDirectory + "/" + Config.OutputFolder;
23
+ }
24
+
25
+ public void Transpile()
26
+ {
27
+ ParseSource();
28
+ var compiler = CompileASTs();
29
+ CopyIncludedLua();
30
+ WriteLuaOutput(compiler);
31
+ }
32
+
33
+ private void ParseSource()
34
+ {
35
+ if (!Directory.Exists(_sourceDirectory))
36
+ {
37
+ Logger.Error($"Source folder \"{Config.SourceFolder}\" does not exist!");
38
+ }
39
+
40
+ var sourceFiles = FileManager.GetSourceFiles(_sourceDirectory);
41
+ foreach (var sourceFile in sourceFiles)
42
+ {
43
+ var fileContents = File.ReadAllText(sourceFile);
44
+ var tree = TranspilerUtility.ParseTree(fileContents, sourceFile);
45
+ HashSet<Func<SyntaxTree, ConfigData, SyntaxTree>> transformers = [BuiltInTransformers.Main()];
46
+
47
+ foreach (var transformerName in Config.EnabledBuiltInTransformers)
48
+ {
49
+ transformers.Add(BuiltInTransformers.Get(transformerName));
50
+ }
51
+
52
+ var transformedTree = TranspilerUtility.TransformTree(tree, transformers, Config);
53
+ foreach (var diagnostic in transformedTree.GetDiagnostics())
54
+ {
55
+ Logger.HandleDiagnostic(diagnostic);
56
+ }
57
+
58
+ _fileTrees.Add(transformedTree);
59
+ }
60
+ }
61
+
62
+ private CSharpCompilation CompileASTs()
63
+ {
64
+ var compiler = TranspilerUtility.GetCompiler(_fileTrees, Config);
65
+ foreach (var diagnostic in compiler.GetDiagnostics())
66
+ {
67
+ Logger.HandleDiagnostic(diagnostic);
68
+ }
69
+
70
+ return compiler;
71
+ }
72
+
73
+ private void CopyIncludedLua()
74
+ {
75
+ var rbxcsDirectory = Utility.GetRbxcsDirectory();
76
+ if (rbxcsDirectory == null)
77
+ {
78
+ Logger.CompilerError("Failed to find RobloxCS directory");
79
+ return;
80
+ }
81
+
82
+ var compilerDirectory = Utility.FixPathSep(Path.Combine(rbxcsDirectory, "RobloxCS"));
83
+ var includeDirectory = Utility.FixPathSep(Path.Combine(compilerDirectory, _includeFolderName));
84
+ var destinationIncludeDirectory = includeDirectory
85
+ .Replace(compilerDirectory, _inputDirectory)
86
+ .Replace(_includeFolderName, _includeFolderName.ToLower());
87
+
88
+ try
89
+ {
90
+ FileManager.CopyDirectory(includeDirectory, destinationIncludeDirectory);
91
+ }
92
+ catch (Exception e)
93
+ {
94
+ Logger.Error($"Failed to copy included Lua files: {e.Message}");
95
+ }
96
+ }
97
+
98
+ private void WriteLuaOutput(CSharpCompilation compiler)
99
+ {
100
+ var compiledFiles = new List<CompiledFile>();
101
+ var memberCollector = new MemberCollector(_fileTrees);
102
+ var members = memberCollector.Collect();
103
+ if (Config.CSharpOptions.EntryPointRequired && _fileTrees.All(tree => !tree.GetRoot().DescendantNodes().Any(node => node is ClassDeclarationSyntax classDeclaration && Utility.GetNamesFromNode(classDeclaration).FirstOrDefault() == Config.CSharpOptions.EntryPointName)))
104
+ {
105
+ Logger.Error($"No entry point class \"{Config.CSharpOptions.EntryPointName}\" found!");
106
+ }
107
+
108
+ foreach (var tree in _fileTrees)
109
+ {
110
+ var generatedLua = TranspilerUtility.GenerateLua(tree, compiler, members, _inputDirectory, Config);
111
+ var targetPath = tree.FilePath.Replace(Config.SourceFolder, Config.OutputFolder).Replace(".cs", ".lua");
112
+ compiledFiles.Add(new CompiledFile(targetPath, generatedLua));
113
+ }
114
+
115
+ EnsureDirectoriesExist();
116
+ FileManager.WriteCompiledFiles(_outDirectory, compiledFiles);
117
+ }
118
+
119
+ private void EnsureDirectoriesExist()
120
+ {
121
+ var subDirectories = Directory.GetDirectories(_sourceDirectory, "*", SearchOption.AllDirectories);
122
+ foreach (string subDirectory in subDirectories)
123
+ {
124
+ Directory.CreateDirectory(subDirectory.Replace(Config.SourceFolder, Config.OutputFolder));
125
+ }
126
+ }
127
+ }
128
+ }
roblox-cs-master/RobloxCS/TranspilerUtility.cs ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using System.Reflection;
2
+ using Microsoft.CodeAnalysis;
3
+ using Microsoft.CodeAnalysis.CSharp;
4
+ using Microsoft.CodeAnalysis.CSharp.Syntax;
5
+
6
+ namespace RobloxCS
7
+ {
8
+ public static class TranspilerUtility
9
+ {
10
+ public static RojoProject? GetRojoProject(string inputDirectory, string projectName)
11
+ {
12
+ if (projectName == "UNIT_TESTING") return null;
13
+
14
+ var path = RojoReader.FindProjectPath(inputDirectory, projectName);
15
+ if (path == null)
16
+ {
17
+ Logger.Error($"Failed to find Rojo project file \"{projectName}.project.json\"!");
18
+ return null!;
19
+ }
20
+
21
+ return RojoReader.Read(path);
22
+ }
23
+
24
+ public static string CleanUpLuaForTests(string luaSource, int? extraLines)
25
+ {
26
+ var lines = luaSource.Split('\n').ToList();
27
+ lines.RemoveRange(0, 2 + (extraLines ?? 0));
28
+
29
+ return string.Join('\n', lines).Replace("\r", "").Trim();
30
+ }
31
+
32
+ public static string GenerateLua(
33
+ SyntaxTree tree,
34
+ CSharpCompilation compiler,
35
+ MemberCollectionResult members,
36
+ string inputDirectory = "",
37
+ ConfigData? config = null
38
+ )
39
+ {
40
+ config ??= ConfigReader.UnitTestingConfig;
41
+ var rojoProject = GetRojoProject(inputDirectory, config.RojoProjectName);
42
+ var codeGenerator = new CodeGenerator(tree, compiler, rojoProject, members, config, Utility.FixPathSep(inputDirectory));
43
+ return codeGenerator.GenerateLua();
44
+ }
45
+
46
+ public static CSharpCompilation GetCompiler(List<SyntaxTree> trees, ConfigData? config = null)
47
+ {
48
+ config ??= ConfigReader.UnitTestingConfig;
49
+ var compilationOptions = new CSharpCompilationOptions(OutputKind.ConsoleApplication);
50
+ var compiler = CSharpCompilation.Create(
51
+ assemblyName: config.CSharpOptions.AssemblyName,
52
+ syntaxTrees: trees,
53
+ references: GetCompilationReferences(),
54
+ options: compilationOptions
55
+ );
56
+
57
+ return compiler;
58
+ }
59
+
60
+ public static SyntaxTree TransformTree(SyntaxTree cleanTree, HashSet<Func<SyntaxTree, ConfigData, SyntaxTree>> transformMethods, ConfigData? config = null)
61
+ {
62
+ config ??= ConfigReader.UnitTestingConfig;
63
+
64
+ var tree = cleanTree;
65
+ foreach (var transform in transformMethods)
66
+ {
67
+ tree = transform(tree, config);
68
+ }
69
+ return tree;
70
+ }
71
+
72
+ public static SyntaxTree ParseTree(string source, string sourceFile = "TestFile.client.cs")
73
+ {
74
+ var cleanTree = CSharpSyntaxTree.ParseText(source);
75
+ var compilationUnit = (CompilationUnitSyntax)cleanTree.GetRoot();
76
+ var usingDirective = SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System"));
77
+ var newRoot = compilationUnit.AddUsings(usingDirective);
78
+ return cleanTree
79
+ .WithRootAndOptions(newRoot, cleanTree.Options)
80
+ .WithFilePath(sourceFile);
81
+ }
82
+
83
+ private static List<PortableExecutableReference> GetCompilationReferences()
84
+ {
85
+ var runtimeLibAssemblyPath = string.Join('/', Utility.GetAssemblyDirectory(), Utility.RuntimeAssemblyName + ".dll");
86
+ if (!File.Exists(runtimeLibAssemblyPath))
87
+ {
88
+ var directoryName = Path.GetDirectoryName(runtimeLibAssemblyPath);
89
+ Logger.Error($"Failed to find {Utility.RuntimeAssemblyName}.dll in {(directoryName == null ? "(could not find assembly directory)" : Utility.FixPathSep(directoryName))}");
90
+ }
91
+
92
+ var references = new List<PortableExecutableReference>()
93
+ {
94
+ MetadataReference.CreateFromFile(runtimeLibAssemblyPath)
95
+ };
96
+
97
+ foreach (var coreLibReference in GetCoreLibReferences())
98
+ {
99
+ references.Add(coreLibReference);
100
+ }
101
+ return references;
102
+ }
103
+
104
+ private static HashSet<PortableExecutableReference> GetCoreLibReferences()
105
+ {
106
+ var coreLib = typeof(object).GetTypeInfo().Assembly.Location;
107
+ HashSet<string> coreDlls = ["System.Runtime.dll", "System.Core.dll", "System.Collections.dll"];
108
+ HashSet<PortableExecutableReference> references = [MetadataReference.CreateFromFile(coreLib)];
109
+
110
+ foreach (var coreDll in coreDlls)
111
+ {
112
+ var dllPath = Path.Combine(Path.GetDirectoryName(coreLib)!, coreDll);
113
+ references.Add(MetadataReference.CreateFromFile(dllPath));
114
+ }
115
+ return references;
116
+ }
117
+ }
118
+ }
roblox-cs-master/RobloxCS/Utility.cs ADDED
@@ -0,0 +1,255 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using System.Reflection;
2
+ using System.Text.RegularExpressions;
3
+ using Microsoft.CodeAnalysis;
4
+ using Microsoft.CodeAnalysis.CSharp;
5
+ using Microsoft.CodeAnalysis.CSharp.Syntax;
6
+
7
+ namespace RobloxCS
8
+ {
9
+ public static class Utility
10
+ {
11
+ public const string RuntimeAssemblyName = "Roblox";
12
+ public const string LuaRuntimeModuleName = "RuntimeLib";
13
+
14
+ public static string GetMappedType(string csharpType)
15
+ {
16
+ if (csharpType.EndsWith("[]"))
17
+ {
18
+ var arrayType = csharpType.Substring(0, csharpType.Length - 2);
19
+ return $"{{ {GetMappedType(arrayType)} }}";
20
+ }
21
+ if (csharpType.EndsWith('?'))
22
+ {
23
+ var nonNullableType = csharpType.Substring(0, csharpType.Length - 1);
24
+ return $"{GetMappedType(nonNullableType)}?";
25
+ }
26
+
27
+ switch (csharpType)
28
+ {
29
+ case "object":
30
+ return "any";
31
+
32
+ case "void":
33
+ case "null":
34
+ return "nil";
35
+
36
+ case "char":
37
+ case "Char":
38
+ case "String":
39
+ return "string";
40
+ case "double":
41
+ case "float":
42
+ return "number";
43
+
44
+ default:
45
+ if (Constants.INTEGER_TYPES.Contains(csharpType))
46
+ {
47
+ return "number";
48
+ }
49
+ return csharpType;
50
+ }
51
+ }
52
+
53
+ public static string GetBit32MethodName(string bitOp)
54
+ {
55
+ switch (bitOp)
56
+ {
57
+ case "&=":
58
+ case "&":
59
+ return "band";
60
+ case "|=":
61
+ case "|":
62
+ return "bor";
63
+ case "^=":
64
+ case "^":
65
+ return "bxor";
66
+ case ">>=":
67
+ case ">>":
68
+ return "rshift";
69
+ case ">>>=":
70
+ case ">>>":
71
+ return "arshift";
72
+ case "<<=":
73
+ case "<<":
74
+ return "lshift";
75
+ case "~":
76
+ return "bnot";
77
+ default:
78
+ return bitOp;
79
+ }
80
+ }
81
+
82
+ public static string GetMappedOperator(string op)
83
+ {
84
+ switch (op)
85
+ {
86
+ case "++":
87
+ return "+=";
88
+ case "--":
89
+ return "--";
90
+ case "!":
91
+ return "not ";
92
+ case "!=":
93
+ return "~=";
94
+ case "&&":
95
+ return "and";
96
+ case "||":
97
+ return "or";
98
+ default:
99
+ return op;
100
+ }
101
+ }
102
+
103
+ public static List<string> ExtractTypeArguments(string input)
104
+ {
105
+ var typeArguments = new List<string>();
106
+ var regex = new Regex(@"<(?<args>[^<>]+)>");
107
+ var match = regex.Match(input);
108
+ if (match.Success)
109
+ {
110
+ // Get the matched group containing the type arguments
111
+ var args = match.Groups["args"].Value;
112
+
113
+ // Split the arguments by comma and trim whitespace
114
+ var argsArray = args.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
115
+
116
+ foreach (var arg in argsArray)
117
+ {
118
+ typeArguments.Add(arg.Trim());
119
+ }
120
+ }
121
+
122
+ return typeArguments;
123
+ }
124
+
125
+ public static string FormatLocation(FileLinePositionSpan lineSpan)
126
+ {
127
+ return $"{(lineSpan.Path == "" ? "<anonymous>" : lineSpan.Path)}:{lineSpan.StartLinePosition.Line + 1}:{lineSpan.StartLinePosition.Character + 1}";
128
+ }
129
+
130
+ public static ISymbol? FindMember(INamespaceSymbol namespaceSymbol, string memberName)
131
+ {
132
+ var member = namespaceSymbol.GetMembers().FirstOrDefault<ISymbol?>(member => member?.Name == memberName, null);
133
+ if (member == null && namespaceSymbol.ContainingNamespace != null)
134
+ {
135
+ member = FindMember(namespaceSymbol.ContainingNamespace, memberName);
136
+ }
137
+ return member;
138
+ }
139
+
140
+ public static ISymbol? FindMemberDeep(INamedTypeSymbol namedTypeSymbol, string memberName)
141
+ {
142
+ var member = namedTypeSymbol.GetMembers().FirstOrDefault(member => member.Name == memberName);
143
+ if (namedTypeSymbol.BaseType != null && member == null)
144
+ {
145
+ return FindMemberDeep(namedTypeSymbol.BaseType, memberName);
146
+ }
147
+ return member;
148
+ }
149
+
150
+ public static List<string> GetNamesFromNode(SyntaxNode? node)
151
+ {
152
+ if (node is BaseExpressionSyntax baseExpression)
153
+ {
154
+ return [""];
155
+ }
156
+
157
+ var names = new List<string>();
158
+ if (node == null) return names;
159
+
160
+ var identifierProperty = node.GetType().GetProperty("Identifier");
161
+ var identifierValue = identifierProperty?.GetValue(node);
162
+ if (identifierProperty != null && identifierValue != null && identifierValue is SyntaxToken)
163
+ {
164
+ names.Add(((SyntaxToken)identifierValue).ValueText.Trim());
165
+ return names;
166
+ }
167
+
168
+ var childNodes = node.ChildNodes();
169
+ var qualifiedNameNodes = node.IsKind(SyntaxKind.QualifiedName) ? [(QualifiedNameSyntax)node] : childNodes.OfType<QualifiedNameSyntax>();
170
+ var identifierNameNodes = node.IsKind(SyntaxKind.IdentifierName) ? [(IdentifierNameSyntax)node] : childNodes.OfType<IdentifierNameSyntax>();
171
+ foreach (var qualifiedNameNode in qualifiedNameNodes)
172
+ {
173
+ foreach (var name in GetNamesFromNode(qualifiedNameNode.Left))
174
+ {
175
+ names.Add(name.Trim());
176
+ }
177
+ foreach (var name in GetNamesFromNode(qualifiedNameNode.Right))
178
+ {
179
+ names.Add(name.Trim());
180
+ }
181
+ }
182
+
183
+ foreach (var identifierNameNode in identifierNameNodes)
184
+ {
185
+ names.Add(identifierNameNode.Identifier.ValueText.Trim());
186
+ }
187
+
188
+ return names;
189
+ }
190
+
191
+ public static string FixPathSep(string path)
192
+ {
193
+ path = Path.TrimEndingDirectorySeparator(path);
194
+ return Regex.Replace(path.Replace("\\\\", "/").Replace('\\', '/').Replace("//", "/"), @"(?<!\.)\./", "");
195
+ }
196
+
197
+ public static string? GetRbxcsDirectory()
198
+ {
199
+ var directoryName = Path.GetDirectoryName(GetAssemblyDirectory()); // pretend like this isn't here lol
200
+ return directoryName == null ? null : FixPathSep(directoryName);
201
+ }
202
+
203
+ public static string GetAssemblyDirectory()
204
+ {
205
+ var location = FixPathSep(Assembly.GetExecutingAssembly().Location);
206
+ var directoryName = Path.GetDirectoryName(location)!;
207
+ return FixPathSep(directoryName);
208
+ }
209
+
210
+ public static string GetTargetFramework()
211
+ {
212
+ var assemblyDirectory = GetAssemblyDirectory();
213
+ if (assemblyDirectory == null)
214
+ {
215
+ Logger.Error("Failed to find RobloxCS assembly directory!");
216
+ }
217
+
218
+ return assemblyDirectory!.Split('/').Last();
219
+ }
220
+
221
+ public static List<T> FilterDuplicates<T>(IEnumerable<T> items, IEqualityComparer<T> comparer)
222
+ {
223
+ var seen = new Dictionary<T, bool>(comparer);
224
+ var result = new List<T>();
225
+ foreach (var item in items)
226
+ {
227
+ if (!seen.ContainsKey(item))
228
+ {
229
+ seen[item] = true;
230
+ result.Add(item);
231
+ }
232
+ }
233
+
234
+ return result;
235
+ }
236
+
237
+ public static void PrintChildNodes(SyntaxNode node)
238
+ {
239
+ Logger.Info($"{node.Kind()} node children: {node.ChildNodes().Count()}");
240
+ foreach (var child in node.ChildNodes())
241
+ {
242
+ Logger.Info(child.Kind().ToString() + ": " + child.GetText());
243
+ }
244
+ }
245
+
246
+ public static void PrintChildTokens(SyntaxNode node)
247
+ {
248
+ Logger.Info($"{node.Kind()} token children: {node.ChildTokens().Count()}");
249
+ foreach (var child in node.ChildTokens())
250
+ {
251
+ Logger.Info(child.Kind().ToString() + ": " + child.Text);
252
+ }
253
+ }
254
+ }
255
+ }
roblox-cs-master/roblox-cs.sln ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 
2
+ Microsoft Visual Studio Solution File, Format Version 12.00
3
+ # Visual Studio Version 17
4
+ VisualStudioVersion = 17.8.34330.188
5
+ MinimumVisualStudioVersion = 10.0.40219.1
6
+ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RobloxCS", "RobloxCS\RobloxCS.csproj", "{B61F0F15-38BF-4A07-A08C-DD309F4EFF33}"
7
+ EndProject
8
+ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{3F25D38D-D965-4675-B51F-80A39A5259F2}"
9
+ ProjectSection(SolutionItems) = preProject
10
+ .gitignore = .gitignore
11
+ .github\workflows\publish.yml = .github\workflows\publish.yml
12
+ README.md = README.md
13
+ .github\workflows\tests.yml = .github\workflows\tests.yml
14
+ EndProjectSection
15
+ EndProject
16
+ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RobloxCS.Tests", "RobloxCS.Tests\RobloxCS.Tests.csproj", "{ECEF0FD6-BADA-4AA9-B55F-EB9D0BE18A5F}"
17
+ EndProject
18
+ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RobloxCS.CLI", "RobloxCS.CLI\RobloxCS.CLI.csproj", "{DCB5D69F-7AE3-4F15-85AA-CE9490529A71}"
19
+ EndProject
20
+ Global
21
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
22
+ Debug|Any CPU = Debug|Any CPU
23
+ Release|Any CPU = Release|Any CPU
24
+ EndGlobalSection
25
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
26
+ {B61F0F15-38BF-4A07-A08C-DD309F4EFF33}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
27
+ {B61F0F15-38BF-4A07-A08C-DD309F4EFF33}.Debug|Any CPU.Build.0 = Debug|Any CPU
28
+ {B61F0F15-38BF-4A07-A08C-DD309F4EFF33}.Release|Any CPU.ActiveCfg = Release|Any CPU
29
+ {B61F0F15-38BF-4A07-A08C-DD309F4EFF33}.Release|Any CPU.Build.0 = Release|Any CPU
30
+ {ECEF0FD6-BADA-4AA9-B55F-EB9D0BE18A5F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
31
+ {ECEF0FD6-BADA-4AA9-B55F-EB9D0BE18A5F}.Debug|Any CPU.Build.0 = Debug|Any CPU
32
+ {ECEF0FD6-BADA-4AA9-B55F-EB9D0BE18A5F}.Release|Any CPU.ActiveCfg = Release|Any CPU
33
+ {ECEF0FD6-BADA-4AA9-B55F-EB9D0BE18A5F}.Release|Any CPU.Build.0 = Release|Any CPU
34
+ {DCB5D69F-7AE3-4F15-85AA-CE9490529A71}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
35
+ {DCB5D69F-7AE3-4F15-85AA-CE9490529A71}.Debug|Any CPU.Build.0 = Debug|Any CPU
36
+ {DCB5D69F-7AE3-4F15-85AA-CE9490529A71}.Release|Any CPU.ActiveCfg = Release|Any CPU
37
+ {DCB5D69F-7AE3-4F15-85AA-CE9490529A71}.Release|Any CPU.Build.0 = Release|Any CPU
38
+ EndGlobalSection
39
+ GlobalSection(SolutionProperties) = preSolution
40
+ HideSolutionNode = FALSE
41
+ EndGlobalSection
42
+ GlobalSection(ExtensibilityGlobals) = postSolution
43
+ SolutionGuid = {5C9B4FC3-25E8-401B-AC78-69DE5D1AFA9C}
44
+ EndGlobalSection
45
+ EndGlobal