├── .gitignore ├── Build ├── build.bat └── build.sh ├── EditorIntegration └── Assets │ └── UnityScript2CSharp │ └── Editor │ ├── MonoInstalationFinder.cs │ ├── ProcessOutputStreamReader.cs │ └── UnityScript2CSharpRunner.cs ├── UnityScript2CSharp.Tests ├── Properties │ └── AssemblyInfo.cs ├── TestSubjects.cs ├── Tests.Arrays.cs ├── Tests.Attributes.cs ├── Tests.Expressions.cs ├── Tests.Operators.cs ├── Tests.Statements.Switch.cs ├── Tests.Statements.Yield.cs ├── Tests.Statements.cs ├── Tests.Utilities.cs ├── Tests.cs ├── UnityScript2CSharp.Tests.csproj └── packages.config ├── UnityScript2CSharp.sln ├── UnityScript2CSharp ├── AnchorKind.cs ├── App.config ├── BlockIdentation.cs ├── CommandLineArguments.cs ├── Comment.cs ├── CommentKind.cs ├── CompilerErrorComparer.cs ├── EntityExtensions.cs ├── Extensions │ ├── ASTNodeExtensions.cs │ └── StringExtensions.cs ├── Libs │ ├── Boo.Lang.Compiler.dll │ ├── Boo.Lang.Compiler.pdb │ ├── Boo.Lang.Extensions.dll │ ├── Boo.Lang.Extensions.pdb │ ├── Boo.Lang.Parser.dll │ ├── Boo.Lang.Parser.pdb │ ├── Boo.Lang.PatternMatching.dll │ ├── Boo.Lang.PatternMatching.pdb │ ├── Boo.Lang.Useful.dll │ ├── Boo.Lang.Useful.pdb │ ├── Boo.Lang.dll │ ├── Boo.Lang.pdb │ ├── UnityScript.Lang.dll │ ├── UnityScript.Lang.pdb │ ├── UnityScript.dll │ └── UnityScript.pdb ├── LogModuleName.cs ├── OrphanCommentVisitor.cs ├── Program.cs ├── Properties │ └── AssemblyInfo.cs ├── SourceFile.cs ├── Steps │ ├── ApplyEnumToImplicitConversions.cs │ ├── AttachComments.cs │ ├── CSharpReservedKeywordIdentifierClashFix.cs │ ├── CastInjector.cs │ ├── CtorFieldInitializationFix.cs │ ├── ExpandAssignmentToValueTypeMembers.cs │ ├── ExpandValueTypeObjectInitialization.cs │ ├── FixClosures.cs │ ├── FixEnumReferences.cs │ ├── FixFunctionReferences.cs │ ├── FixSwitchBreaks.cs │ ├── FixSwitchWithOnlyDefault.cs │ ├── FixTypeAccessibility.cs │ ├── InferredMethodReturnTypeFix.cs │ ├── InjectTypeOfExpressionsInArgumentsOfSystemType.cs │ ├── InstanceToTypeReferencedStaticMemberReference.cs │ ├── MergeMainMethodStatementsIntoStartMethod.cs │ ├── OperatorMethodToLanguageOperator.cs │ ├── PreProcessCollector.cs │ ├── PromoteImplicitBooleanConversionsToExplicitComparisons.cs │ ├── RemoveUnnecessaryCastInArrayInstantiation.cs │ ├── RenameArrayDeclaration.cs │ ├── ReplaceArrayAndStringMemberReferenceWithCamelCaseVersion.cs │ ├── ReplaceGetSetItemMethodsWithOriginalIndexers.cs │ ├── ReplaceUnityScriptArrayWithObjectArray.cs │ ├── SelectiveUnaryExpressionExpansionProcessUnityScriptMethods.cs │ └── TransforwmKnownUnityEngineMethods.cs ├── SwitchConverter.cs ├── UnityScript2CSharp.csproj ├── UnityScript2CSharpConverter.cs ├── UnityScript2CSharpConverterVisitor.cs ├── UsingCollector.cs ├── Writer.cs └── packages.config ├── license.md └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | **/bin/* 2 | **/obj/* 3 | packages/ 4 | .vs 5 | EditorIntegration/Library 6 | -------------------------------------------------------------------------------- /Build/build.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | REM This batch file is used to generate the final "Unity Editor Integration Package" for the "UnityScript2CSharp" converter 4 | REM 5 | REM Requirements: 6 | REM - 7z.exe must be in the search path 7 | REM - MSBuild must be in the search path 8 | REM - Unity.exe must be in the searh path 9 | 10 | set DEPENDENCIES_NOT_FOUND=0 11 | call :CheckExecutable 7z.exe DEPENDENCIES_NOT_FOUND 12 | call :CheckExecutable MSBuild.exe DEPENDENCIES_NOT_FOUND 13 | call :CheckExecutable Unity.exe DEPENDENCIES_NOT_FOUND 14 | 15 | if [%DEPENDENCIES_NOT_FOUND%] == [1] goto :eof 16 | 17 | REM Create Project Folder 18 | set UNITYSCRIPT2CSHARP_PROJECT_ROOT=%temp%\UnityScript2CSharp 19 | if exist %UNITYSCRIPT2CSHARP_PROJECT_ROOT% rmdir %UNITYSCRIPT2CSHARP_PROJECT_ROOT% /S /Q 20 | mkdir %UNITYSCRIPT2CSHARP_PROJECT_ROOT% 21 | 22 | REM Build app 23 | msbuild /p:Configuration=Release 24 | 25 | if %ERRORLEVEL% EQU 0 goto BuildSucceeded 26 | @echo Error while building converter. Error code = %errorlevel% 27 | goto End 28 | 29 | :BuildSucceeded 30 | 31 | REM Get current version 32 | UnityScript2CSharp\bin\Release\UnityScript2CSharp.exe > %temp%\UnityScript2CSharp.version 2>&1 33 | set /p CONVERTER_OUTPUT=<%temp%\UnityScript2CSharp.version 34 | 35 | REM Extracts version # from output like: UnityScript2CSharp 1.0.6577.20371 36 | set CONVERTER_VERSION=%CONVERTER_OUTPUT:~19,15% 37 | 38 | REM Create folders 39 | set UNITYSCRIPT2CSHARP_IN_ASSETS_FOLDER=%UNITYSCRIPT2CSHARP_PROJECT_ROOT%\Assets\UnityScript2CSharp 40 | mkdir %UNITYSCRIPT2CSHARP_IN_ASSETS_FOLDER% 41 | 42 | REM Zip output 43 | set OUTPUT_FILE=%UNITYSCRIPT2CSHARP_IN_ASSETS_FOLDER%\UnityScript2CSharp_%CONVERTER_VERSION%.zip 44 | pushd UnityScript2CSharp\bin\Release\ 45 | 7z.exe a -tzip %OUTPUT_FILE% * 46 | popd 47 | 48 | 49 | REM Copy editor integration sources.. 50 | mkdir %UNITYSCRIPT2CSHARP_IN_ASSETS_FOLDER%\Editor 51 | xcopy EditorIntegration\Assets\UnityScript2CSharp\Editor %UNITYSCRIPT2CSHARP_IN_ASSETS_FOLDER%\Editor\ 52 | 53 | REM Create package 54 | 55 | set EXPORTED_PACKAGE_PATH=%temp%\UnityScript2CSharp_Conversion_%CONVERTER_VERSION%.unitypackage 56 | @echo Exporting unity package (%UNITYSCRIPT2CSHARP_IN_ASSETS_FOLDER%) to %EXPORTED_PACKAGE_PATH% 57 | unity.exe --createProject -batchmode -projectPath %UNITYSCRIPT2CSHARP_PROJECT_ROOT% -exportPackage Assets %EXPORTED_PACKAGE_PATH% -quit 58 | 59 | if %ERRORLEVEL% EQU 0 goto PackagingSucceeded 60 | 61 | @echo Error while exporting Unity package. Error code = %errorlevel% 62 | goto End 63 | 64 | :PackagingSucceeded 65 | @echo Unity package exported successfully 66 | 67 | :End 68 | goto :eof 69 | 70 | :CheckExecutable 71 | where %1 > NUL 2>&1 72 | 73 | if errorlevel 1 @echo %1 not found. Please, add it to search path. 74 | if [%ERRORLEVEL%]==[1] Set %~2=%ERRORLEVEL% 75 | 76 | goto :eof 77 | 78 | -------------------------------------------------------------------------------- /Build/build.sh: -------------------------------------------------------------------------------- 1 | # This batch file is used to generate the final "Unity Editor Integration Package" for the "UnityScript2CSharp" converter 2 | # 3 | # Requirements: 4 | # - gzip must be in the search path 5 | # - msbuild must be in the search path 6 | # - Unity must be in the searh path 7 | 8 | expand_bg="\e[K" 9 | blue_bg="\e[0;104m${expand_bg}" 10 | red_bg="\e[0;101m${expand_bg}" 11 | green_bg="\e[0;102m${expand_bg}" 12 | 13 | red="\e[0;91m" 14 | blue="\e[0;94m" 15 | green="\e[0;92m" 16 | white="\e[0;97m" 17 | bold="\e[1m" 18 | uline="\e[4m" 19 | reset="\e[0m" 20 | 21 | function CheckExecutable() 22 | { 23 | which $1 > /dev/null 24 | 25 | if [ $? -ne 0 ]; then 26 | echo Could not find $1. Please make sure it is in your path and try again. 27 | exit 28 | fi 29 | } 30 | 31 | CheckExecutable msbuild 32 | if [ $? -ne 0 ]; then 33 | DependenciesNotFound 34 | fi 35 | 36 | CheckExecutable Unity 37 | if [ $? -ne 0 ]; then 38 | DependenciesNotFound 39 | fi 40 | 41 | # Create Project Folder 42 | UNITYSCRIPT2CSHARP_PROJECT_ROOT=/tmp/UnityScript2CSharpBuildProject 43 | if [ -d $UNITYSCRIPT2CSHARP_PROJECT_ROOT ]; then 44 | rm -rf $UNITYSCRIPT2CSHARP_PROJECT_ROOT 45 | fi 46 | mkdir $UNITYSCRIPT2CSHARP_PROJECT_ROOT 47 | 48 | # Build app 49 | msbuild /p:Configuration=Release 50 | 51 | if [ $? -ne 0 ]; then 52 | echo Error while building converter. Error code = $? 53 | exit 54 | fi 55 | 56 | # Extracts version # from output like: UnityScript2CSharp 1.0.6577.20371 57 | CONVERTER_VERSION="$(mono UnityScript2CSharp/bin/Release/UnityScript2CSharp.exe 2>&1 | head -n 1 | cut -b20-40)" 58 | echo $CONVERTER_VERSION 59 | 60 | # Create folders 61 | UNITYSCRIPT2CSHARP_IN_ASSETS_FOLDER=$UNITYSCRIPT2CSHARP_PROJECT_ROOT/Assets/UnityScript2CSharp 62 | mkdir -p $UNITYSCRIPT2CSHARP_IN_ASSETS_FOLDER 63 | 64 | # Zip output 65 | OUTPUT_FILE="$UNITYSCRIPT2CSHARP_IN_ASSETS_FOLDER/UnityScript2CSharp_$CONVERTER_VERSION.zip" 66 | 67 | echo Generating zip file: $OUTPUT_FILE 68 | 69 | UNITY_APP_PATH="$(which Unity)" 70 | UNITY_INSTALL_FOLDER="${UNITY_APP_PATH%/Unity}" 71 | 72 | pushd UnityScript2CSharp/bin/Release/ 73 | $UNITY_INSTALL_FOLDER/Data/Tools/7za a -tzip $OUTPUT_FILE * 74 | COMPRESSING_STATUS=$? 75 | popd 76 | 77 | if [ $COMPRESSING_STATUS -ne 0 ]; then 78 | echo -e "${red}Failed to generate zip file. Aborting${reset}" 79 | exit 80 | fi 81 | 82 | # Copy editor integration sources.. 83 | mkdir $UNITYSCRIPT2CSHARP_IN_ASSETS_FOLDER/Editor 84 | cp EditorIntegration/Assets/UnityScript2CSharp/Editor/* $UNITYSCRIPT2CSHARP_IN_ASSETS_FOLDER/Editor/ 85 | 86 | # Create package 87 | 88 | EXPORTED_PACKAGE_PATH="/tmp/UnityScript2CSharp_Conversion_$CONVERTER_VERSION.unitypackage" 89 | echo -e "${green_bg}${white}Exporting unity package from project $UNITYSCRIPT2CSHARP_PROJECT_ROOT to ${EXPORTED_PACKAGE_PATH}${reset}" 90 | 91 | Unity --createProject -batchmode -projectPath $UNITYSCRIPT2CSHARP_PROJECT_ROOT -exportPackage Assets $EXPORTED_PACKAGE_PATH -quit 92 | 93 | if [ $? -ne 0 ]; then 94 | echo Error while exporting Unity package. Error code = $? 95 | exit 96 | fi 97 | 98 | echo -e "${white}Unity package exported successfully${reset}" -------------------------------------------------------------------------------- /EditorIntegration/Assets/UnityScript2CSharp/Editor/MonoInstalationFinder.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using UnityEngine; 3 | using UnityEditor; 4 | 5 | public class MonoInstallationFinder 6 | { 7 | public const string MonoInstallation = "Mono"; 8 | public const string MonoBleedingEdgeInstallation = "MonoBleedingEdge"; 9 | 10 | public static string GetFrameWorksFolder() 11 | { 12 | var editorAppPath = EditorApplication.applicationPath; 13 | if (Application.platform == RuntimePlatform.WindowsEditor) 14 | return Path.Combine(Path.GetDirectoryName(editorAppPath), "Data"); 15 | else if (Application.platform == RuntimePlatform.OSXEditor) 16 | return Path.Combine(editorAppPath, "Contents"); 17 | else // Linux...? 18 | return Path.Combine(Path.GetDirectoryName(editorAppPath), "Data"); 19 | } 20 | 21 | public static string GetProfileDirectory(string profile) 22 | { 23 | var monoprefix = GetMonoInstallation(); 24 | return Path.Combine(monoprefix, Path.Combine("lib", Path.Combine("mono", profile))); 25 | } 26 | 27 | public static string GetProfileDirectory(string profile, string monoInstallation) 28 | { 29 | var monoprefix = GetMonoInstallation(monoInstallation); 30 | return Path.Combine(monoprefix, Path.Combine("lib", Path.Combine("mono", profile))); 31 | } 32 | 33 | public static string GetProfilesDirectory(string monoInstallation) 34 | { 35 | var monoprefix = GetMonoInstallation(monoInstallation); 36 | return Path.Combine(monoprefix, Path.Combine("lib", "mono")); 37 | } 38 | 39 | public static string GetEtcDirectory(string monoInstallation) 40 | { 41 | var monoprefix = GetMonoInstallation(monoInstallation); 42 | return Path.Combine(monoprefix, Path.Combine("etc", "mono")); 43 | } 44 | 45 | public static string GetMonoInstallation() 46 | { 47 | return GetMonoInstallation(MonoInstallation); 48 | } 49 | 50 | public static string GetMonoBleedingEdgeInstallation() 51 | { 52 | return GetMonoInstallation(MonoBleedingEdgeInstallation); 53 | } 54 | 55 | public static string GetMonoInstallation(string monoName) 56 | { 57 | return Path.Combine(GetFrameWorksFolder(), monoName); 58 | } 59 | } -------------------------------------------------------------------------------- /EditorIntegration/Assets/UnityScript2CSharp/Editor/ProcessOutputStreamReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Threading; 6 | 7 | namespace Assets.Editor 8 | { 9 | internal class ProcessOutputStreamReader 10 | { 11 | private readonly Func hostProcessExited; 12 | private readonly StreamReader stream; 13 | internal List lines; 14 | private Thread thread; 15 | 16 | internal ProcessOutputStreamReader(Process p, StreamReader stream) : this(() => p.HasExited, stream) 17 | { 18 | } 19 | 20 | internal ProcessOutputStreamReader(Func hostProcessExited, StreamReader stream) 21 | { 22 | this.hostProcessExited = hostProcessExited; 23 | this.stream = stream; 24 | lines = new List(); 25 | 26 | thread = new Thread(ThreadFunc); 27 | thread.Start(); 28 | } 29 | 30 | private void ThreadFunc() 31 | { 32 | if (hostProcessExited()) return; 33 | while (true) 34 | { 35 | if (stream.BaseStream == null) return; 36 | 37 | var line = stream.ReadLine(); 38 | if (line == null) 39 | return; 40 | 41 | lock (lines) 42 | { 43 | lines.Add(line); 44 | } 45 | } 46 | } 47 | 48 | internal string[] GetOutput() 49 | { 50 | if (hostProcessExited()) 51 | thread.Join(); 52 | 53 | lock (lines) 54 | { 55 | return lines.ToArray(); 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /UnityScript2CSharp.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("UnityScript2CSharp.Tests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("UnityScript2CSharp.Tests")] 13 | [assembly: AssemblyCopyright("Copyright © 2017")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("c6c49d9a-1ccf-4b0d-8cc2-b35998c11c42")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /UnityScript2CSharp.Tests/TestSubjects.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace UnityEngine 4 | { 5 | public class Component 6 | { 7 | public Component GetComponent(string s) { return this; } 8 | public Component GetComponent(Type t) { return this; } 9 | public Component GetComponent() { return this; } 10 | } 11 | public class GameObject 12 | { 13 | public Component GetComponent(string s) { return null; } 14 | public Component GetComponent(Type t) { return null; } 15 | public Component GetComponent() { return null; } 16 | } 17 | } 18 | 19 | namespace UnityEditor 20 | { 21 | public class Editor 22 | { 23 | } 24 | } 25 | 26 | namespace UnityScript2CSharp.Tests 27 | { 28 | public class Dummy 29 | { 30 | public static void Foo(object o) { } 31 | } 32 | 33 | public struct DelegateInvocation 34 | { 35 | public static bool Run(Func f) { return f(10); } 36 | 37 | // We can't overload Run() otherwise UnityScript compiler will not be able to figure out which 38 | // overload is being invoked and try to resolve it at runtime (replacing the method call with 39 | // a call to Quack() 40 | public static void RunAction(Action f) { } 41 | } 42 | 43 | public class Outer 44 | { 45 | public class Inner 46 | { 47 | public class Inner2 {} 48 | } 49 | } 50 | 51 | public class Base 52 | { 53 | public virtual void M() {} 54 | public virtual void M(int i) { } 55 | } 56 | 57 | public class C 58 | { 59 | public static C instance; 60 | public C myself; 61 | 62 | public static int staticField; 63 | public static int staticMethod() { return 1; } 64 | public C instanceMethod() { return null; } 65 | 66 | public C(int i) {} 67 | public C() {} 68 | } 69 | 70 | public struct Other 71 | { 72 | public string value; 73 | } 74 | 75 | public sealed class ReferenceType 76 | { 77 | public static Other staticOther { get; set; } 78 | } 79 | 80 | public struct Struct 81 | { 82 | public Struct(int i) 83 | { 84 | value = i; 85 | other = default(Other); 86 | } 87 | 88 | public int value { get; set; } 89 | public Other other { get; set; } 90 | public static Other staticOther { get; set; } 91 | } 92 | 93 | public class NonGeneric 94 | { 95 | public string ToName(int n) 96 | { 97 | return typeof(T).FullName; 98 | } 99 | 100 | public Struct Struct { get; set; } 101 | } 102 | 103 | public class Operators 104 | { 105 | public static implicit operator bool(Operators instance) 106 | { 107 | return false; 108 | } 109 | 110 | public static Operators operator*(Operators instance, float n) { return instance; } 111 | 112 | public static Operators operator+(Operators instance, float n) { return instance; } 113 | 114 | public static Operators operator-(Operators instance) { return instance; } 115 | 116 | public static Operators operator+(Operators instance) { return instance; } 117 | 118 | public static Operators operator~(Operators instance) { return instance; } 119 | 120 | public static bool operator!(Operators instance) { return instance; } 121 | 122 | public string Message = "Foo"; 123 | } 124 | 125 | public class Properties 126 | { 127 | public int this[int i, string j] 128 | { 129 | get { return i; } 130 | set {} 131 | } 132 | 133 | public int this[int i] 134 | { 135 | get { return i; } 136 | set {} 137 | } 138 | } 139 | 140 | public class AttrAttribute : Attribute 141 | { 142 | public AttrAttribute() 143 | { 144 | } 145 | 146 | public AttrAttribute(int i) 147 | { 148 | } 149 | 150 | public AttrAttribute(Type t) 151 | { 152 | } 153 | 154 | public bool Prop { get; set; } 155 | } 156 | 157 | public class NonCompliant : Attribute 158 | { 159 | } 160 | 161 | public class SystemTypeAsParameter 162 | { 163 | public SystemTypeAsParameter(Type t) {} 164 | 165 | public static void SimpleMethod(Type t) {} 166 | 167 | public void InParamsArray(params Type[] t) {} 168 | } 169 | 170 | public class Methods 171 | { 172 | public static void OutRef(out int i, ref int j) 173 | { 174 | i = 10; 175 | j = i; 176 | } 177 | } 178 | 179 | public struct ObjectType 180 | { 181 | public static Object NonGeneric() { return null; } 182 | 183 | public static T Generic() { return default(T); } 184 | 185 | public static void Parameter(T p) { } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /UnityScript2CSharp.Tests/Tests.Arrays.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | 3 | namespace UnityScript2CSharp.Tests 4 | { 5 | [TestFixture] 6 | public partial class Tests 7 | { 8 | [Test] 9 | public void Arrays() 10 | { 11 | var sourceFiles = SingleSourceFor("arrays.js", "public var a : int [];"); 12 | var expectedConvertedContents = SingleSourceFor("arrays.cs", DefaultGeneratedClass + @"arrays : MonoBehaviour { public int[] a; }"); 13 | 14 | AssertConversion(sourceFiles, expectedConvertedContents); 15 | } 16 | 17 | [TestCase("var a = Array(42)", "object[] a = new object[42]")] 18 | [TestCase("var a = Array(42, 43)", "object[] a = new object[] {42, 43}")] 19 | [TestCase("var a = Array(other)", "Not Used", true)] 20 | public void UnityScript_Lang_Array(string usSnippet, string csSnippet, bool expectError = false) 21 | { 22 | var sourceFiles = SingleSourceFor("unity_script_lang_array.js", $"function F(other:IEnumerable) {{ {usSnippet}; return a.length; }}"); 23 | var expectedConvertedContents = SingleSourceFor("unity_script_lang_array.cs", DefaultGeneratedClass + $"unity_script_lang_array : MonoBehaviour {{ public virtual int F(IEnumerable other) {{ {csSnippet}; return a.Length; }} }}"); 24 | 25 | AssertConversion(sourceFiles, expectedConvertedContents, expectError); 26 | } 27 | 28 | [TestCase("var a = [1, 2, 3]", "int[] a = new int[] {1, 2, 3}", TestName = "Primitive Arrays")] 29 | [TestCase("var a = Array(true, false)", "object[] a = new object[] {true, false}", TestName = "Array class (bools)")] 30 | [TestCase("var a = Array(1, 2)", "object[] a = new object[] {1, 2}", TestName = "Array Class (ints)")] 31 | public void Arrays_With_Initializer(string usSnippet, string csSnippet) 32 | { 33 | var sourceFiles = SingleSourceFor("arrays_with_initializer.js", $"function F() : Object {{ {usSnippet}; return a.length > 0 ? a[0] : a[1]; }}"); 34 | var expectedConvertedContents = SingleSourceFor("arrays_with_initializer.cs", DefaultGeneratedClass + $"arrays_with_initializer : MonoBehaviour {{ public virtual object F() {{ {csSnippet}; return a.Length > 0 ? a[0] : a[1]; }} }}"); 35 | 36 | AssertConversion(sourceFiles, expectedConvertedContents); 37 | } 38 | 39 | [TestCase("int", "int")] 40 | [TestCase("String", "string")] 41 | [TestCase("System.Object", "object")] 42 | [TestCase("boolean", "bool")] 43 | [TestCase("int", "int", "System.Math.Min(1, 2)", TestName = "Method invocation as array size")] 44 | [TestCase("int", "int", "System.Array.BinarySearch(new int[10], 1)", TestName = "Complex method invocation as array size")] 45 | public void Arrays_New(string usTypeName, string csTypeName, string lengthExpression = null) 46 | { 47 | var sourceFiles = SingleSourceFor("arrays_new.js", $"public var a : {usTypeName} []; function F() {{ a = new {usTypeName}[{lengthExpression ?? "10"}]; }}"); 48 | var expectedConvertedContents = SingleSourceFor("arrays_new.cs", DefaultGeneratedClass + $@"arrays_new : MonoBehaviour {{ public {csTypeName}[] a; public virtual void F() {{ this.a = new {csTypeName}[{lengthExpression ?? "10"}]; }} }}"); 49 | 50 | AssertConversion(sourceFiles, expectedConvertedContents); 51 | } 52 | 53 | [TestCase("int", "int")] 54 | [TestCase("String", "string")] 55 | [TestCase("System.Object", "object")] 56 | [TestCase("boolean", "bool")] 57 | public void Arrays_Item_Access(string usTypeName, string csTypeName) 58 | { 59 | var sourceFiles = SingleSourceFor("array_item_access.js", $"function F(a:{usTypeName} []) {{ return a[0]; }}"); 60 | var expectedConvertedContents = SingleSourceFor("array_item_access.cs", DefaultGeneratedClass + $@"array_item_access : MonoBehaviour {{ public virtual {csTypeName} F({csTypeName}[] a) {{ return a[0]; }} }}"); 61 | 62 | AssertConversion(sourceFiles, expectedConvertedContents); 63 | } 64 | 65 | [Test] 66 | public void Implicit_Bool_Conversion_For_Array_Member_Access() 67 | { 68 | var sourceFiles = SingleSourceFor("bool_conversion_array_member.js", "function F(a: int[]) { return !a.length || (a.Length > 0); }"); 69 | var expectedConvertedContents = SingleSourceFor("bool_conversion_array_member.cs", DefaultGeneratedClass + @"bool_conversion_array_member : MonoBehaviour { public virtual bool F(int[] a) { return (a.Length == 0) || (a.Length > 0); } }"); 70 | 71 | AssertConversion(sourceFiles, expectedConvertedContents); 72 | } 73 | 74 | [Test] 75 | public void Implicit_Bool_Conversion_For_Array() 76 | { 77 | var sourceFiles = SingleSourceFor("bool_conversion_array.js", "function F(a: int[]) { return !a; }"); 78 | var expectedConvertedContents = SingleSourceFor("bool_conversion_array.cs", DefaultGeneratedClass + @"bool_conversion_array : MonoBehaviour { public virtual bool F(int[] a) { return a == null; } }"); 79 | 80 | AssertConversion(sourceFiles, expectedConvertedContents); 81 | } 82 | 83 | [TestCase("int", "int")] 84 | [TestCase("String", "string")] 85 | [TestCase("System.Object", "object")] 86 | [TestCase("boolean", "bool")] 87 | public void Arrays_MultiDimensional_Item_Access(string usTypeName, string csTypeName) 88 | { 89 | var sourceFiles = SingleSourceFor("multidimensiona_array_item_access.js", $"function F(a:{usTypeName}[,]) {{ return a[4,2]; }}"); 90 | var expectedConvertedContents = SingleSourceFor("multidimensiona_array_item_access.cs", DefaultGeneratedClass + $@"multidimensiona_array_item_access : MonoBehaviour {{ public virtual {csTypeName} F({csTypeName}[,] a) {{ return a[4, 2]; }} }}"); 91 | 92 | AssertConversion(sourceFiles, expectedConvertedContents); 93 | } 94 | 95 | [Test] 96 | public void Array_Members() 97 | { 98 | var sourceFiles = SingleSourceFor("array_members.js", "function F(a:int[]) { return a.length; }"); 99 | var expectedConvertedContents = SingleSourceFor("array_members.cs", DefaultGeneratedClass + "array_members : MonoBehaviour { public virtual int F(int[] a) { return a.Length; } }"); 100 | 101 | AssertConversion(sourceFiles, expectedConvertedContents); 102 | } 103 | 104 | [TestCase("int", "int")] 105 | [TestCase("String", "string")] 106 | [TestCase("System.Object", "object")] 107 | [TestCase("boolean", "bool")] 108 | public void Arrays_Three_Dimensions_Item_Access(string usTypeName, string csTypeName) 109 | { 110 | var sourceFiles = SingleSourceFor("three_dimensions_array_item_access.js", $"function F() {{ var a:{usTypeName}[,,] = new {usTypeName}[1,2,3]; return a[0,0,1]; }}"); 111 | var expectedConvertedContents = SingleSourceFor("three_dimensions_array_item_access.cs", DefaultGeneratedClass + $@"three_dimensions_array_item_access : MonoBehaviour {{ public virtual {csTypeName} F() {{ {csTypeName}[,,] a = new {csTypeName}[1, 2, 3]; return a[0, 0, 1]; }} }}"); 112 | 113 | AssertConversion(sourceFiles, expectedConvertedContents); 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /UnityScript2CSharp.Tests/Tests.Attributes.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | 3 | namespace UnityScript2CSharp.Tests 4 | { 5 | [TestFixture] 6 | public partial class Tests 7 | { 8 | [TestCase("\"Foo\""), Category("Attributes")] 9 | [TestCase("\"Foo\", false"), Category("Attributes")] 10 | [TestCase(""), Category("Attributes")] 11 | public void Attributes_On_Methods(string args) 12 | { 13 | var sourceFiles = new[] { new SourceFile { FileName = "method_attributes.js", Contents = $"@System.Obsolete({args}) function F() {{ }}" } }; 14 | 15 | var argsIncludingParentheses = args.Length > 0 ? $"({args})" : string.Empty; 16 | var expectedConvertedContents = new[] { new SourceFile { FileName = "method_attributes.cs", Contents = DefaultGeneratedClass + $"method_attributes : MonoBehaviour {{ [System.Obsolete{argsIncludingParentheses}] public virtual void F() {{ }} }}" } }; 17 | AssertConversion(sourceFiles, expectedConvertedContents); 18 | } 19 | 20 | [TestCase("\"Foo\""), Category("Attributes")] 21 | [TestCase("\"Foo\", false"), Category("Attributes")] 22 | [TestCase(""), Category("Attributes")] 23 | public void Attributes_On_Types(string args) 24 | { 25 | var sourceFiles = new[] { new SourceFile { FileName = "type_attributes.js", Contents = $"@System.Obsolete({args}) class C {{}}" } }; 26 | 27 | var argsIncludingParentheses = args.Length > 0 ? $"({args})" : string.Empty; 28 | var expectedConvertedContents = new[] { new SourceFile { FileName = "type_attributes.cs", Contents = $"using System.Collections; [System.Serializable] [System.Obsolete{argsIncludingParentheses}] public class C : object {{ }}" } }; 29 | 30 | AssertConversion(sourceFiles, expectedConvertedContents); 31 | } 32 | 33 | [Test] 34 | public void Non_Compliant_Attribute_Type_Name() 35 | { 36 | var sourceFiles = new[] { new SourceFile { FileName = "non_compliant_attribute_type_name.js", Contents = "import UnityScript2CSharp.Tests; @NonCompliant class C {}" } }; 37 | 38 | var expectedConvertedContents = new[] { new SourceFile { FileName = "non_compliant_attribute_type_name.cs", Contents = "using UnityScript2CSharp.Tests; " + DefaultUsingsNoUnityType + " [UnityScript2CSharp.Tests.NonCompliant] public class C : object { }" } }; 39 | AssertConversion(sourceFiles, expectedConvertedContents); 40 | } 41 | 42 | [TestCase(""), Category("Attributes")] 43 | [TestCase("42"), Category("Attributes")] 44 | [TestCase("42, Prop = true"), Category("Attributes")] 45 | public void Attributes_With_Named_Arguments(string args) 46 | { 47 | var sourceFiles = new[] { new SourceFile { FileName = "attribute_named_arguments.js", Contents = $"import UnityScript2CSharp.Tests; @Attr({args}) public var i:int = 10;" } }; 48 | var expectedWithParams = args.Length > 0 ? $"({args})" : string.Empty; 49 | var expectedConvertedContents = new[] { new SourceFile { FileName = "attribute_named_arguments.cs", Contents = "using UnityScript2CSharp.Tests; " + DefaultGeneratedClass + $"attribute_named_arguments : MonoBehaviour {{ [UnityScript2CSharp.Tests.Attr{expectedWithParams}] public int i; public attribute_named_arguments() {{ this.i = 10; }} }}" } }; 50 | AssertConversion(sourceFiles, expectedConvertedContents); 51 | } 52 | 53 | [Test] 54 | public void Serialized_Attribute_Is_Not_Duplicated() 55 | { 56 | var sourceFiles = new[] { new SourceFile { FileName = "serializable_attribute.js", Contents = "@System.Serializable class C {}" } }; 57 | 58 | var expectedConvertedContents = new[] { new SourceFile { FileName = "serializable_attribute.cs", Contents = DefaultUsingsNoUnityType + " public class C : object { }" } }; 59 | AssertConversion(sourceFiles, expectedConvertedContents); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /UnityScript2CSharp.Tests/Tests.Operators.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using NUnit.Framework; 4 | 5 | namespace UnityScript2CSharp.Tests 6 | { 7 | public partial class Tests 8 | { 9 | [Test] 10 | public void Bool_Implicit() 11 | { 12 | var sourceFiles = SingleSourceFor("bool_implicit.js", "import UnityScript2CSharp.Tests; function F(o:Operators) { if (o) { return o; } return false; }"); 13 | var expectedConvertedContents = SingleSourceFor("bool_implicit.cs", "using UnityScript2CSharp.Tests; " + DefaultGeneratedClass + @"bool_implicit : MonoBehaviour { public virtual object F(Operators o) { if (o) { return o; } return false; } }"); 14 | 15 | AssertConversion(sourceFiles, expectedConvertedContents); 16 | } 17 | 18 | [TestCase("o != null", "!(o == null)")] 19 | [TestCase("o == null")] 20 | [TestCase("o == \"foo\"")] 21 | [TestCase("o instanceof System.Action", "o is System.Action")] 22 | [TestCase("i == 42")] 23 | [TestCase("i != 42")] 24 | [TestCase("i > 10")] 25 | [TestCase("i < 10")] 26 | [TestCase("i >= 10")] 27 | [TestCase("i <= 10")] 28 | [TestCase("(i > 0) || (i < 30)")] 29 | [TestCase("(i >= 0) && (i <= 30)")] 30 | public void BoolOperators(string usOperatorUsage, string csOperatorUsage = null) 31 | { 32 | var sourceFiles = SingleSourceFor("operators.js", $"function F(o:Object, i:int) {{ return {usOperatorUsage}; }}"); 33 | var expectedConvertedContents = SingleSourceFor("operators.cs", DefaultGeneratedClass + $"operators : MonoBehaviour {{ public virtual bool F(object o, int i) {{ return {csOperatorUsage ?? usOperatorUsage}; }} }}"); 34 | 35 | AssertConversion(sourceFiles, expectedConvertedContents); 36 | } 37 | 38 | [TestCase("i + j")] 39 | [TestCase("i << j")] 40 | [TestCase("i >> j")] 41 | [TestCase("i | j")] 42 | [TestCase("i & j")] 43 | [TestCase("i ^ j")] 44 | public void ArithmethicOperators(string operatorUsage) 45 | { 46 | var sourceFiles = SingleSourceFor("arithmetic_operators.js", $"function F(j:int, i:int) {{ return {operatorUsage}; }}"); 47 | var expectedConvertedContents = SingleSourceFor("arithmetic_operators.cs", DefaultGeneratedClass + $"arithmetic_operators : MonoBehaviour {{ public virtual int F(int j, int i) {{ return {operatorUsage}; }} }}"); 48 | 49 | AssertConversion(sourceFiles, expectedConvertedContents); 50 | } 51 | 52 | [TestCase("i >>= j", "i = i >> j")] 53 | [TestCase("i <<= j", "i = i << j")] 54 | [TestCase("i &= j", "i = i & j")] 55 | [TestCase("i |= j", "i = i | j")] 56 | [TestCase("i ^= j", "i = i ^ j")] 57 | [TestCase("i += j", "i = i + j")] 58 | [TestCase("i -= j", "i = i - j")] 59 | [TestCase("i *= j", "i = i * j")] 60 | [TestCase("i /= j", "i = i / j")] 61 | public void Arithmethic_InPlace_Operators(string usOperatorUsage, string csOperatorUsage) 62 | { 63 | var sourceFiles = SingleSourceFor("arithmetic_implicit_operators.js", $"function F(j:int, i:int) {{ {usOperatorUsage}; }}"); 64 | var expectedConvertedContents = SingleSourceFor("arithmetic_implicit_operators.cs", DefaultGeneratedClass + $"arithmetic_implicit_operators : MonoBehaviour {{ public virtual void F(int j, int i) {{ {csOperatorUsage}; }} }}"); 65 | 66 | AssertConversion(sourceFiles, expectedConvertedContents); 67 | } 68 | 69 | [TestCase("~x")] 70 | [TestCase("x++")] 71 | [TestCase("++x")] 72 | [TestCase("x--")] 73 | [TestCase("--x")] 74 | public void Unary_Numeric_Operators(string operatorUsage) 75 | { 76 | var sourceFiles = SingleSourceFor("unary_operators.js", $"function F(x:int) {{ return {operatorUsage}; }}"); 77 | var expectedConvertedContents = SingleSourceFor("unary_operators.cs", DefaultGeneratedClass + $"unary_operators : MonoBehaviour {{ public virtual int F(int x) {{ return {operatorUsage}; }} }}"); 78 | 79 | AssertConversion(sourceFiles, expectedConvertedContents); 80 | } 81 | 82 | [Test] 83 | public void Unary_Logical_Not() 84 | { 85 | var sourceFiles = SingleSourceFor("unary_logical_not.js", "function F(b:boolean) { return !b; }"); 86 | var expectedConvertedContents = SingleSourceFor("unary_logical_not.cs", DefaultGeneratedClass + "unary_logical_not : MonoBehaviour { public virtual bool F(bool b) { return !b; } }"); 87 | 88 | AssertConversion(sourceFiles, expectedConvertedContents); 89 | } 90 | 91 | [Test] 92 | public void Operators_Are_Handled() 93 | { 94 | var sourceFiles = SingleSourceFor("operator_expression_type.js", "import UnityScript2CSharp.Tests; function F(op:Operators) { return (op * 1.2f).Message; }"); 95 | var expectedConvertedContents = SingleSourceFor("operator_expression_type.cs", "using UnityScript2CSharp.Tests; " + DefaultGeneratedClass + "operator_expression_type : MonoBehaviour { public virtual string F(Operators op) { return (op * 1.2f).Message; } }"); 96 | 97 | AssertConversion(sourceFiles, expectedConvertedContents); 98 | } 99 | 100 | [Test, TestCaseSource("Arithmetic_Operators_With_Object_Typed_Var_Scenarios")] 101 | public void Arithmetic_Operators_With_Object_Typed_Var(string type, string usExpression, string csExpression) 102 | { 103 | var sourceFiles = SingleSourceFor("arithmetic_operators_object_operand.js", $"function F(f:{type}, o:Object) : boolean {{ return {usExpression}; }}"); 104 | var expectedConvertedContents = SingleSourceFor("arithmetic_operators_object_operand.cs", DefaultGeneratedClass + $"arithmetic_operators_object_operand : MonoBehaviour {{ public virtual bool F({type} f, object o) {{ return {csExpression}; }} }}"); 105 | 106 | AssertConversion(sourceFiles, expectedConvertedContents); 107 | } 108 | 109 | protected static IEnumerable Arithmetic_Operators_With_Object_Typed_Var_Scenarios() 110 | { 111 | var typeValueMapping = new Dictionary 112 | { 113 | {"float", "1F"}, 114 | {"int", "1"}, 115 | {"long", "1"}, 116 | }; 117 | 118 | foreach (var typeName in new[] {"float", "int", "long"}) 119 | { 120 | var valueSufix = typeName[0] != 'i' ? typeName.Substring(0, 1) : ""; 121 | 122 | yield return new TestCaseData(typeName, $"f > o + 1{valueSufix}", $"f > ((({typeName}) o) + 1{valueSufix})").SetName($"Simple ({typeName})"); 123 | yield return new TestCaseData(typeName, $"o + 1{valueSufix} > f", $"((({typeName}) o) + 1{valueSufix}) > f").SetName($"Simle - Reversed ({typeName})"); 124 | yield return new TestCaseData(typeName, $"(f > 2) && (f > o + 1{valueSufix})", $"(f > 2) && (f > ((({typeName}) o) + 1{valueSufix}))").SetName($"Composed ({typeName})"); 125 | yield return new TestCaseData(typeName, $"(f > 2) && (f > o + 1{valueSufix})", $"(f > 2) && (f > ((({typeName}) o) + 1{valueSufix}))").SetName($"Composed - Reversed ({typeName})"); 126 | } 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /UnityScript2CSharp.Tests/Tests.Statements.Switch.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using NUnit.Framework; 3 | 4 | namespace UnityScript2CSharp.Tests 5 | { 6 | public partial class Tests 7 | { 8 | // This test proves that switch handling does not break due to handling of labels/gotos required in some "for" statement constructs 9 | [Test] 10 | public void Conditional_Continue_Mixed_With_Switch_Inside_For() 11 | { 12 | var sourceFiles = SingleSourceFor("conditional_continue_with_switch.js", "function F() { for (var x = 1; x < 10; x++) { switch(x) { case 1: x = x + 1; break; case 2: continue; }; x = x; } }"); 13 | var expectedConvertedContents = SingleSourceFor("conditional_continue_with_switch.cs", DefaultGeneratedClass + @"conditional_continue_with_switch : MonoBehaviour { public virtual void F() { int x = 1; while (x < 10) { switch (x) { case 1: x = x + 1; break; case 2: goto Label_for_1; } x = x; Label_for_1: x++; } } }"); 14 | 15 | AssertConversion(sourceFiles, expectedConvertedContents); 16 | } 17 | 18 | [TestCaseSource("Switch_On_Non_Const_Scenarios")] 19 | public void Switch_On_Non_Const(string us, string cs) 20 | { 21 | var sourceFiles = SingleSourceFor("switch_non_const.js", us); 22 | var expectedConvertedContents = SingleSourceFor("switch_non_const.cs", DefaultGeneratedClass + "switch_non_const : MonoBehaviour { " + cs + " }"); 23 | 24 | AssertConversion(sourceFiles, expectedConvertedContents); 25 | } 26 | 27 | [TestCase("i")] 28 | [TestCase("i + 1")] 29 | [TestCase("System.Environment.ProcessorCount")] 30 | public void Switch(string condition) 31 | { 32 | var sourceFiles = SingleSourceFor("switch_statement.js", $"function F(i:int) {{ switch({condition}) {{ case 1: return 1; case 2: return 2; default: return 3; }} }}"); 33 | var expectedConvertedContents = SingleSourceFor("switch_statement.cs", DefaultGeneratedClass + $"switch_statement : MonoBehaviour {{ public virtual int F(int i) {{ switch ({condition}) {{ case 1: return 1; case 2: return 2; default: return 3; break; }} }} }}"); 34 | 35 | AssertConversion(sourceFiles, expectedConvertedContents); 36 | } 37 | 38 | [Test] 39 | public void Switch_Multiple_Statements() 40 | { 41 | var sourceFiles = SingleSourceFor("switch_multiple_statements.js", "function F(i:int) { var l:int; switch(i) { case 1: l = i; i = i + 1; break; case 2: i = 0; break; } return l + i; }"); 42 | var expectedConvertedContents = SingleSourceFor("switch_multiple_statements.cs", DefaultGeneratedClass + "switch_multiple_statements : MonoBehaviour { public virtual int F(int i) { int l = 0; switch (i) { case 1: l = i; i = i + 1; break; case 2: i = 0; break; } return l + i; } }"); 43 | 44 | AssertConversion(sourceFiles, expectedConvertedContents); 45 | } 46 | 47 | [Test] 48 | public void Switch_On_Extern_Enum() 49 | { 50 | var sourceFiles = SingleSourceFor("switch_on_enum.js", "import System; function F(t:DateTimeKind) { switch(t) { case DateTimeKind.Utc: return 0; case DateTimeKind.Local: return 1; } return 2; }"); 51 | var expectedConvertedContents = SingleSourceFor("switch_on_enum.cs", "using System; " + DefaultGeneratedClass + "switch_on_enum : MonoBehaviour { public virtual int F(DateTimeKind t) { switch (t) { case DateTimeKind.Utc: return 0; case DateTimeKind.Local: return 1; } return 2; } }"); 52 | 53 | AssertConversion(sourceFiles, expectedConvertedContents); 54 | } 55 | 56 | [Test] 57 | public void Switch_On_Local_Enum() 58 | { 59 | var sourceFiles = SingleSourceFor("switch_local_enum.js", "enum E { First, Second } function F(t:E) { switch(t) { case E.First: return 0; case E.Second: return 1; } return 2; }"); 60 | var expectedConvertedContents = SingleSourceFor("switch_local_enum.cs", DefaultUsings + " public enum E { First = 0, Second = 1 } [System.Serializable] public partial class switch_local_enum : MonoBehaviour { public virtual int F(E t) { switch (t) { case E.First: return 0; case E.Second: return 1; } return 2; } }"); 61 | 62 | AssertConversion(sourceFiles, expectedConvertedContents); 63 | } 64 | 65 | [Test] 66 | public void Switch_Fall_Through() 67 | { 68 | var sourceFiles = SingleSourceFor("switch_fall_through.js", "function F(i:int) { switch(i) { case 1: case 2: return 0; break; default: i = 10; break; } }"); 69 | var expectedConvertedContents = SingleSourceFor("switch_fall_through.cs", DefaultGeneratedClass + "switch_fall_through : MonoBehaviour { public virtual int F(int i) { switch (i) { case 1: case 2: return 0; break; default: i = 10; break; } } }"); 70 | 71 | AssertConversion(sourceFiles, expectedConvertedContents); 72 | } 73 | 74 | [Test] 75 | public void Switch_With_If_Statement_In_Default_Case() 76 | { 77 | var sourceFiles = SingleSourceFor("switch_default_if.js", "function F(i:int) { switch(i) { case 1: return 1; break; default: if (i > 10) return 1; else i = 8; } }"); 78 | var expectedConvertedContents = SingleSourceFor("switch_default_if.cs", DefaultGeneratedClass + "switch_default_if : MonoBehaviour { public virtual int F(int i) { switch (i) { case 1: return 1; break; default: if (i > 10) { return 1; } else { i = 8; } break; } } }"); 79 | 80 | AssertConversion(sourceFiles, expectedConvertedContents); 81 | } 82 | 83 | [TestCase("i")] 84 | [TestCase("++i")] 85 | public void Switch_With_Only_Default(string condition) 86 | { 87 | var sourceFiles = SingleSourceFor("switch_only_default.js", $"function F(i:int) {{ switch({condition}) {{ default: if (i != 0) i = 10; }} return i; }}"); 88 | var expectedConvertedContents = SingleSourceFor("switch_only_default.cs", DefaultGeneratedClass + $"switch_only_default : MonoBehaviour {{ public virtual int F(int i) {{ int _switch_1 = {condition}; {{ if (i != 0) {{ i = 10; }} }} return i; }} }}"); 89 | 90 | AssertConversion(sourceFiles, expectedConvertedContents); 91 | } 92 | 93 | [Test] 94 | public void Switch_With_Only_Default_Simplest() 95 | { 96 | var sourceFiles = SingleSourceFor("switch_only_default_simplest.js", "function F(i:int) { switch(i) { default: i++; } }"); 97 | var expectedConvertedContents = SingleSourceFor("switch_only_default_simplest.cs", DefaultGeneratedClass + "switch_only_default_simplest : MonoBehaviour { public virtual void F(int i) { int _switch_1 = i; { i++; } } }"); 98 | 99 | AssertConversion(sourceFiles, expectedConvertedContents); 100 | } 101 | 102 | [TestCase("s")] 103 | [TestCase("System.Console.Title")] 104 | [TestCase("System.Console.ReadLine()")] 105 | public void Switch_Over_String(string condition) 106 | { 107 | var sourceFiles = SingleSourceFor("switch_over_string.js", $"function F(s:String) {{ switch({condition}) {{ case \"foo\": return 1; case \"bar\": return 2; }} }}"); 108 | var expectedConvertedContents = SingleSourceFor("switch_over_string.cs", DefaultGeneratedClass + $"switch_over_string : MonoBehaviour {{ public virtual int F(string s) {{ switch ({condition}) {{ case \"foo\": return 1; case \"bar\": return 2; }} }} }}"); 109 | 110 | AssertConversion(sourceFiles, expectedConvertedContents); 111 | } 112 | 113 | 114 | [Test] 115 | public void Switch_With_Int_Expression_As_Condition_And_Enum_As_Comparison() 116 | { 117 | var sourceFiles = SingleSourceFor("switch_int_enum.js", "enum E {A, B} function F(i:int) { switch(i) { case E.A: return 1; case E.B: return 2; } return 0; }"); 118 | var expectedConvertedContents = SingleSourceFor("switch_int_enum.cs", DefaultUsings + " public enum E { A = 0, B = 1 } " + SerializableAttr + " public partial class switch_int_enum : MonoBehaviour { public virtual int F(int i) { switch ((E) i) { case E.A: return 1; case E.B: return 2; } return 0; } }"); 119 | 120 | AssertConversion(sourceFiles, expectedConvertedContents); 121 | } 122 | 123 | private static IEnumerable Switch_On_Non_Const_Scenarios() 124 | { 125 | yield return new TestCaseData( 126 | "function F(name:String, tbc:String) { switch(name) { case tbc: return 1; default: return 3; } }", 127 | "public virtual int F(string name, string tbc) { switch (name) { default: if (name == tbc) { return 1; } return 3; break; } }") 128 | .SetName("Existing Default"); 129 | 130 | yield return new TestCaseData( 131 | "function F(name:String, tbc:String) { switch(name) { case tbc: return 1; } }", 132 | "public virtual int F(string name, string tbc) { switch (name) { default: if (name == tbc) { return 1; } break; } }") 133 | .SetName("With No Default"); 134 | 135 | yield return new TestCaseData( 136 | "function F(name:String, tbc:String) { switch(name) { case System.Environment.MachineName: return 1; case tbc: return 2; } }", 137 | "public virtual int F(string name, string tbc) { switch (name) { default: if (name == System.Environment.MachineName) { return 1; } if (name == tbc) { return 2; } break; } }") 138 | .SetName("Multiple Non Constant Expressions"); 139 | 140 | yield return new TestCaseData( 141 | "function F(i:int, tbc:int) { switch(i) { case 1: return -1; case tbc: return 2; } }", 142 | "public virtual int F(int i, int tbc) { switch (i) { case 1: return -1; default: if (i == tbc) { return 2; } break; } }") 143 | .SetName("Mixed Const/Non Const"); 144 | 145 | yield return new TestCaseData( 146 | "function F(i:int) { switch(i) { case System.Environment.ProcessorCount:\r\ncase i:\r\n return -1; } }", 147 | "public virtual int F(int i) { switch (i) { default: if ((i == System.Environment.ProcessorCount) || (i == i)) { return -1; } break; } }") 148 | .SetName("With Fall Throughs"); 149 | 150 | yield return new TestCaseData( 151 | "function F(i:int) { switch(i) { case i:\r\ncase 42:\r\n return -1; } }", 152 | "public virtual int F(int i) { switch (i) { default: if ((i == i) || (i == 42)) { return -1; } break; } }") 153 | .SetName("With Mixed Fall Throughs - Simple References"); 154 | 155 | yield return new TestCaseData( 156 | "function F(i:int) { switch(i) { case System.Environment.ProcessorCount:\r\ncase 42:\r\n return -1; } }", 157 | "public virtual int F(int i) { switch (i) { default: if ((i == System.Environment.ProcessorCount) || (i == 42)) { return -1; } break; } }") 158 | .SetName("With Mixed Fall Throughs - Property"); 159 | 160 | yield return new TestCaseData( 161 | "function M(i:int) { return i; } function F(i:int) { switch(i) { case M(i):\r\ncase 42:\r\n return -1; } }", 162 | "public virtual int M(int i) { return i; } public virtual int F(int i) { switch (i) { default: if ((i == this.M(i)) || (i == 42)) { return -1; } break; } }") 163 | .SetName("With Mixed Fall Throughs - Method"); 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /UnityScript2CSharp.Tests/Tests.Statements.Yield.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | 3 | namespace UnityScript2CSharp.Tests 4 | { 5 | public partial class Tests 6 | { 7 | [TestCase("1", "1", TestName = "Literal")] 8 | [TestCase("System.Text.StringBuilder", "typeof(System.Text.StringBuilder)", TestName = "Type")] 9 | public void Yield_Return_Type_Inference(string usExpression, string csExpression) 10 | { 11 | var sourceFiles = SingleSourceFor("yield_return_type.js", $"function F() {{ yield {usExpression}; }}"); 12 | var expectedConvertedContents = SingleSourceFor("yield_return_type.cs", DefaultGeneratedClass + $"yield_return_type : MonoBehaviour {{ public virtual IEnumerator F() {{ yield return {csExpression}; }} }}"); 13 | 14 | AssertConversion(sourceFiles, expectedConvertedContents); 15 | } 16 | 17 | [Test] 18 | public void Mixed_Yield_With_And_Without_Values() 19 | { 20 | var sourceFiles = SingleSourceFor("mixed_yield_without_values.js", "function F() { yield 1; yield ; yield 3; }"); 21 | var expectedConvertedContents = SingleSourceFor("mixed_yield_without_values.cs", DefaultGeneratedClass + "mixed_yield_without_values : MonoBehaviour { public virtual IEnumerator F() { yield return 1; yield return null; yield return 3; } }"); 22 | 23 | AssertConversion(sourceFiles, expectedConvertedContents); 24 | } 25 | 26 | [Test] 27 | public void Yield_Without_Values() 28 | { 29 | var sourceFiles = SingleSourceFor("yield_without_values.js", "function F(l:int[]) { for (var i in l) { yield; } }"); 30 | var expectedConvertedContents = SingleSourceFor("yield_without_values.cs", DefaultGeneratedClass + "yield_without_values : MonoBehaviour { public virtual IEnumerator F(int[] l) { foreach (int i in l) { yield return null; } } }"); 31 | 32 | AssertConversion(sourceFiles, expectedConvertedContents); 33 | } 34 | 35 | [Test] 36 | public void Return_As_Yield_Break() 37 | { 38 | var sourceFiles = SingleSourceFor("yield_break.js", "function F(i:int) { while (i < 10) { if (i % 2 == 0) return; yield i++; } }"); 39 | var expectedConvertedContents = SingleSourceFor("yield_break.cs", DefaultGeneratedClass + "yield_break : MonoBehaviour { public virtual IEnumerator F(int i) { while (i < 10) { if ((i % 2) == 0) { yield break; } yield return i++; } } }"); 40 | 41 | AssertConversion(sourceFiles, expectedConvertedContents); 42 | } 43 | 44 | [Test] 45 | public void StartCoroutine_Is_Called_At_Call_Site_Of_Methods_That_Return_IEnumerator() 46 | { 47 | var sourceFiles = SingleSourceFor("startcoroutine.js", "function IEnumeratorMethod() : IEnumerator { return null; } function F() { IEnumeratorMethod(); yield 1;}"); 48 | var expectedConvertedContents = SingleSourceFor("startcoroutine.cs", DefaultGeneratedClass + "startcoroutine : MonoBehaviour { public virtual IEnumerator IEnumeratorMethod() { return null; } public virtual IEnumerator F() { this.StartCoroutine(this.IEnumeratorMethod()); yield return 1; } }"); 49 | 50 | AssertConversion(sourceFiles, expectedConvertedContents); 51 | } 52 | 53 | [Test] 54 | public void Yield_Variables() 55 | { 56 | var sourceFiles = SingleSourceFor("yield_variables.js", "function F() { var o:Object = null; yield o; }"); 57 | var expectedConvertedContents = SingleSourceFor("yield_variables.cs", DefaultGeneratedClass + "yield_variables : MonoBehaviour { public virtual IEnumerator F() { object o = null; yield return o; } }"); 58 | 59 | AssertConversion(sourceFiles, expectedConvertedContents); 60 | } 61 | 62 | [Test] public void Yield_Fieds() 63 | { 64 | var sourceFiles = SingleSourceFor("yield_fields.js", "var o:Object = null; function F() { yield o; }"); 65 | var expectedConvertedContents = SingleSourceFor("yield_fields.cs", DefaultGeneratedClass + "yield_fields : MonoBehaviour { public object o; public virtual IEnumerator F() { yield return this.o; } }"); 66 | 67 | AssertConversion(sourceFiles, expectedConvertedContents); 68 | } 69 | 70 | [Test] public void Yield_Parameters() 71 | { 72 | var sourceFiles = SingleSourceFor("yield_parameters.js", "function F(o:Object) { yield o; }"); 73 | var expectedConvertedContents = SingleSourceFor("yield_parameters.cs", DefaultGeneratedClass + "yield_parameters : MonoBehaviour { public virtual IEnumerator F(object o) { yield return o; } }"); 74 | 75 | AssertConversion(sourceFiles, expectedConvertedContents); 76 | } 77 | 78 | [Test] public void Yield_Method_Return() 79 | { 80 | var sourceFiles = SingleSourceFor("yield_method_return.js", "function M() { return 10; } function F() { yield M(); }"); 81 | var expectedConvertedContents = SingleSourceFor("yield_method_return.cs", DefaultGeneratedClass + "yield_method_return : MonoBehaviour { public virtual int M() { return 10; } public virtual IEnumerator F() { yield return this.M(); } }"); 82 | 83 | AssertConversion(sourceFiles, expectedConvertedContents); 84 | } 85 | 86 | [Test] public void Yield_Property() 87 | { 88 | var sourceFiles = SingleSourceFor("yield_property.js", "function F(s:String) { yield s.Length; }"); 89 | var expectedConvertedContents = SingleSourceFor("yield_property.cs", DefaultGeneratedClass + "yield_property : MonoBehaviour { public virtual IEnumerator F(string s) { yield return s.Length; } }"); 90 | 91 | AssertConversion(sourceFiles, expectedConvertedContents); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /UnityScript2CSharp.Tests/Tests.Utilities.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Text.RegularExpressions; 7 | using Microsoft.Win32; 8 | using NUnit.Framework; 9 | 10 | namespace UnityScript2CSharp.Tests 11 | { 12 | public partial class Tests 13 | { 14 | public SourceFile[] SingleSourceFor(string fileName, string contents) 15 | { 16 | return new[] { new SourceFile { FileName = fileName, Contents = contents } }; 17 | } 18 | 19 | private static string UnityInstallFolder 20 | { 21 | get 22 | { 23 | var installFolder = Environment.GetEnvironmentVariable("UNITY_INSTALL_FOLDER") ?? @"d:\Work\Repo\Trunk\Build\WindowsEditor\"; 24 | if (installFolder != null) 25 | return installFolder; 26 | 27 | switch (Environment.OSVersion.Platform) 28 | { 29 | case PlatformID.MacOSX: 30 | return ""; 31 | 32 | case PlatformID.Unix: 33 | return ""; 34 | } 35 | 36 | var installKey = Registry.CurrentUser.OpenSubKey(@"Software\Unity Technologies\Installer\Unity\"); 37 | if (installKey != null) 38 | { 39 | installFolder = (string) installKey.GetValue("Location x64"); 40 | if (installFolder != null) 41 | return Path.Combine(installFolder, "Editor/"); 42 | } 43 | 44 | throw new Exception("Could not find Unity installation."); 45 | } 46 | } 47 | 48 | private static void AssertConversion(IList sourceFiles, IList expectedConverted, bool expectError = false, bool verboseLog = false) 49 | { 50 | var tempFolder = Path.Combine(Path.GetTempPath(), "UnityScript2CSharpConversionTests", SavePathFrom(TestContext.CurrentContext.Test.Name)); 51 | var converter = ConvertScripts(sourceFiles, tempFolder, verboseLog); 52 | if (!expectError) 53 | { 54 | if (converter.CompilerErrors.Any()) 55 | { 56 | Assert.Fail("Error while compiling UnityScript sources:" + converter.CompilerErrors.Aggregate("\t", (acc, curr) => acc + Environment.NewLine + "\t" + curr + Environment.NewLine + "\t" + curr)); 57 | } 58 | } 59 | else 60 | { 61 | if (!converter.CompilerErrors.Any()) 62 | Assert.Fail("Expected error."); 63 | 64 | TestContext.WriteLine(converter.CompilerErrors.Aggregate("\t", (acc, curr) => acc + Environment.NewLine + "\t" + curr + Environment.NewLine + "\t" + curr)); 65 | return; 66 | } 67 | 68 | var r = new Regex("\\s{2,}|\\r\\n|\\n(?!\\r)", RegexOptions.Multiline | RegexOptions.Compiled); 69 | for (int i = 0; i < sourceFiles.Count; i++) 70 | { 71 | var convertedFilePath = Path.Combine(tempFolder, expectedConverted[i].FileName); 72 | Assert.That(File.Exists(convertedFilePath), Is.True); 73 | 74 | var generatedScript = File.ReadAllText(convertedFilePath); 75 | generatedScript = r.Replace(generatedScript, " "); 76 | 77 | var expected = r.Replace(expectedConverted[i].Contents, " "); 78 | Assert.That(generatedScript.Trim(), Is.EqualTo(expected), Environment.NewLine + "Converted: " + Environment.NewLine + generatedScript + Environment.NewLine); 79 | } 80 | } 81 | 82 | private static UnityScript2CSharpConverter ConvertScripts(IList sourceFiles, string saveToFolder, bool verboseLog = false) 83 | { 84 | Console.WriteLine("Converted files saved to: {0}", saveToFolder); 85 | 86 | var converter = new UnityScript2CSharpConverter(true, skipComments:false, checkOrphanComments:false, verboseLog:verboseLog); 87 | 88 | Action onScriptConverted = (name, content, unsupportedCount) => 89 | { 90 | var targetFilePath = Path.Combine(saveToFolder, Path.GetFileNameWithoutExtension(name) + ".cs"); 91 | var targetFolder = Path.GetDirectoryName(targetFilePath); 92 | if (!Directory.Exists(targetFolder)) 93 | Directory.CreateDirectory(targetFolder); 94 | 95 | File.WriteAllText(targetFilePath, content); 96 | }; 97 | 98 | var referencedAssemblies = new[] 99 | { 100 | typeof(object).Assembly.Location, 101 | $@"{UnityInstallFolder}Data/Managed/UnityEngine.dll", 102 | $@"{UnityInstallFolder}Data/Managed/UnityEditor.dll", 103 | }; 104 | 105 | sourceFiles = sourceFiles.Select(s => new SourceFile(Path.Combine(Directory.GetCurrentDirectory(), s.FileName), s.Contents)).ToList(); 106 | 107 | converter.Convert(sourceFiles, new[] {"MY_DEFINE"}, referencedAssemblies, onScriptConverted); 108 | 109 | return converter; 110 | } 111 | 112 | private static string SavePathFrom(string testName) 113 | { 114 | var sb = new StringBuilder(testName); 115 | foreach (var invalidPathChar in Path.GetInvalidFileNameChars()) 116 | { 117 | sb.Replace(invalidPathChar, '_'); 118 | } 119 | return sb.ToString(); 120 | } 121 | 122 | private const string UsingSystemCollections = "using System.Collections;"; 123 | private const string DefaultUsings = "using UnityEngine; " + UsingSystemCollections; 124 | private const string DefaultUsingsNoUnityType = "using System.Collections;" + SerializableAttr; 125 | private const string SerializableAttr = " [System.Serializable]"; 126 | private const string DefaultUsingsForClasses = DefaultUsings + SerializableAttr; 127 | private const string DefaultGeneratedClass = DefaultUsingsForClasses + " public partial class "; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /UnityScript2CSharp.Tests/UnityScript2CSharp.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {C6C49D9A-1CCF-4B0D-8CC2-B35998C11C42} 8 | Library 9 | Properties 10 | UnityScript2CSharp.Tests 11 | UnityScript2CSharp.Tests 12 | v4.6.1 13 | 512 14 | 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | pdbonly 27 | true 28 | bin\Release\ 29 | TRACE 30 | prompt 31 | 4 32 | 33 | 34 | 35 | False 36 | ..\UnityScript2CSharp\Libs\Boo.Lang.Compiler.dll 37 | 38 | 39 | ..\packages\NUnit.3.7.0\lib\net45\nunit.framework.dll 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | {72c806db-029c-4435-ba36-677440400e21} 69 | UnityScript2CSharp 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /UnityScript2CSharp.Tests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /UnityScript2CSharp.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26403.7 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnityScript2CSharp", "UnityScript2CSharp\UnityScript2CSharp.csproj", "{72C806DB-029C-4435-BA36-677440400E21}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnityScript2CSharp.Tests", "UnityScript2CSharp.Tests\UnityScript2CSharp.Tests.csproj", "{C6C49D9A-1CCF-4B0D-8CC2-B35998C11C42}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {72C806DB-029C-4435-BA36-677440400E21}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {72C806DB-029C-4435-BA36-677440400E21}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {72C806DB-029C-4435-BA36-677440400E21}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {72C806DB-029C-4435-BA36-677440400E21}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {C6C49D9A-1CCF-4B0D-8CC2-B35998C11C42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {C6C49D9A-1CCF-4B0D-8CC2-B35998C11C42}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {C6C49D9A-1CCF-4B0D-8CC2-B35998C11C42}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {C6C49D9A-1CCF-4B0D-8CC2-B35998C11C42}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /UnityScript2CSharp/AnchorKind.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace UnityScript2CSharp 4 | { 5 | [Flags] 6 | enum AnchorKind 7 | { 8 | None, 9 | Above, 10 | Below, 11 | Left, 12 | Right, 13 | 14 | All = Above | Below | Left | Right 15 | } 16 | } -------------------------------------------------------------------------------- /UnityScript2CSharp/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /UnityScript2CSharp/BlockIdentation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace UnityScript2CSharp 4 | { 5 | internal class BlockIdentation : IDisposable 6 | { 7 | private readonly Writer _identationAware; 8 | 9 | public BlockIdentation(Writer identationAware) 10 | { 11 | _identationAware = identationAware; 12 | _identationAware.Identation++; 13 | } 14 | 15 | public void Dispose() 16 | { 17 | _identationAware.Identation--; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /UnityScript2CSharp/CommandLineArguments.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using CommandLine; 3 | 4 | namespace UnityScript2CSharp 5 | { 6 | public class CommandLineArguments 7 | { 8 | [Option('u', "unityPath", Required = false, HelpText = "Unity installation path. By default the tool will attempt to automatically locate your Unity install.")] 9 | public string UnityPath { get; set; } 10 | 11 | [Option('p', "projectPath", Required = true, HelpText = "Path of project to be converted.")] 12 | public string ProjectPath { get; set; } 13 | 14 | [Option('r', "references", Min = 0, Max = 100, HelpText = "Assembly references required by the scripts (space separated list).")] 15 | public IList InternalReferences { get; set; } 16 | 17 | [Option('g', "no-game-assemblies", HelpText = "Ignores previously built game assemblies references (Assembly-*.dll under Library/).")] public bool IgnoreGameAssemblyReferences { get; set; } 18 | 19 | [Option('s', "symbols", HelpText = "A (comma separated) list of custom symbols to be defined.")] 20 | public string SymbolsStr 21 | { 22 | get { return string.Join(",", _symbols); } 23 | set { _symbols = new List(value.Split(',')); } 24 | } 25 | 26 | [Option('o', "deleteOriginals", HelpText = "Deletes original files (default is to rename).")] public bool RemoveOriginalFiles { get; set; } 27 | 28 | [Option('d', HelpText = "Dumps out the list of scripts being processed.")] public bool DumpScripts { get; set; } 29 | 30 | [Option('i', HelpText = "Ignore compilation errors. This allows the conversion process to continue instead of aborting.")] public bool IgnoreErrors { get; set; } 31 | 32 | [Option(HelpText = "Do not try to preserve comments (Use this option if processing comments cause any issues).", DefaultValue = false)] public bool SkipComments { get; set; } 33 | 34 | [Option(HelpText = "Show a list of comments that were not written to the converted sources (used to help identifying issues with the comment processing code).")] public bool ShowOrphanComments { get; set; } 35 | 36 | [Option('n', "dry-run", HelpText = "Run the conversion but do not change/create any files on disk.")] public bool DryRun { get; set; } 37 | 38 | [Option('v', "verbose", HelpText = "Show verbose messages.")] public bool Verbose { get; set; } 39 | 40 | [Option("outputFile", HelpText = "Path of file to be used to write messages instead of the console.")] public string OutputFile { get; set; } 41 | 42 | [Option("responseFile")] public string ResponseFile { get; set; } 43 | 44 | public IList Symbols { get { return _symbols; } } 45 | 46 | public IList References 47 | { 48 | get 49 | { 50 | if (_references == null) 51 | _references = new List(InternalReferences); 52 | 53 | return _references; 54 | } 55 | } 56 | 57 | private IList _symbols = new List(); 58 | private IList _references = null; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /UnityScript2CSharp/Comment.cs: -------------------------------------------------------------------------------- 1 | using antlr; 2 | using Boo.Lang.Compiler.Ast; 3 | 4 | namespace UnityScript2CSharp 5 | { 6 | class Comment 7 | { 8 | public IToken Token; 9 | 10 | public CommentKind CommentKind; 11 | public AnchorKind AnchorKind; 12 | 13 | public Node BestCandidate; 14 | public int Distance; 15 | 16 | public Comment(IToken token, CommentKind commentKind, IToken previous) 17 | { 18 | BestCandidate = null; 19 | Distance = 0; 20 | 21 | CommentKind = commentKind; 22 | Token = token; 23 | PreviousToken = previous; 24 | AnchorKind = AnchorKind.None; 25 | } 26 | 27 | public IToken PreviousToken { get; private set; } 28 | 29 | public static implicit operator bool(Comment c) 30 | { 31 | return c.BestCandidate != null; 32 | } 33 | 34 | public override string ToString() 35 | { 36 | return $"[Comment] {Token.getFilename()} ({Token.getLine()}, {Token.getColumn()}) : {Token.getText()}"; 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /UnityScript2CSharp/CommentKind.cs: -------------------------------------------------------------------------------- 1 | namespace UnityScript2CSharp 2 | { 3 | enum CommentKind 4 | { 5 | SingleLine, 6 | MultipleLine 7 | } 8 | } -------------------------------------------------------------------------------- /UnityScript2CSharp/CompilerErrorComparer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Boo.Lang.Compiler; 4 | 5 | namespace UnityScript2CSharp 6 | { 7 | internal class CompilerErrorComparer : IEqualityComparer 8 | { 9 | public bool Equals(CompilerError x, CompilerError y) 10 | { 11 | return x.LexicalInfo.CompareTo(y.LexicalInfo) == 0; 12 | } 13 | 14 | public int GetHashCode(CompilerError obj) 15 | { 16 | return obj.LexicalInfo.GetHashCode(); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /UnityScript2CSharp/EntityExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Boo.Lang.Compiler.TypeSystem; 4 | using Boo.Lang.Compiler.TypeSystem.Reflection; 5 | 6 | namespace UnityScript2CSharp 7 | { 8 | internal static class EntityExtensions 9 | { 10 | public static bool IsBoolean(this IEntity entity) 11 | { 12 | var typedEntity = (ITypedEntity)entity; 13 | if (typedEntity.Type.IsArray) 14 | return IsBoolean(((IArrayType)typedEntity.Type).ElementType); 15 | 16 | return typedEntity.Type.FullName == "boolean" || typedEntity.Type.GetMembers().OfType().Any(m => m.Name == "op_Implicit" && IsBoolean(m.ReturnType)); 17 | } 18 | 19 | public static string DefaultValue(this IEntity entity) 20 | { 21 | var typedEntity = (ITypedEntity)entity; 22 | 23 | if (TypeSystemServices.IsReferenceType(typedEntity.Type)) 24 | return "null"; 25 | 26 | switch (typedEntity.Type.FullName) 27 | { 28 | case "double": 29 | case "float": return "0.0f"; 30 | 31 | case "int": 32 | case "long": 33 | case "byte": return "0"; 34 | 35 | case "char": return "'\0'"; 36 | case "boolean": return "false"; 37 | } 38 | 39 | return $"default({typedEntity.Type.Name})"; 40 | } 41 | 42 | public static string TypeName(this IEntity entity, IList usings) 43 | { 44 | string typeName = null; 45 | var externalType = entity as ExternalType; 46 | 47 | if (externalType != null) 48 | { 49 | switch (externalType.ActualType.FullName) 50 | { 51 | case "System.String": 52 | typeName = "string"; 53 | break; 54 | 55 | case "System.Boolean": 56 | typeName = "bool"; 57 | break; 58 | 59 | case "System.Object": 60 | typeName = "object"; 61 | break; 62 | 63 | case "System.Int32": 64 | typeName = "int"; 65 | break; 66 | 67 | case "System.Int64": 68 | typeName = "long"; 69 | break; 70 | } 71 | 72 | if (typeName == null && usings.Contains(externalType.ActualType.Namespace)) 73 | { 74 | typeName = externalType.Name; 75 | } 76 | } 77 | 78 | return typeName ?? entity.Name; 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /UnityScript2CSharp/Extensions/ASTNodeExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Boo.Lang.Compiler.Ast; 4 | using Boo.Lang.Compiler.TypeSystem; 5 | 6 | namespace UnityScript2CSharp.Extensions 7 | { 8 | public static class ASTNodeExtensions 9 | { 10 | public static IEnumerable WithExpressionStatementOfType(this StatementCollection source) where T : Expression 11 | { 12 | var found = source.OfType().Where(stmt => stmt.Expression.GetType() == typeof(T)); 13 | return found.Select(stmt => (T) stmt.Expression); 14 | } 15 | 16 | public static bool IsConstructor(this Method node) 17 | { 18 | return node.NodeType == NodeType.Constructor; 19 | } 20 | 21 | public static bool IsConstructorInvocation(this Node node) 22 | { 23 | if (node.NodeType != NodeType.MethodInvocationExpression) 24 | return false; 25 | 26 | var invocation = (MethodInvocationExpression)node; 27 | return invocation.Target.Entity != null && invocation.Target.Entity.EntityType == EntityType.Constructor; 28 | } 29 | 30 | public static bool IsArrayInstantiation(this Node node) 31 | { 32 | if (node.NodeType != NodeType.GenericReferenceExpression) 33 | return false; 34 | 35 | var gre = (GenericReferenceExpression)node; 36 | // Arrays in UnityScript are represented as a GenericReferenceExpession 37 | var target = gre.Target as ReferenceExpression; 38 | return target != null && (target.Name == "array" || target.ToCodeString() == "Boo.Lang.Builtins.matrix"); 39 | } 40 | 41 | public static bool TryExtractSwitchStatementDetails(this Block candidateBlock, out BinaryExpression conditionVarInitialization, out IfStatement firstSwitchCheckStatement, out LabelStatement switchEnd) 42 | { 43 | conditionVarInitialization = null; 44 | firstSwitchCheckStatement = null; 45 | switchEnd = null; 46 | 47 | if (candidateBlock.Statements.Count < 2 || candidateBlock.Statements[0].NodeType != NodeType.ExpressionStatement) 48 | return false; 49 | 50 | conditionVarInitialization = ((ExpressionStatement)candidateBlock.Statements[0]).Expression as BinaryExpression; 51 | 52 | if (!IsSwitchVariableInitialization(conditionVarInitialization)) 53 | return false; 54 | 55 | // Next statement should be something like: "if ($switch$1 == 1)" 56 | firstSwitchCheckStatement = candidateBlock.Statements[1] as IfStatement; 57 | if (firstSwitchCheckStatement == null) 58 | return false; 59 | 60 | var conditionExpression = firstSwitchCheckStatement.Condition as BinaryExpression; 61 | if (conditionExpression == null || !firstSwitchCheckStatement.IsCaseEntry(conditionVarInitialization.Left)) 62 | return false; 63 | 64 | switchEnd = (LabelStatement) candidateBlock.LastStatement; 65 | 66 | return true; 67 | } 68 | 69 | public static bool IsCaseEntry(this IfStatement statement, Expression expectedLocalVarInComparison) 70 | { 71 | var comparison = statement.Condition as BinaryExpression; 72 | if (comparison == null) 73 | return false; 74 | 75 | return IsCaseEntry(comparison, expectedLocalVarInComparison); 76 | } 77 | 78 | public static bool IsCaseEntry(this BinaryExpression comparison, Expression expectedLocalVarInComparison) 79 | { 80 | // First statment of blobk should be something like "$switch$1 = i;" 81 | var candidateLocalVarReference = comparison.Left as ReferenceExpression; 82 | if (candidateLocalVarReference != null && candidateLocalVarReference.Matches(expectedLocalVarInComparison)) 83 | return true; 84 | 85 | var leftAsBinary = comparison.Left as BinaryExpression; 86 | if (leftAsBinary != null) 87 | return IsCaseEntry(leftAsBinary, expectedLocalVarInComparison); 88 | 89 | var leftAsCast = comparison.Left as CastExpression; 90 | if (leftAsCast != null && ((IType)leftAsCast.Type.Entity)?.IsEnum == true) 91 | return true; 92 | 93 | return false; 94 | } 95 | 96 | public static IEnumerable GetSwitchCases(this Block node, BinaryExpression conditionVarInitialization) 97 | { 98 | var cases = node.Statements.OfType().Where(stmt => stmt.IsCaseEntry(conditionVarInitialization.Left) && stmt.Condition.NodeType == NodeType.BinaryExpression); 99 | return cases; 100 | } 101 | 102 | public static bool NeedsQualificationFor(this Node node, INamespace ns) 103 | { 104 | return node.GetAncestors().Any(imp => imp.Namespace == ns.FullName); 105 | } 106 | 107 | public static bool IsDeclarationStatement(this BinaryExpression node) 108 | { 109 | return node.Operator == BinaryOperatorType.Assign && node.Left.NodeType == NodeType.ReferenceExpression && node.IsSynthetic; 110 | } 111 | 112 | public static bool IsEnum(this ReferenceExpression re) 113 | { 114 | var type = re.Entity as IType; 115 | return type != null && type.IsEnum; 116 | } 117 | 118 | private static bool IsSwitchVariableInitialization(BinaryExpression conditionVarInitialization) 119 | { 120 | // First statment of blobk should be something like "$switch$1 = i;" 121 | return conditionVarInitialization != null 122 | && conditionVarInitialization.Left.NodeType == NodeType.ReferenceExpression 123 | && conditionVarInitialization.Left.ToCodeString().Contains("$switch"); 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /UnityScript2CSharp/Extensions/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | 3 | namespace UnityScript2CSharp.Extensions 4 | { 5 | public static class StringExtensions 6 | { 7 | public static bool IsSwitchLabel(this string candidate) 8 | { 9 | return Regex.IsMatch(candidate, @":?\$switch\$\d+"); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /UnityScript2CSharp/Libs/Boo.Lang.Compiler.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unity-Technologies/unityscript2csharp/94adf9c35ecb159626fb4770bc71fc66b8d5d00d/UnityScript2CSharp/Libs/Boo.Lang.Compiler.dll -------------------------------------------------------------------------------- /UnityScript2CSharp/Libs/Boo.Lang.Compiler.pdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unity-Technologies/unityscript2csharp/94adf9c35ecb159626fb4770bc71fc66b8d5d00d/UnityScript2CSharp/Libs/Boo.Lang.Compiler.pdb -------------------------------------------------------------------------------- /UnityScript2CSharp/Libs/Boo.Lang.Extensions.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unity-Technologies/unityscript2csharp/94adf9c35ecb159626fb4770bc71fc66b8d5d00d/UnityScript2CSharp/Libs/Boo.Lang.Extensions.dll -------------------------------------------------------------------------------- /UnityScript2CSharp/Libs/Boo.Lang.Extensions.pdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unity-Technologies/unityscript2csharp/94adf9c35ecb159626fb4770bc71fc66b8d5d00d/UnityScript2CSharp/Libs/Boo.Lang.Extensions.pdb -------------------------------------------------------------------------------- /UnityScript2CSharp/Libs/Boo.Lang.Parser.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unity-Technologies/unityscript2csharp/94adf9c35ecb159626fb4770bc71fc66b8d5d00d/UnityScript2CSharp/Libs/Boo.Lang.Parser.dll -------------------------------------------------------------------------------- /UnityScript2CSharp/Libs/Boo.Lang.Parser.pdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unity-Technologies/unityscript2csharp/94adf9c35ecb159626fb4770bc71fc66b8d5d00d/UnityScript2CSharp/Libs/Boo.Lang.Parser.pdb -------------------------------------------------------------------------------- /UnityScript2CSharp/Libs/Boo.Lang.PatternMatching.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unity-Technologies/unityscript2csharp/94adf9c35ecb159626fb4770bc71fc66b8d5d00d/UnityScript2CSharp/Libs/Boo.Lang.PatternMatching.dll -------------------------------------------------------------------------------- /UnityScript2CSharp/Libs/Boo.Lang.PatternMatching.pdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unity-Technologies/unityscript2csharp/94adf9c35ecb159626fb4770bc71fc66b8d5d00d/UnityScript2CSharp/Libs/Boo.Lang.PatternMatching.pdb -------------------------------------------------------------------------------- /UnityScript2CSharp/Libs/Boo.Lang.Useful.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unity-Technologies/unityscript2csharp/94adf9c35ecb159626fb4770bc71fc66b8d5d00d/UnityScript2CSharp/Libs/Boo.Lang.Useful.dll -------------------------------------------------------------------------------- /UnityScript2CSharp/Libs/Boo.Lang.Useful.pdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unity-Technologies/unityscript2csharp/94adf9c35ecb159626fb4770bc71fc66b8d5d00d/UnityScript2CSharp/Libs/Boo.Lang.Useful.pdb -------------------------------------------------------------------------------- /UnityScript2CSharp/Libs/Boo.Lang.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unity-Technologies/unityscript2csharp/94adf9c35ecb159626fb4770bc71fc66b8d5d00d/UnityScript2CSharp/Libs/Boo.Lang.dll -------------------------------------------------------------------------------- /UnityScript2CSharp/Libs/Boo.Lang.pdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unity-Technologies/unityscript2csharp/94adf9c35ecb159626fb4770bc71fc66b8d5d00d/UnityScript2CSharp/Libs/Boo.Lang.pdb -------------------------------------------------------------------------------- /UnityScript2CSharp/Libs/UnityScript.Lang.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unity-Technologies/unityscript2csharp/94adf9c35ecb159626fb4770bc71fc66b8d5d00d/UnityScript2CSharp/Libs/UnityScript.Lang.dll -------------------------------------------------------------------------------- /UnityScript2CSharp/Libs/UnityScript.Lang.pdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unity-Technologies/unityscript2csharp/94adf9c35ecb159626fb4770bc71fc66b8d5d00d/UnityScript2CSharp/Libs/UnityScript.Lang.pdb -------------------------------------------------------------------------------- /UnityScript2CSharp/Libs/UnityScript.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unity-Technologies/unityscript2csharp/94adf9c35ecb159626fb4770bc71fc66b8d5d00d/UnityScript2CSharp/Libs/UnityScript.dll -------------------------------------------------------------------------------- /UnityScript2CSharp/Libs/UnityScript.pdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unity-Technologies/unityscript2csharp/94adf9c35ecb159626fb4770bc71fc66b8d5d00d/UnityScript2CSharp/Libs/UnityScript.pdb -------------------------------------------------------------------------------- /UnityScript2CSharp/LogModuleName.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Boo.Lang.Compiler; 3 | using Boo.Lang.Compiler.Ast; 4 | using Boo.Lang.Compiler.Steps; 5 | 6 | namespace UnityScript2CSharp 7 | { 8 | internal class LogModuleName : AbstractTransformerCompilerStep 9 | { 10 | public override void OnModule(Module node) 11 | { 12 | if (node.LexicalInfo.IsValid) 13 | { 14 | Console.WriteLine($"Start compiling {node.LexicalInfo.FullPath}"); 15 | base.OnModule(node); 16 | Console.WriteLine($"Finish compiling {node.LexicalInfo.FullPath}"); 17 | } 18 | else 19 | { 20 | base.OnModule(node); 21 | } 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /UnityScript2CSharp/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("UnityScript2CSharp")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("UnityScript2CSharp")] 13 | [assembly: AssemblyCopyright("Unity Technologies Copyright © 2017")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("72c806db-029c-4435-ba36-677440400e21")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.*")] 36 | [assembly: InternalsVisibleTo("UnityScript2CSharp.Tests")] 37 | -------------------------------------------------------------------------------- /UnityScript2CSharp/SourceFile.cs: -------------------------------------------------------------------------------- 1 | namespace UnityScript2CSharp 2 | { 3 | public struct SourceFile 4 | { 5 | public string FileName; 6 | public string Contents; 7 | 8 | public SourceFile(string fileName, string contents) 9 | { 10 | FileName = fileName; 11 | Contents = contents; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /UnityScript2CSharp/Steps/ApplyEnumToImplicitConversions.cs: -------------------------------------------------------------------------------- 1 | using Boo.Lang.Compiler.Ast; 2 | using Boo.Lang.Compiler.Steps; 3 | using Boo.Lang.Compiler.TypeSystem; 4 | using UnityScript2CSharp.Extensions; 5 | 6 | namespace UnityScript2CSharp.Steps 7 | { 8 | class ApplyEnumToImplicitConversions : AbstractTransformerCompilerStep 9 | { 10 | public override void OnBinaryExpression(BinaryExpression node) 11 | { 12 | if (node.Left.ExpressionType == null || node.Right.ExpressionType == null) 13 | { 14 | base.OnBinaryExpression(node); 15 | return; 16 | } 17 | 18 | var oneOperatorIsNotAnEnum = node.Left.ExpressionType.IsEnum ^ node.Right.ExpressionType.IsEnum; 19 | if (!oneOperatorIsNotAnEnum) 20 | { 21 | base.OnBinaryExpression(node); 22 | return; 23 | } 24 | 25 | if (node.Operator == BinaryOperatorType.Assign) 26 | { 27 | node.Replace(node.Right, CodeBuilder.CreateCast(node.Left.ExpressionType, node.Right)); 28 | } 29 | else if (AstUtil.GetBinaryOperatorKind(node.Operator) == BinaryOperatorKind.Comparison) 30 | { 31 | if (node.Left.ExpressionType.IsEnum) 32 | node.Replace(node.Right, CodeBuilder.CreateCast(node.Left.ExpressionType, node.Right)); 33 | else 34 | node.Replace(node.Left, CodeBuilder.CreateCast(node.Right.ExpressionType, node.Left)); 35 | } 36 | 37 | base.OnBinaryExpression(node); 38 | } 39 | 40 | public override void OnMethodInvocationExpression(MethodInvocationExpression node) 41 | { 42 | base.OnMethodInvocationExpression(node); 43 | 44 | if (node.Target.Entity == null || (node.Target.Entity.EntityType != EntityType.Method && node.Target.Entity.EntityType != EntityType.Constructor && node.Target.Entity.EntityType != EntityType.Property)) 45 | return; 46 | 47 | var parameters = ((IMethodBase)node.Target.Entity).GetParameters(); 48 | for (int i = 0; i < node.Arguments.Count && i < parameters.Length; i++) 49 | { 50 | var arg = node.Arguments[i]; 51 | var param = parameters[i]; 52 | 53 | if (arg.ExpressionType.IsEnum ^ param.Type.IsEnum) 54 | { 55 | node.Replace(arg, CodeBuilder.CreateCast(param.Type, arg)); 56 | } 57 | } 58 | } 59 | 60 | public override void OnSlice(Slice node) 61 | { 62 | if (node.Begin.NodeType != NodeType.OmittedExpression && node.Begin.ExpressionType.IsEnum) 63 | { 64 | node.Replace(node.Begin, CodeBuilder.CreateCast(TypeSystemServices.IntType, node.Begin)); 65 | } 66 | 67 | if (node.End != null && node.End.NodeType != NodeType.OmittedExpression && node.End.ExpressionType.IsEnum) 68 | { 69 | node.Replace(node.End, CodeBuilder.CreateCast(TypeSystemServices.IntType, node.End)); 70 | } 71 | 72 | base.OnSlice(node); 73 | } 74 | 75 | public override void OnReturnStatement(ReturnStatement node) 76 | { 77 | var declaringMethod = node.GetAncestor(); 78 | if (declaringMethod.IsConstructor()) 79 | return; 80 | 81 | if (NoReturnValueWasSpecified(node)) 82 | { 83 | base.OnReturnStatement(node); 84 | return; 85 | } 86 | 87 | var declaredReturnType = (IType)declaringMethod.ReturnType.Entity; 88 | if (declaredReturnType.IsEnum ^ node.Expression.ExpressionType.IsEnum) 89 | { 90 | node.Replace(node.Expression, CodeBuilder.CreateCast(declaredReturnType, node.Expression)); 91 | } 92 | 93 | base.OnReturnStatement(node); 94 | } 95 | 96 | private static bool NoReturnValueWasSpecified(ReturnStatement node) 97 | { 98 | return node.Expression == null; 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /UnityScript2CSharp/Steps/AttachComments.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Boo.Lang.Compiler.Ast; 5 | using Boo.Lang.Compiler.Steps; 6 | 7 | namespace UnityScript2CSharp.Steps 8 | { 9 | internal class AttachComments : AbstractTransformerCompilerStep 10 | { 11 | private readonly IDictionary> _comments; 12 | private IList _sourceComments; 13 | private const string COMMENTS_KEY = "COMMENTS"; 14 | 15 | public AttachComments(IDictionary> comments) 16 | { 17 | _comments = comments; 18 | } 19 | 20 | public override void OnModule(Module node) 21 | { 22 | if (node.LexicalInfo.IsValid) 23 | _comments.TryGetValue(node.LexicalInfo.FullPath, out _sourceComments); 24 | 25 | base.OnModule(node); 26 | 27 | AttachRemainingComments(node); 28 | } 29 | 30 | public override void LeaveMethod(Method node) 31 | { 32 | AttachRemainingComments(node); 33 | base.LeaveMethod(node); 34 | } 35 | 36 | protected override void OnNode(Node node) 37 | { 38 | if (!node.LexicalInfo.IsValid || _sourceComments == null || node.NodeType == NodeType.Block || node.IsSynthetic) 39 | { 40 | base.OnNode(node); 41 | return; 42 | } 43 | 44 | // find comments above *node* and either attach them to current node (in case they are orphans) or to the *best candidate* so far. 45 | var commentsAboveNode = _sourceComments.Where(candidate => candidate.Token.getLine() < node.LexicalInfo.Line).ToArray(); 46 | foreach (var comment in commentsAboveNode) 47 | { 48 | if (comment.BestCandidate == null) 49 | { 50 | comment.BestCandidate = node; 51 | comment.AnchorKind = AnchorKind.Above; 52 | } 53 | 54 | var attachedComments = GetAttachedCommentsFrom(comment.BestCandidate); 55 | attachedComments.Add(comment); 56 | 57 | _sourceComments.Remove(comment); 58 | } 59 | 60 | 61 | if (node.Entity != null && node.Entity.FullName == "Boo.Lang.Builtins.array") 62 | return; 63 | 64 | // Handle comments in the same line 65 | var foundOnSameLine = _sourceComments.Where(candidate => candidate.Token.getLine() == node.LexicalInfo.Line); 66 | foreach (var comment in foundOnSameLine) 67 | { 68 | if (!comment) 69 | { 70 | comment.BestCandidate = node; 71 | comment.AnchorKind = AnchorKind.Right; 72 | comment.Distance = Int32.MaxValue; 73 | } 74 | 75 | int distance = 0; 76 | if (node.LexicalInfo.Column > comment.Token.getColumn()) // comment is on left of the AST node 77 | { 78 | var endOfCommentCollumn = comment.Token.getColumn() + comment.Token.getText().Length; 79 | distance = node.LexicalInfo.Column - endOfCommentCollumn; 80 | if (distance <= comment.Distance && distance >= 0) 81 | { 82 | comment.BestCandidate = node; 83 | comment.Distance = distance; 84 | comment.AnchorKind = AnchorKind.Left; 85 | } 86 | } 87 | 88 | // comment sould be on RIGHT of the AST node 89 | var endOfNodeColumn = EndColumnOf(node); 90 | distance = comment.Token.getColumn() - endOfNodeColumn; 91 | if (distance <= comment.Distance && distance >= 0) 92 | { 93 | comment.BestCandidate = node; 94 | comment.Distance = distance; 95 | comment.AnchorKind = AnchorKind.Right; 96 | } 97 | } 98 | 99 | base.OnNode(node); 100 | } 101 | 102 | private void AttachRemainingComments(Node node) 103 | { 104 | if (!node.EndSourceLocation.IsValid) 105 | return; 106 | 107 | foreach (var comment in _sourceComments.Where(comment => comment.Token.getLine() <= node.EndSourceLocation.Line).ToArray()) 108 | { 109 | if (comment.BestCandidate == null) 110 | { 111 | comment.BestCandidate = node; 112 | comment.AnchorKind = AnchorKind.Below; 113 | } 114 | 115 | var attachedComments = GetAttachedCommentsFrom(comment.BestCandidate); 116 | attachedComments.Add(comment); 117 | _sourceComments.Remove(comment); 118 | } 119 | } 120 | 121 | private IList GetAttachedCommentsFrom(Node node) 122 | { 123 | if (!node.ContainsAnnotation(COMMENTS_KEY)) 124 | { 125 | node.Annotate(COMMENTS_KEY, new System.Collections.Generic.List()); 126 | } 127 | 128 | return (IList) node[COMMENTS_KEY]; 129 | } 130 | 131 | // This method returns an approximation for the *end column* of the passed node. 132 | private int EndColumnOf(Node node) 133 | { 134 | switch (node.NodeType) 135 | { 136 | case NodeType.BinaryExpression: return ((BinaryExpression) node).Left.LexicalInfo.Column + node.ToString().Length; 137 | case NodeType.ReturnStatement: return node.LexicalInfo.Column + "return".Length; 138 | case NodeType.IfStatement: 139 | { 140 | var condition = ((IfStatement)node).Condition; 141 | return condition.LexicalInfo.Column + condition.ToString().Length + 1; // consider the ')' after the condition 142 | } 143 | } 144 | 145 | return node.LexicalInfo.Column + node.ToString().Length; 146 | } 147 | } 148 | } -------------------------------------------------------------------------------- /UnityScript2CSharp/Steps/CSharpReservedKeywordIdentifierClashFix.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Boo.Lang.Compiler.Ast; 3 | using Boo.Lang.Compiler.Steps; 4 | using Boo.Lang.Compiler.TypeSystem; 5 | using Boo.Lang.Compiler.TypeSystem.Internal; 6 | 7 | namespace UnityScript2CSharp.Steps 8 | { 9 | // This step is reponsible for renaming identifiers used in UnityScript code that are reserved keywords in C# 10 | // by appending an @ in the identifier 11 | class CSharpReservedKeywordIdentifierClashFix : AbstractTransformerCompilerStep 12 | { 13 | public override void OnClassDefinition(ClassDefinition node) 14 | { 15 | if (TryFixNameClash(node.Name, out string fixedName)) 16 | node.Name = fixedName; 17 | 18 | base.OnClassDefinition(node); 19 | } 20 | 21 | public override void OnEnumDefinition(EnumDefinition node) 22 | { 23 | if (TryFixNameClash(node.Name, out string fixedName)) 24 | node.Name = fixedName; 25 | 26 | base.OnEnumDefinition(node); 27 | } 28 | 29 | public override void OnEnumMember(EnumMember node) 30 | { 31 | if (TryFixNameClash(node.Name, out string fixedName)) 32 | node.Name = fixedName; 33 | 34 | base.OnEnumMember(node); 35 | } 36 | 37 | public override void OnMethod(Method node) 38 | { 39 | if (TryFixNameClash(node.Name, out string fixedName)) 40 | node.Name = fixedName; 41 | 42 | foreach (var parameter in node.Parameters) 43 | { 44 | if (TryFixNameClash(parameter.Name, out fixedName)) 45 | parameter.Name = fixedName; 46 | } 47 | 48 | foreach (var local in node.Locals) 49 | { 50 | if (!_CSharpReservedKeywords.Contains(local.Name)) 51 | continue; 52 | 53 | var internalLocal = local.Entity as InternalLocal; 54 | if (internalLocal != null) 55 | { 56 | local.Name = "@" + local.Name; // we need to set the "local name" here, and the one in the declaration 57 | internalLocal.OriginalDeclaration.Name = local.Name; 58 | } 59 | } 60 | 61 | base.OnMethod(node); 62 | } 63 | 64 | public override void OnField(Field node) 65 | { 66 | if (TryFixNameClash(node.Name, out string fixedName)) 67 | node.Name = fixedName; 68 | 69 | base.OnField(node); 70 | } 71 | 72 | public override void OnMemberReferenceExpression(MemberReferenceExpression node) 73 | { 74 | if (TryFixNameClash(node.Name, out string fixedName)) 75 | node.Name = fixedName; 76 | 77 | base.OnMemberReferenceExpression(node); 78 | } 79 | 80 | public override void OnReferenceExpression(ReferenceExpression node) 81 | { 82 | if (node.Entity == null) 83 | return; 84 | 85 | switch (node.Entity.EntityType) 86 | { 87 | case EntityType.Field: 88 | case EntityType.Parameter: 89 | case EntityType.Property: 90 | case EntityType.Method: 91 | case EntityType.Event: 92 | case EntityType.Local: 93 | if (TryFixNameClash(node.Name, out string fixedName)) 94 | node.Name = fixedName; 95 | break; 96 | } 97 | } 98 | 99 | public override void OnSimpleTypeReference(SimpleTypeReference node) 100 | { 101 | if (node.Entity.EntityType == EntityType.Type && TryFixNameClash(node.Name, out string fixedName)) 102 | node.Name = fixedName; 103 | } 104 | 105 | private bool TryFixNameClash(string name, out string fixedName) 106 | { 107 | fixedName = name; 108 | if (!_CSharpReservedKeywords.Contains(name)) 109 | return false; 110 | 111 | fixedName = "@" + name; 112 | return true; 113 | } 114 | 115 | //Note: keywords that are valid in both *UnityScript* and *CSharp* are not present in this list since they could not be used 116 | // in UnityScript sources anyway. 117 | private ISet _CSharpReservedKeywords = new HashSet() 118 | { 119 | "abstract", 120 | "alias", 121 | "async", 122 | "await", 123 | "base", 124 | "bool", 125 | "checked", 126 | "const", 127 | "decimal", 128 | "delegate", 129 | "dynamic", 130 | "event", 131 | "explicit", 132 | "extern", 133 | "fixed", 134 | "foreach", 135 | "goto", 136 | "implicit", 137 | "is", 138 | "lock", 139 | "nameof", 140 | "namespace", 141 | "object", 142 | "operator", 143 | "out", 144 | "params", 145 | "readonly", 146 | "ref", 147 | "remove", 148 | "sealed", 149 | "sizeof", 150 | "stackalloc", 151 | "string", 152 | "struct", 153 | "unchecked", 154 | "unsafe", 155 | "using", 156 | "volatile" 157 | }; 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /UnityScript2CSharp/Steps/CastInjector.cs: -------------------------------------------------------------------------------- 1 | using Boo.Lang.Compiler.Ast; 2 | using Boo.Lang.Compiler.Steps; 3 | using Boo.Lang.Compiler.TypeSystem; 4 | using Boo.Lang.Compiler.TypeSystem.Reflection; 5 | using Boo.Lang.Runtime; 6 | 7 | namespace UnityScript2CSharp.Steps 8 | { 9 | /* 10 | * There are 2 scenarios in which we inject casts: 11 | * 12 | * 1. Assigning / Passing "big" type to "small" type (long -> int, for instance) 13 | * 2. Assigning / Passing "Object" type to any other type type (object -> String, for instance) 14 | * This is safe because US compiler would emit an error had the cast not be valid. 15 | */ 16 | class CastInjector : AbstractTransformerCompilerStep 17 | { 18 | public override void OnBinaryExpression(BinaryExpression node) 19 | { 20 | if (node.Operator == BinaryOperatorType.Assign && node.Left.ExpressionType != node.Right.ExpressionType && node.Left.ExpressionType != null && node.Right.ExpressionType != null) 21 | { 22 | if (NeedsCastWithPotentialDataLoss(node.Left.ExpressionType.Type, node.Right.ExpressionType.Type)) 23 | { 24 | node.Replace(node.Right, CodeBuilder.CreateCast(node.Left.ExpressionType.Type, node.Right)); 25 | } 26 | } 27 | else if (AstUtil.GetBinaryOperatorKind(node) == BinaryOperatorKind.Arithmetic && node.ExpressionType.ElementType == TypeSystemServices.ObjectType) 28 | { 29 | if (TypeSystemServices.IsNumber(node.Left.ExpressionType)) 30 | { 31 | node.ExpressionType = node.Left.ExpressionType; 32 | node.Replace(node.Right, CodeBuilder.CreateCast(node.ExpressionType, node.Right)); 33 | } 34 | else 35 | { 36 | node.ExpressionType = node.Right.ExpressionType; 37 | node.Replace(node.Left, CodeBuilder.CreateCast(node.ExpressionType, node.Left)); 38 | } 39 | } 40 | 41 | base.OnBinaryExpression(node); 42 | } 43 | 44 | public override void OnMethodInvocationExpression(MethodInvocationExpression node) 45 | { 46 | if (node.Target.Entity != null && node.Target.Entity.EntityType == EntityType.Method) 47 | { 48 | var method = (IMethodBase)node.Target.Entity; 49 | var parameters = method.GetParameters(); 50 | 51 | for (int i = 0; i < node.Arguments.Count; i++) 52 | { 53 | if (parameters[i].IsByRef || !NeedsCastWithPotentialDataLoss(parameters[i].Type, node.Arguments[i].ExpressionType)) 54 | continue; 55 | 56 | node.Arguments[i] = CodeBuilder.CreateCast(parameters[i].Type, node.Arguments[i]); 57 | } 58 | } 59 | 60 | base.OnMethodInvocationExpression(node); 61 | } 62 | 63 | private bool NeedsCastWithPotentialDataLoss(IType targetType, IType sourceType) 64 | { 65 | if (targetType == sourceType) 66 | return false; 67 | 68 | if (sourceType == TypeSystemServices.ObjectType) 69 | return true; 70 | 71 | if (targetType != TypeSystemServices.ObjectType && !sourceType.IsNull() && !targetType.IsAssignableFrom(sourceType)) 72 | { 73 | if (TypeSystemServices.IsNumber(targetType) && TypeSystemServices.IsNumber(sourceType)) 74 | return !IsWideningPromotion(targetType, sourceType); 75 | 76 | return TypeSystemServices.IsNumber(targetType) ^ TypeSystemServices.IsNumber(sourceType); 77 | } 78 | 79 | return false; 80 | } 81 | 82 | private bool IsWideningPromotion(IType targeType, IType sourceType) 83 | { 84 | var externalTargetType = targeType as ExternalType; 85 | if (null == externalTargetType) 86 | return false; 87 | 88 | var externalSourceType = sourceType as ExternalType; 89 | if (null == externalSourceType) 90 | return false; 91 | 92 | return NumericTypes.IsWideningPromotion(externalTargetType.ActualType, externalSourceType.ActualType); 93 | } 94 | } 95 | } -------------------------------------------------------------------------------- /UnityScript2CSharp/Steps/CtorFieldInitializationFix.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Text.RegularExpressions; 3 | using Boo.Lang.Compiler.Ast; 4 | using Boo.Lang.Compiler.Steps; 5 | 6 | namespace UnityScript2CSharp.Steps 7 | { 8 | // This step is reponsible for taking the AST generated for field initialization in ctors. 9 | // 10 | // for instance some field initialization code in US makes the compiler emit code similar to: 11 | // 12 | // if (class_initialization_var_gard) goto initizalized; 13 | // 14 | // // initialization of members.... 15 | // class_initialization_var_gard = true; 16 | // initialized: 17 | // 18 | // represented by the following AST nodes: 19 | // 20 | // [0] if self.$initialized__C$: goto ___initialized___ {} 21 | // [1] self.i1 = C(1) 22 | // [2] self.i2 = C(2) 23 | // [3] self.$initialized__C$ = true 24 | // [4] :___initialized___ 25 | // [5] C(42) 26 | // 27 | // This step changes this code to: 28 | // 29 | // if (!class_initialization_var_gard) 30 | // { 31 | // // initialization of members.... 32 | // class_initialization_var_gard = true; 33 | // } 34 | // 35 | class CtorFieldInitializationFix : AbstractTransformerCompilerStep 36 | { 37 | public override void OnIfStatement(IfStatement node) 38 | { 39 | var condition = node.Condition as MemberReferenceExpression; 40 | if (condition != null && IsClassInitializationGardVariable(condition.Name) && condition.Target.NodeType == NodeType.SelfLiteralExpression) 41 | { 42 | var initializedBranch = node.TrueBlock.Statements[0] as GotoStatement; 43 | if (initializedBranch != null) 44 | { 45 | // Invert the condition.... 46 | node.Replace(node.Condition, CodeBuilder.CreateNotExpression(node.Condition)); 47 | 48 | // Move initializatin statements inside if block 49 | var parentBlock = node.GetAncestor(); 50 | var nodeIndex = parentBlock.Statements.IndexOf(node); 51 | var targetLabelIndex = parentBlock.Statements.SingleOrDefault(s => s.NodeType == NodeType.LabelStatement && ((LabelStatement)s).Name == initializedBranch.Label.Name); 52 | 53 | node.TrueBlock.Statements.RemoveAt(0); 54 | for (int i = 1; parentBlock.Statements[nodeIndex + 1] != targetLabelIndex; i++) 55 | { 56 | node.TrueBlock.Statements.Add(parentBlock.Statements[nodeIndex + 1]); 57 | parentBlock.Statements.RemoveAt(nodeIndex + 1); 58 | } 59 | 60 | // Remove the label... 61 | parentBlock.Statements.RemoveAt(nodeIndex + 1); 62 | } 63 | } 64 | 65 | base.OnIfStatement(node); 66 | } 67 | 68 | public override void OnMemberReferenceExpression(MemberReferenceExpression node) 69 | { 70 | if (IsClassInitializationGardVariable(node.Name)) 71 | { 72 | //Fix references to initialization var 73 | node.Name = node.Name.Replace("$", string.Empty); 74 | } 75 | 76 | base.OnMemberReferenceExpression(node); 77 | } 78 | 79 | public override void OnField(Field node) 80 | { 81 | if (IsClassInitializationGardVariable(node.Name)) 82 | { 83 | node.Name = node.Name.Replace("$", string.Empty); 84 | } 85 | base.OnField(node); 86 | } 87 | 88 | private bool IsClassInitializationGardVariable(string varName) 89 | { 90 | return Regex.IsMatch(varName, @"^\$initialized__([^\$]+)\$$"); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /UnityScript2CSharp/Steps/ExpandAssignmentToValueTypeMembers.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Boo.Lang.Compiler.Ast; 4 | using Boo.Lang.Compiler.Steps; 5 | using Boo.Lang.Compiler.TypeSystem; 6 | 7 | namespace UnityScript2CSharp.Steps 8 | { 9 | // This step is responsible for expanding "Eval" macros introduced by the US compiler 10 | // to handle assignment to fields of value types like in the example below: 11 | // 12 | // a.b.c = 1; 13 | // 14 | // if *b* type is a ValueType this code is invalid in C#, but 15 | // US compiler happly generates a "macro evaluation" like: 16 | // 17 | // $1 = 1; 18 | // $2 = a.b; 19 | // $2.c = $1; 20 | // a.b = $2; 21 | // 22 | // This "macro" is represented as an invocation of a "builtin" function called "Eval" 23 | // which takes the above lines as arguments 24 | class ExpandAssignmentToValueTypeMembers : AbstractTransformerCompilerStep 25 | { 26 | public override void OnMethodInvocationExpression(MethodInvocationExpression node) 27 | { 28 | if (_owner != null || node.Target.Entity != BuiltinFunction.Eval) 29 | return; 30 | 31 | var statements = ExpandAssignmentToValueTypeMember(node); 32 | var newBloc = new Block(statements); 33 | 34 | var toBeReplaced = node.GetAncestor(); 35 | toBeReplaced.ParentNode.Replace(toBeReplaced, newBloc); 36 | } 37 | 38 | public override void OnBinaryExpression(BinaryExpression node) 39 | { 40 | if (node.ParentNode != _owner) 41 | return; 42 | 43 | switch (node.Left.NodeType) 44 | { 45 | case NodeType.ReferenceExpression: 46 | var declaration = new Declaration(VariableNameFor((ReferenceExpression)node.Left), CodeBuilder.CreateTypeReference(node.ExpressionType)); 47 | _statements.Add(new DeclarationStatement(declaration, node.Right)); 48 | node.Right.Accept(ReferenceNameFix.Instance); 49 | break; 50 | 51 | case NodeType.MemberReferenceExpression: 52 | _statements.Add(new ExpressionStatement(node)); 53 | node.Accept(ReferenceNameFix.Instance); 54 | break; 55 | } 56 | } 57 | 58 | private Statement[] ExpandAssignmentToValueTypeMember(MethodInvocationExpression node) 59 | { 60 | _owner = node; 61 | _processed.Clear(); 62 | _statements.Clear(); 63 | foreach (var argument in node.Arguments) 64 | { 65 | argument.Accept(this); 66 | } 67 | _owner = null; 68 | 69 | return _statements.ToArray(); 70 | } 71 | 72 | internal static string VariableNameFor(ReferenceExpression node) 73 | { 74 | string name; 75 | if (!_processed.TryGetValue(node.Name, out name)) 76 | { 77 | name = node.Name.Replace("$", "_"); 78 | _processed[node.Name] = name; 79 | } 80 | 81 | return name; 82 | } 83 | 84 | private ISet _statements = new HashSet(); 85 | private static IDictionary _processed = new Dictionary(); 86 | 87 | private Expression _owner; 88 | } 89 | 90 | internal class ReferenceNameFix : FastDepthFirstVisitor 91 | { 92 | internal static ReferenceNameFix Instance = new ReferenceNameFix(); 93 | 94 | public override void OnReferenceExpression(ReferenceExpression node) 95 | { 96 | if (node.Name.StartsWith("$")) 97 | node.Name = ExpandAssignmentToValueTypeMembers.VariableNameFor(node); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /UnityScript2CSharp/Steps/ExpandValueTypeObjectInitialization.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Boo.Lang.Compiler.Ast; 3 | using Boo.Lang.Compiler.Steps; 4 | using Boo.Lang.Compiler.TypeSystem; 5 | 6 | namespace UnityScript2CSharp.Steps 7 | { 8 | // This only applies for default ctor of value types 9 | // 10 | // Expressions like "new VT()" (i.e, "invocation of default ctor of a value type") are converted 11 | // to an eval() that initializes the value type if the value type in question does not have a default ctor (which is the case 12 | // if it has any other ctor). This is done because boo/us does not have a node to represent default(T) 13 | class ExpandValueTypeObjectInitialization : AbstractTransformerCompilerStep 14 | { 15 | public override void OnMethodInvocationExpression(MethodInvocationExpression node) 16 | { 17 | if (node.Arguments.Count > 0 && node.Arguments[0].NodeType == NodeType.MethodInvocationExpression) 18 | { 19 | var evalTargetInvocation = (MethodInvocationExpression)node.Arguments[0]; 20 | if (evalTargetInvocation.Target.Entity == BuiltinFunction.InitValueType) 21 | { 22 | var typeBeingInitialized = ((ITypedEntity)evalTargetInvocation.Arguments[0].Entity).Type; 23 | var found = typeBeingInitialized.GetConstructors().FirstOrDefault(); 24 | if (found != null) // Use any available ctor reference. 25 | { 26 | ReplaceCurrentNode(CodeBuilder.CreateConstructorInvocation(LexicalInfo.Empty, found, evalTargetInvocation.Arguments.Skip(1).ToArray())); 27 | } 28 | else 29 | { 30 | var replacement = new ReferenceExpression(typeBeingInitialized.Name) { Entity = typeBeingInitialized, ExpressionType = typeBeingInitialized.Type }; 31 | replacement.Annotate("VALUE_TYPE_INITIALIZATON_MARKER"); 32 | ReplaceCurrentNode(replacement); 33 | } 34 | } 35 | } 36 | base.OnMethodInvocationExpression(node); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /UnityScript2CSharp/Steps/FixClosures.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Boo.Lang.Compiler.Ast; 4 | using Boo.Lang.Compiler.Steps; 5 | using Boo.Lang.Compiler.TypeSystem.Internal; 6 | using UnityScript2CSharp.Extensions; 7 | using BinaryExpression = Boo.Lang.Compiler.Ast.BinaryExpression; 8 | using BlockExpression = Boo.Lang.Compiler.Ast.BlockExpression; 9 | 10 | namespace UnityScript2CSharp.Steps 11 | { 12 | /* 13 | * This step is in charge of fixing the AST related to closures handling by: 14 | * 15 | * 1. Remove the synthetic closure type (no need to do that again, C# compiler will take care) 16 | * 2. Remove the initialization of a new instance of the emited closure type 17 | * 3. Remove "artificial" local variable declaration 18 | * 4. Remove "artificial" local variable declaration initialization 19 | * 5. Replace captured local variables with the original ones (e.g: $locals.$callback --> callback) 20 | * 6. Introduce locals since US compiler convert captured locals to members of the closure. 21 | * 22 | { 23 | // ---+ +--- Binary Expression 24 | // (3) | | 25 | // v v 26 | var $locals = new function_forum.$Adapt$locals$2(); 27 | 28 | $locals.$callback = callback; // <-- Captured variables 29 | $locals.$value = value; 30 | } 31 | 32 | $locals.$l1 = 42; 33 | $locals.$l1 = $locals.$l1 + 1; 34 | 35 | return () => 36 | { 37 | $locals.$callback($locals.$value + $locals.$l1); 38 | } 39 | 40 | // 1 : Closure Type 41 | [System.Serializable] 42 | internal class $Adapt$locals$2 : object 43 | { 44 | internal Func $callback; 45 | internal int $value; 46 | internal int $l1; 47 | } 48 | */ 49 | 50 | public class FixClosures : AbstractTransformerCompilerStep 51 | { 52 | public override void OnClassDefinition(ClassDefinition node) 53 | { 54 | if (node.IsSynthetic && node.Name.StartsWith("$")) // Synthetic closure class 55 | node.DeclaringType.Members.Remove(node); // Step 1: remove it. 56 | else 57 | base.OnClassDefinition(node); 58 | } 59 | 60 | public override void OnBinaryExpression(BinaryExpression node) 61 | { 62 | if (node.IsDeclarationStatement()) 63 | { 64 | var localDeclaration = (InternalLocal) node.Left.Entity; 65 | if (localDeclaration.OriginalDeclaration == null) 66 | { 67 | var closureBlock = node.GetAncestor(); 68 | if (closureBlock != null && closureBlock == node.ParentNode.ParentNode) 69 | { 70 | closureBlock.Statements.Remove((ExpressionStatement) node.ParentNode); // Step 4: removes the local initialization 71 | 72 | var capturedVariables = CollectCapturedParameters(closureBlock); 73 | 74 | IntroduceLocalVariablesForCapturedLocals(closureBlock, capturedVariables); 75 | 76 | FixCapturedVariableReferences(closureBlock, capturedVariables); 77 | 78 | RemoveLocalVariableDeclaration(closureBlock, localDeclaration); 79 | 80 | RemoveClosureInstanceInitialization(closureBlock); 81 | } 82 | } 83 | return; 84 | } 85 | 86 | base.OnBinaryExpression(node); 87 | } 88 | 89 | private static void RemoveClosureInstanceInitialization(Block closureBlock) 90 | { 91 | var parentBlock = closureBlock.GetAncestor(); 92 | parentBlock.Statements.Remove(closureBlock); // Step 2: Remove the closure type initialization 93 | } 94 | 95 | private static void RemoveLocalVariableDeclaration(Block closureBlock, InternalLocal localDeclaration) 96 | { 97 | var declaringMethod = closureBlock.GetRootAncestor(); 98 | declaringMethod.Locals.Remove(localDeclaration.Local); // Step 3: Removes the local declaration 99 | } 100 | 101 | private void IntroduceLocalVariablesForCapturedLocals(Block closureBlock, Dictionary capturedVariables) 102 | { 103 | var method = closureBlock.GetAncestor(); 104 | var collector = new ReferencedCapturedLocalVariablesCollector(capturedVariables); 105 | 106 | method.Accept(collector); 107 | 108 | var candidates = method.Body.Statements.WithExpressionStatementOfType().Where(exp => exp.Operator == BinaryOperatorType.Assign).ToList(); 109 | 110 | foreach (var referencedLocalVariable in collector.ReferencedCapturedLocalVariables) 111 | { 112 | var initializationExpression = candidates.FirstOrDefault(exp => exp.Left.Matches(referencedLocalVariable)); 113 | if (initializationExpression == null) 114 | continue; 115 | 116 | var local = CreateLocalVariableDeclarationFor(referencedLocalVariable, initializationExpression); 117 | 118 | initializationExpression.Replace(initializationExpression.Left, local); 119 | capturedVariables.Add(referencedLocalVariable.ToString(), local); 120 | } 121 | } 122 | 123 | private ReferenceExpression CreateLocalVariableDeclarationFor(MemberReferenceExpression mre, BinaryExpression initializationExpression) 124 | { 125 | var localName = mre.Name.Replace("$", ""); 126 | var local = new ReferenceExpression(localName) 127 | { 128 | ExpressionType = initializationExpression.Right.ExpressionType, 129 | Entity = new InternalLocal(new Local(localName, true), initializationExpression.Right.ExpressionType) 130 | { 131 | OriginalDeclaration = new Declaration(localName, CodeBuilder.CreateTypeReference(initializationExpression.Right.ExpressionType)), 132 | } 133 | }; 134 | 135 | return local; 136 | } 137 | 138 | private void FixCapturedVariableReferences(Block closureBlock, Dictionary capturedVariables) 139 | { 140 | closureBlock.Statements.Clear(); 141 | var method = closureBlock.GetRootAncestor(); 142 | method.Accept(new CapturedVariableReferencesFixer(capturedVariables)); 143 | } 144 | 145 | private static Dictionary CollectCapturedParameters(Block closureBlock) 146 | { 147 | var capturedVariables = new Dictionary(); 148 | 149 | var candidateCaptures = closureBlock.Statements.OfType() 150 | .Where(stmt => stmt.Expression.NodeType == NodeType.BinaryExpression) 151 | .Select(stmt => (BinaryExpression) stmt.Expression); 152 | 153 | foreach (var capture in candidateCaptures) 154 | { 155 | if (capture.Left.NodeType == NodeType.MemberReferenceExpression && capture.Right.NodeType == NodeType.ReferenceExpression) 156 | capturedVariables[capture.Left.ToString()] = (ReferenceExpression) capture.Right; 157 | } 158 | 159 | return capturedVariables; 160 | } 161 | } 162 | 163 | internal class ReferencedCapturedLocalVariablesCollector : DepthFirstVisitor 164 | { 165 | private readonly ISet _referencedCapturedLocalVariables = new HashSet(); 166 | private readonly Dictionary _capturedParameters; 167 | private bool _insideExpressionBlock; 168 | 169 | public ReferencedCapturedLocalVariablesCollector(Dictionary capturedParameters) 170 | { 171 | _capturedParameters = capturedParameters; 172 | } 173 | 174 | public override void OnBlockExpression(BlockExpression node) 175 | { 176 | _insideExpressionBlock = true; 177 | base.OnBlockExpression(node); 178 | _insideExpressionBlock = false; 179 | } 180 | 181 | public override void OnMemberReferenceExpression(MemberReferenceExpression node) 182 | { 183 | base.OnMemberReferenceExpression(node); 184 | 185 | if (!_insideExpressionBlock) 186 | return; 187 | 188 | if (node.Name.StartsWith("$") && node.Target.ToString().StartsWith("$")) 189 | { 190 | if (!_capturedParameters.ContainsKey(node.ToString())) 191 | _referencedCapturedLocalVariables.Add(node); 192 | } 193 | } 194 | 195 | public ISet ReferencedCapturedLocalVariables => _referencedCapturedLocalVariables; 196 | } 197 | 198 | internal class CapturedVariableReferencesFixer : DepthFirstTransformer 199 | { 200 | private readonly Dictionary _capturedVariables; 201 | 202 | public CapturedVariableReferencesFixer(Dictionary capturedVariables) 203 | { 204 | _capturedVariables = capturedVariables; 205 | } 206 | 207 | public override void OnMemberReferenceExpression(MemberReferenceExpression node) 208 | { 209 | if (_capturedVariables.TryGetValue(node.ToString(), out var capturedValue)) 210 | { 211 | node.ParentNode.Replace(node, capturedValue); 212 | } 213 | } 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /UnityScript2CSharp/Steps/FixEnumReferences.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Boo.Lang.Compiler.Ast; 3 | using Boo.Lang.Compiler.Steps; 4 | using Boo.Lang.Compiler.TypeSystem; 5 | 6 | namespace UnityScript2CSharp.Steps 7 | { 8 | // 9 | // In UnityScript, given an enum (lets say MyEnum) the following code: 10 | // 11 | // MyEnum.GetValues(MyEnum) 12 | // 13 | // is equivalent to: 14 | // Enum.GetValues(typeof(MyEnum)) 15 | // 16 | class FixEnumReferences : AbstractTransformerCompilerStep 17 | { 18 | public override void OnMemberReferenceExpression(MemberReferenceExpression node) 19 | { 20 | if (!ReplaceEnumTypeWithSystemEnumReference(node)) 21 | { 22 | base.OnMemberReferenceExpression(node); 23 | } 24 | } 25 | 26 | public override void OnReferenceExpression(ReferenceExpression node) 27 | { 28 | ReplaceEnumTypeWithSystemEnumReference(node); 29 | } 30 | 31 | private bool ReplaceEnumTypeWithSystemEnumReference(ReferenceExpression node) 32 | { 33 | var candidate = node.Entity as IType; 34 | if (candidate == null || !candidate.IsEnum || node.ParentNode.NodeType != NodeType.MemberReferenceExpression) 35 | return false; 36 | 37 | if (node.ParentNode.Entity.EntityType != EntityType.Method) // We don't want to replace fully qualified enum members (only Enum methods) 38 | return false; 39 | 40 | ReplaceCurrentNode(new ReferenceExpression(typeof(Enum).FullName) {Entity = TypeSystemServices.EnumType}); 41 | return true; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /UnityScript2CSharp/Steps/FixFunctionReferences.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | using Boo.Lang.Compiler.Ast; 5 | using Boo.Lang.Compiler.Steps; 6 | using Boo.Lang.Compiler.TypeSystem; 7 | using Boo.Lang.Compiler.TypeSystem.Generics; 8 | using Boo.Lang.Compiler.TypeSystem.Internal; 9 | using UnityScript2CSharp.Extensions; 10 | 11 | namespace UnityScript2CSharp.Steps 12 | { 13 | /* 14 | * This step replaces: 15 | * 16 | * 1) references to Boo.Lang.ICallable interface with "System.Delegate" (parameters, fields, locals) (injecting casts to Action / Func as needed) 17 | * 2) Boo.Lang.ICallable.Call() method with "System.Delegate.DynamicInvoke()" 18 | * 3) UnityScript.Lang.UnityBuiltins.parseInt/parseFloat => Int32/Single.Parse() 19 | */ 20 | class FixFunctionReferences : AbstractTransformerCompilerStep 21 | { 22 | public override void OnField(Field node) 23 | { 24 | node.Type = FixTypeToDelegateIfNeeded(node.Type); 25 | } 26 | 27 | public override void OnParameterDeclaration(ParameterDeclaration node) 28 | { 29 | node.Type = FixTypeToDelegateIfNeeded(node.Type); 30 | } 31 | 32 | public override void OnDeclaration(Declaration node) 33 | { 34 | node.Type = FixTypeToDelegateIfNeeded(node.Type); 35 | } 36 | 37 | public override void OnBinaryExpression(BinaryExpression node) 38 | { 39 | if (node.IsDeclarationStatement()) 40 | { 41 | var localDeclaration = (InternalLocal) node.Left.Entity; 42 | var declaration = localDeclaration.OriginalDeclaration; 43 | 44 | if (declaration == null || declaration.Type == null || declaration.Type.Entity != TypeSystemServices.ICallableType) 45 | return; 46 | 47 | declaration.Type = FixTypeToDelegateIfNeeded(declaration.Type); 48 | 49 | node.Replace(node.Right, InjectCast(node.Right)); 50 | } 51 | else if (node.Operator == BinaryOperatorType.Assign && node.Left?.ExpressionType?.FullName == "Function" && TypeSystemServices.IsCallable(node.Right.ExpressionType)) 52 | { 53 | node.Replace(node.Right, InjectCast(node.Right)); 54 | } 55 | } 56 | 57 | public override void OnMethodInvocationExpression(MethodInvocationExpression node) 58 | { 59 | if (node.Target.Entity != null && node.Target.Entity.EntityType == EntityType.Method && node.Target.Entity.Name == "parseInt") 60 | { 61 | HandleUnityScriptLangUtilsParseXInvocations(node, TypeSystemServices.IntType, new ReferenceExpression(typeof(int).FullName)); 62 | } 63 | else if (node.Target.Entity != null && node.Target.Entity.EntityType == EntityType.Method && node.Target.Entity.Name == "parseFloat") 64 | { 65 | HandleUnityScriptLangUtilsParseXInvocations(node, TypeSystemServices.SingleType, new ReferenceExpression(typeof(float).FullName)); 66 | } 67 | 68 | if (node.Target.Entity != null && node.Target.Entity == TypeSystemServices.ICallableType.GetMembers().Single(m => m.Name == "Call")) 69 | { 70 | var mreOriginal = (MemberReferenceExpression) node.Target; 71 | var newMethod = TypeSystemServices.Map(typeof(Delegate).GetMethod("DynamicInvoke", BindingFlags.Instance | BindingFlags.Public)); 72 | var newTarget = new MemberReferenceExpression(mreOriginal.Target, newMethod.Name); 73 | newTarget.Entity = newMethod; 74 | 75 | node.Replace(node.Target, newTarget); 76 | } 77 | else if (node.Target.Entity != null && node.Target.Entity.EntityType == EntityType.Method) 78 | { 79 | var parameters = ((IMethodBase) node.Target.Entity).GetParameters(); 80 | var args = node.Arguments.ToArray(); 81 | 82 | for (int i = 0; i < args.Length && i < parameters.Length; i++) 83 | { 84 | if (args[i].ExpressionType != null && TypeSystemServices.IsCallable(args[i].ExpressionType) && !TypeSystemServices.IsCallable(parameters[i].Type)) 85 | node.Replace(args[i], InjectCast(args[i])); 86 | } 87 | } 88 | 89 | base.OnMethodInvocationExpression(node); 90 | } 91 | 92 | private void HandleUnityScriptLangUtilsParseXInvocations(MethodInvocationExpression node, IType targetType, ReferenceExpression targetTypeAsExpression) 93 | { 94 | if (node.Arguments[0].ExpressionType == TypeSystemServices.StringType) 95 | { 96 | var parseMethod = targetType.GetMembers().OfType().Single(m => m.Name == "Parse" && m.GetParameters().Length == 1); 97 | node.Replace(node.Target, CodeBuilder.CreateMemberReference(targetTypeAsExpression, parseMethod)); 98 | } 99 | else if (node.Arguments[0].ExpressionType != targetType) 100 | { 101 | Console.WriteLine($"Warning: call to {node} ({node.LexicalInfo}) should be translated to `checked( ({targetType.Name}) {node.Arguments[0]});` but the converter does not support `checked expressions`. Consider adding it manually to the generated code. "); 102 | ReplaceCurrentNode(CodeBuilder.CreateCast(targetType, node.Arguments[0])); 103 | } 104 | else 105 | { 106 | ReplaceCurrentNode(node.Arguments[0]); 107 | } 108 | } 109 | 110 | private Expression InjectCast(Expression exp) 111 | { 112 | var method = exp.Entity as IMethod; 113 | if (method == null) 114 | return exp; 115 | 116 | IType castTargetType; 117 | var parameters = method.GetParameters(); 118 | if (parameters.Length > 0 || (method.ReturnType != TypeSystemServices.VoidType && method.ReturnType != null)) 119 | { 120 | var isFunc = method.ReturnType != null && method.ReturnType != TypeSystemServices.VoidType; 121 | var genericTypeParameters = parameters.Select(p => p.Type).ToList(); 122 | if (isFunc) 123 | genericTypeParameters.Add(method.ReturnType); 124 | 125 | castTargetType = new GenericConstructedType(TypeSystemServices.Map(isFunc ? typeof(Func<>) : typeof(Action<>)), genericTypeParameters.ToArray()); 126 | } 127 | else 128 | castTargetType = TypeSystemServices.Map(typeof(Action)); 129 | 130 | return CodeBuilder.CreateCast(castTargetType, exp); 131 | } 132 | 133 | private TypeReference FixTypeToDelegateIfNeeded(TypeReference originalType) 134 | { 135 | if (originalType.Entity != TypeSystemServices.ICallableType) 136 | return originalType; 137 | 138 | var newType = TypeReference.Lift(typeof(Delegate)); 139 | newType.Entity = TypeSystemServices.DelegateType; 140 | 141 | return newType; 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /UnityScript2CSharp/Steps/FixSwitchBreaks.cs: -------------------------------------------------------------------------------- 1 | using Boo.Lang.Compiler.Ast; 2 | using Boo.Lang.Compiler.Steps; 3 | using UnityScript2CSharp.Extensions; 4 | 5 | namespace UnityScript2CSharp.Steps 6 | { 7 | /* 8 | * US compiler emits *breaks* as gotos to a label that follows the last switch statement. 9 | */ 10 | class FixSwitchBreaks : AbstractTransformerCompilerStep 11 | { 12 | private LabelStatement lastLabel; 13 | 14 | public override void OnBlock(Block node) 15 | { 16 | try 17 | { 18 | LabelStatement switchEnd; 19 | BinaryExpression conditionVarInitialization; 20 | IfStatement firstSwitchCheckStatement; 21 | if (!node.TryExtractSwitchStatementDetails(out conditionVarInitialization, out firstSwitchCheckStatement, out switchEnd)) 22 | return; 23 | 24 | lastLabel = (LabelStatement) node.LastStatement; 25 | } 26 | finally 27 | { 28 | base.OnBlock(node); 29 | } 30 | } 31 | 32 | public override void OnGotoStatement(GotoStatement node) 33 | { 34 | if (node.Label.Name == lastLabel?.Name) 35 | { 36 | var breakStatement = new BreakStatement (); 37 | breakStatement.Annotate("BREAK"); 38 | node.ParentNode.Replace(node, breakStatement); 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /UnityScript2CSharp/Steps/FixSwitchWithOnlyDefault.cs: -------------------------------------------------------------------------------- 1 | using Boo.Lang.Compiler.Ast; 2 | using Boo.Lang.Compiler.Steps; 3 | using Boo.Lang.Compiler.TypeSystem.Internal; 4 | 5 | namespace UnityScript2CSharp.Steps 6 | { 7 | /* 8 | * switches with only the default value generates something like: 9 | * 10 | * { 11 | * $switch$1 = switch_condition; 12 | * } 13 | * 14 | * For example, the following US code: 15 | * switch(Foo()) 16 | * { 17 | * default: 18 | * return 1; 19 | * } 20 | * 21 | * generates the following AST snipet: 22 | * 23 | * { // a block 24 | * $switch$1 = Foo(); 25 | * return 1; 26 | * } 27 | * 28 | * This step simply changes the ReferenceExpression ($switch$1) so the conversion code will handle this as an "automatic variable declaration" 29 | * 30 | */ 31 | class FixSwitchWithOnlyDefault : AbstractTransformerCompilerStep 32 | { 33 | public override void OnBlock(Block node) 34 | { 35 | try 36 | { 37 | if (node.Statements.Count == 0 || node.Statements[0].NodeType != NodeType.Block) 38 | return; 39 | 40 | var innerBlock = (Block)node.Statements[0]; 41 | ExpressionStatement firstStatement; 42 | if (!IsSwitchStatementWithOnlyDefault(innerBlock, out firstStatement)) 43 | return; 44 | 45 | var binaryExp = (BinaryExpression)firstStatement.Expression; 46 | if (binaryExp.Operator != BinaryOperatorType.Assign || binaryExp.Left.NodeType != NodeType.ReferenceExpression || !binaryExp.Left.ToCodeString().Contains("$switch$")) 47 | return; 48 | 49 | var originalLocal = ((InternalLocal) binaryExp.Left.Entity).Local; 50 | var varName = originalLocal.Name.Replace("$", "_"); 51 | var local = new Local(varName, true); 52 | var internalLocal = new InternalLocal(local, binaryExp.ExpressionType); 53 | local.Entity = internalLocal; 54 | 55 | internalLocal.OriginalDeclaration = new Declaration(varName, CodeBuilder.CreateTypeReference(internalLocal.Type)); 56 | 57 | // we need a DeclarationStatment as the parent of the "OriginalDeclaration" 58 | var ds = new DeclarationStatement(internalLocal.OriginalDeclaration, binaryExp.Right); 59 | 60 | innerBlock.Statements.RemoveAt(0); 61 | 62 | var parentMethod = node.GetAncestor(); 63 | parentMethod.Locals.Replace(originalLocal, internalLocal.Local); 64 | } 65 | finally 66 | { 67 | base.OnBlock(node); 68 | } 69 | } 70 | 71 | private bool IsSwitchStatementWithOnlyDefault(Block candidate, out ExpressionStatement firstStatement) 72 | { 73 | firstStatement = candidate.Statements[0] as ExpressionStatement; 74 | if (candidate.Statements.Count < 2) 75 | return false; 76 | 77 | if (firstStatement == null || firstStatement.Expression.NodeType != NodeType.BinaryExpression) 78 | return false; 79 | 80 | var firstSwitchCondition = candidate.Statements[1] as IfStatement; 81 | return firstSwitchCondition == null || firstSwitchCondition.Condition.NodeType != NodeType.BinaryExpression || !firstSwitchCondition.Condition.ToCodeString().Contains("$switch$"); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /UnityScript2CSharp/Steps/FixTypeAccessibility.cs: -------------------------------------------------------------------------------- 1 | using Boo.Lang.Compiler.Ast; 2 | using Boo.Lang.Compiler.Steps; 3 | 4 | namespace UnityScript2CSharp.Steps 5 | { 6 | class FixTypeAccessibility : AbstractFastVisitorCompilerStep 7 | { 8 | public override void OnClassDefinition(ClassDefinition node) 9 | { 10 | FixTypeAccessibilityOf(node); 11 | base.OnClassDefinition(node); 12 | } 13 | 14 | public override void OnInterfaceDefinition(InterfaceDefinition node) 15 | { 16 | FixTypeAccessibilityOf(node); 17 | base.OnInterfaceDefinition(node); 18 | } 19 | 20 | public override void OnEnumDefinition(EnumDefinition node) 21 | { 22 | FixTypeAccessibilityOf(node); 23 | base.OnEnumDefinition(node); 24 | } 25 | 26 | private void FixTypeAccessibilityOf(TypeDefinition node) 27 | { 28 | if (node.IsNested) 29 | return; 30 | 31 | if ( (node.Modifiers & TypeMemberModifiers.Private) == TypeMemberModifiers.Private) 32 | { 33 | node.Modifiers = node.Modifiers & ~TypeMemberModifiers.Private; 34 | node.Modifiers |= TypeMemberModifiers.Internal; 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /UnityScript2CSharp/Steps/InferredMethodReturnTypeFix.cs: -------------------------------------------------------------------------------- 1 | using Boo.Lang.Compiler.Ast; 2 | using Boo.Lang.Compiler.Steps; 3 | using Boo.Lang.Compiler.TypeSystem.Internal; 4 | 5 | namespace UnityScript2CSharp.Steps 6 | { 7 | class InferredMethodReturnTypeFix : AbstractTransformerCompilerStep 8 | { 9 | public override void OnYieldStatement(YieldStatement node) 10 | { 11 | base.OnYieldStatement(node); 12 | TryInferMethodReturnType(node); 13 | } 14 | 15 | private void TryInferMethodReturnType(YieldStatement node) 16 | { 17 | var methodNode = node.GetAncestor(); 18 | var method = methodNode.Entity as InternalMethod; 19 | if (method == null || (method.ReturnType != TypeSystemServices.VoidType && method.ReturnType != null)) 20 | return; 21 | 22 | methodNode.ReturnType = new SimpleTypeReference(TypeSystemServices.IEnumeratorType.Name); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /UnityScript2CSharp/Steps/InjectTypeOfExpressionsInArgumentsOfSystemType.cs: -------------------------------------------------------------------------------- 1 | using Boo.Lang.Compiler.Ast; 2 | using Boo.Lang.Compiler.Steps; 3 | using Boo.Lang.Compiler.TypeSystem; 4 | 5 | namespace UnityScript2CSharp.Steps 6 | { 7 | internal class InjectTypeOfExpressionsInArgumentsOfSystemType : AbstractTransformerCompilerStep 8 | { 9 | private Expression _currentArgument; 10 | 11 | public override void OnMethodInvocationExpression(MethodInvocationExpression node) 12 | { 13 | if (node.Target.Entity == null || (node.Target.Entity.EntityType != EntityType.Method && node.Target.Entity.EntityType != EntityType.Constructor)) 14 | return; 15 | 16 | for (int i = 0; i < node.Arguments.Count; i++) 17 | { 18 | _currentArgument = node.Arguments[i]; 19 | if (_currentArgument.ExpressionType.ElementType != TypeSystemServices.TypeType) 20 | continue; 21 | 22 | _currentArgument.Accept(this); 23 | } 24 | 25 | _currentArgument = null; 26 | base.OnMethodInvocationExpression(node); 27 | } 28 | 29 | public override void OnMemberReferenceExpression(MemberReferenceExpression node) 30 | { 31 | if (HasImplictTypeOfExpression(node)) 32 | { 33 | node.ParentNode.Replace(node, CodeBuilder.CreateTypeofExpression( TypeSystemServices.GetType(node))); 34 | return; 35 | } 36 | 37 | base.OnMemberReferenceExpression(node); 38 | } 39 | 40 | public override void OnReferenceExpression(ReferenceExpression node) 41 | { 42 | if (!HasImplictTypeOfExpression(node)) 43 | return; 44 | 45 | node.ParentNode.Replace(node, CodeBuilder.CreateTypeofExpression(TypeSystemServices.GetType(node))); 46 | } 47 | 48 | public override void OnArrayLiteralExpression(ArrayLiteralExpression node) 49 | { 50 | if (node.Type == null) 51 | { 52 | node.Type = new ArrayTypeReference(new SimpleTypeReference(node.ExpressionType.ElementType.FullName)); 53 | node.Type.ElementType.Entity = node.ExpressionType.ElementType; 54 | } 55 | 56 | base.OnArrayLiteralExpression(node); 57 | } 58 | 59 | public override void OnBinaryExpression(BinaryExpression node) 60 | { 61 | if (node.Operator == BinaryOperatorType.Assign && node.Left.ExpressionType == TypeSystemServices.TypeType && node.Right.ExpressionType.EntityType == EntityType.Type && (node.Right.Entity != null && node.Right.Entity.EntityType == EntityType.Type)) 62 | { 63 | node.Replace(node.Right, CodeBuilder.CreateTypeofExpression(TypeSystemServices.GetType(node.Right))); 64 | } 65 | else if (node.Operator == BinaryOperatorType.TypeTest) 66 | { 67 | var typeofExpression = (TypeofExpression) node.Right; 68 | node.Replace(node.Right, CodeBuilder.CreateReference(typeofExpression.Type.Entity)); 69 | } 70 | 71 | base.OnBinaryExpression(node); 72 | } 73 | 74 | private bool HasImplictTypeOfExpression(ReferenceExpression node) 75 | { 76 | if (_currentArgument != null) 77 | { 78 | if (node.Entity.EntityType != EntityType.Type) 79 | return false; 80 | 81 | return node.ParentNode.NodeType == NodeType.MethodInvocationExpression || node.ParentNode.NodeType == NodeType.ArrayLiteralExpression; 82 | } 83 | 84 | if (node.ParentNode.NodeType == NodeType.YieldStatement) 85 | return node.Entity.EntityType == EntityType.Type; 86 | 87 | return node.ParentNode.NodeType == NodeType.Attribute; 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /UnityScript2CSharp/Steps/InstanceToTypeReferencedStaticMemberReference.cs: -------------------------------------------------------------------------------- 1 | using Boo.Lang.Compiler.Ast; 2 | using Boo.Lang.Compiler.Steps; 3 | using Boo.Lang.Compiler.TypeSystem; 4 | using UnityScript2CSharp.Extensions; 5 | 6 | namespace UnityScript2CSharp.Steps 7 | { 8 | class InstanceToTypeReferencedStaticMemberReference : AbstractTransformerCompilerStep 9 | { 10 | public override void OnMemberReferenceExpression(MemberReferenceExpression node) 11 | { 12 | if (IsStaticReferenceThroughInstance(node)) 13 | { 14 | var declaringType = node.Target.ExpressionType; 15 | var needsQualification = node.NeedsQualificationFor(declaringType.ParentNamespace); 16 | 17 | node.Replace(node.Target, new ReferenceExpression(needsQualification ? declaringType.FullName : declaringType.Name)); 18 | } 19 | 20 | base.OnMemberReferenceExpression(node); 21 | } 22 | 23 | private static bool IsStaticReferenceThroughInstance(MemberReferenceExpression node) 24 | { 25 | IMember member = node.Entity as IMember; 26 | if (member == null || !member.IsStatic) 27 | return false; 28 | 29 | // Ignore 'length' property of UnityScript.Lang.Extensions. 30 | // It is (incorrecly) marked as static but we will not emit code to reference this type anymore. 31 | if (member.Name == "length" && member.DeclaringType.FullName == "UnityScript.Lang.Extensions") 32 | return false; 33 | 34 | if (node.Target.Entity != null) 35 | return node.Target.Entity.EntityType != EntityType.Type; 36 | 37 | // if we don't have an entity we assume any non ReferenceExpression represents an instance member 38 | return !(node.Target is ReferenceExpression); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /UnityScript2CSharp/Steps/MergeMainMethodStatementsIntoStartMethod.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Boo.Lang.Compiler.Ast; 3 | using Boo.Lang.Compiler.Steps; 4 | 5 | namespace UnityScript2CSharp.Steps 6 | { 7 | class MergeMainMethodStatementsIntoStartMethod : AbstractTransformerCompilerStep 8 | { 9 | public override void OnMethod(Method node) 10 | { 11 | if (node.Name != "Main" || node.Parameters.Count != 0 || node.Body.IsEmpty) 12 | return; 13 | 14 | MoveMainStatementsToSTartMethodOf(node); 15 | } 16 | 17 | public void MoveMainStatementsToSTartMethodOf(Method mainMethod) 18 | { 19 | var script = mainMethod.DeclaringType; 20 | var startMethod = script.Members.OfType().FirstOrDefault(IsStartMethod); 21 | if (startMethod == null) 22 | { 23 | startMethod = mainMethod; 24 | startMethod.Name = "Start"; 25 | return; 26 | } 27 | 28 | foreach (var statement in mainMethod.Body.Statements.Reverse()) 29 | { 30 | startMethod.Body.Statements.Insert(0, statement); 31 | } 32 | script.Members.Remove(mainMethod); 33 | } 34 | 35 | private bool IsStartMethod(Method candidate) 36 | { 37 | return candidate.Name == "Start" && candidate.Parameters.Count == 0; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /UnityScript2CSharp/Steps/OperatorMethodToLanguageOperator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Boo.Lang.Compiler; 3 | using Boo.Lang.Compiler.Ast; 4 | using Boo.Lang.Compiler.Steps; 5 | using Boo.Lang.Compiler.TypeSystem; 6 | using Boo.Lang.Compiler.TypeSystem.Services; 7 | using Boo.Lang.Environments; 8 | 9 | namespace UnityScript2CSharp.Steps 10 | { 11 | class OperatorMethodToLanguageOperator : AbstractTransformerCompilerStep 12 | { 13 | public override void Initialize(CompilerContext context) 14 | { 15 | _methodCache = new EnvironmentProvision(); 16 | base.Initialize(context); 17 | } 18 | 19 | public override void OnMethodInvocationExpression(MethodInvocationExpression node) 20 | { 21 | var method = node.Target.Entity as ExternalMethod; 22 | if (method != null && method.IsStatic) 23 | { 24 | var replacement = ReplacementFor(node, method); 25 | if (replacement != null) 26 | node.ParentNode.Replace(node, replacement); 27 | } 28 | 29 | base.OnMethodInvocationExpression(node); 30 | } 31 | 32 | private Expression ReplacementFor(MethodInvocationExpression node, ExternalMethod method) 33 | { 34 | if (BinaryOperatorFor(method, out BinaryOperatorType op)) 35 | return new BinaryExpression(op, node.Arguments[0], node.Arguments[1]) { ExpressionType = node.ExpressionType }; 36 | 37 | if (UnaryExpressionFor(method, out UnaryOperatorType unaryOperator)) 38 | return new UnaryExpression(unaryOperator, node.Arguments[0]) { ExpressionType = node.ExpressionType }; 39 | 40 | if (method.Name != "op_Implicit") 41 | return null; 42 | 43 | node.Arguments[0].ExpressionType = node.ExpressionType; 44 | return node.Arguments[0]; 45 | } 46 | 47 | private bool UnaryExpressionFor(ExternalMethod method, out UnaryOperatorType op) 48 | { 49 | if (!method.Name.StartsWith("op_")) 50 | { 51 | op = 0; 52 | return false; 53 | } 54 | 55 | return Enum.TryParse(method.Name.Substring("op_".Length), true, out op); 56 | } 57 | 58 | private bool BinaryOperatorFor(IMethodBase method, out BinaryOperatorType op) 59 | { 60 | if (method == _methodCache.Instance.RuntimeServices_EqualityOperator) 61 | { 62 | op = BinaryOperatorType.Equality; 63 | return true; 64 | } 65 | 66 | if (!method.Name.StartsWith("op_")) 67 | { 68 | op = BinaryOperatorType.None; 69 | return false; 70 | } 71 | 72 | return Enum.TryParse(method.Name.Substring("op_".Length), true, out op); 73 | } 74 | 75 | private EnvironmentProvision _methodCache; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /UnityScript2CSharp/Steps/PreProcessCollector.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Text.RegularExpressions; 4 | using Boo.Lang.Compiler.IO; 5 | using Boo.Lang.Compiler.Steps; 6 | 7 | namespace UnityScript2CSharp.Steps 8 | { 9 | class PreProcessCollector : AbstractCompilerStep 10 | { 11 | public PreProcessCollector(IList referencedPreProcessorSymbols) 12 | { 13 | ReferencedPreProcessorSymbols = referencedPreProcessorSymbols; 14 | } 15 | 16 | public override void Run() 17 | { 18 | var processed = new List(); 19 | foreach (var input in Parameters.Input) 20 | { 21 | current = input.Name; 22 | var textReader = input.Open(); 23 | 24 | var fullSource = textReader.ReadToEnd(); 25 | processed.Add(new StringInput(input.Name, fullSource)); 26 | 27 | ProcessSource(fullSource); 28 | } 29 | 30 | Parameters.Input.Clear(); 31 | Parameters.Input.Extend(processed); 32 | } 33 | 34 | private void ProcessSource(string fullSource) 35 | { 36 | using (var textReader = new StringReader(fullSource)) 37 | { 38 | var lineNumber = 0; 39 | var line = string.Empty; 40 | while ((line = textReader.ReadLine()) != null) 41 | { 42 | ProcessLine(line, ++lineNumber); 43 | } 44 | } 45 | } 46 | 47 | private void ProcessLine(string line, int lineNumber) 48 | { 49 | var match = preProcessorConditionalPattern.Match(line); 50 | if (match.Success) 51 | { 52 | ReferencedPreProcessorSymbols.Add(new SymbolInfo { PreProcessorExpression = match.Groups[1].Value, Source = current, LineNumber = lineNumber }); 53 | } 54 | } 55 | 56 | public IList ReferencedPreProcessorSymbols { get; private set; } 57 | 58 | private string current; 59 | 60 | private static Regex preProcessorConditionalPattern = new Regex(@"^\s*#(?:if|elif)\s+((.|\s)+)$", RegexOptions.Compiled); 61 | } 62 | 63 | struct SymbolInfo 64 | { 65 | public string Source; 66 | public string PreProcessorExpression; 67 | public int LineNumber; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /UnityScript2CSharp/Steps/PromoteImplicitBooleanConversionsToExplicitComparisons.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Boo.Lang.Compiler.Ast; 5 | using Boo.Lang.Compiler.Steps; 6 | using Boo.Lang.Compiler.TypeSystem; 7 | 8 | namespace UnityScript2CSharp.Steps 9 | { 10 | class PromoteImplicitBooleanConversionsToExplicitComparisons : AbstractTransformerCompilerStep 11 | { 12 | public override void OnIfStatement(IfStatement node) 13 | { 14 | ConvertExpressionToBooleanIfNecessary(node, node.Condition); 15 | 16 | node.TrueBlock.Accept(this); 17 | if (node.FalseBlock != null) 18 | node.FalseBlock.Accept(this); 19 | } 20 | 21 | public override void OnWhileStatement(WhileStatement node) 22 | { 23 | ConvertExpressionToBooleanIfNecessary(node, node.Condition); 24 | node.Block.Accept(this); 25 | } 26 | 27 | public override bool EnterUnaryExpression(UnaryExpression node) 28 | { 29 | if (node.Operator == UnaryOperatorType.LogicalNot) 30 | { 31 | var literalExpression = LiteralExpressionFor(node.Operand.ExpressionType); 32 | if (literalExpression != null) 33 | node.ParentNode.Replace(node, new BinaryExpression(BinaryOperatorType.Equality, node.Operand, literalExpression)); 34 | } 35 | 36 | return base.EnterUnaryExpression(node); 37 | } 38 | 39 | public override void OnConditionalExpression(ConditionalExpression node) 40 | { 41 | node.Accept(new ConditionalExpressionFixer()); 42 | base.OnConditionalExpression(node); 43 | } 44 | 45 | public override bool EnterBinaryExpression(BinaryExpression node) 46 | { 47 | FixBooleanExpressionTypeIfRequired(node); 48 | 49 | if (FixMixedBooleanExpressionComparison(node)) 50 | return false; 51 | 52 | if (node.ExpressionType == TypeSystemServices.BoolType || (AstUtil.GetBinaryOperatorKind(node.Operator) != BinaryOperatorKind.Comparison && AstUtil.GetBinaryOperatorKind(node.Operator) != BinaryOperatorKind.Logical)) 53 | return true; 54 | 55 | ConvertExpressionToBooleanIfNecessary(node, node.Right); 56 | ConvertExpressionToBooleanIfNecessary(node, node.Left); 57 | 58 | node.ExpressionType = TypeSystemServices.BoolType; 59 | 60 | return false; 61 | } 62 | 63 | private bool FixMixedBooleanExpressionComparison(BinaryExpression node) 64 | { 65 | if (AstUtil.GetBinaryOperatorKind(node) != BinaryOperatorKind.Comparison) 66 | return false; 67 | 68 | if (node.ExpressionType != TypeSystemServices.BoolType || (node.Left.ExpressionType == TypeSystemServices.BoolType && node.Right.ExpressionType == TypeSystemServices.BoolType)) 69 | return false; 70 | 71 | if (FixMixedBooleanExpressionOperand(node, node.Left, node.Right)) 72 | return true; 73 | 74 | 75 | return FixMixedBooleanExpressionOperand(node, node.Right, node.Left); 76 | } 77 | 78 | private bool FixMixedBooleanExpressionOperand(BinaryExpression node, Expression booleanSide, Expression nonBooleanSide) 79 | { 80 | if (booleanSide.ExpressionType == TypeSystemServices.BoolType && TypeSystemServices.IsNumber(nonBooleanSide.ExpressionType)) 81 | { 82 | var literalExpression = nonBooleanSide as LiteralExpression; 83 | Expression replacementNode = literalExpression != null 84 | ? (Expression)CodeBuilder.CreateBoolLiteral(Convert.ToInt32(literalExpression.ValueObject) != 0) 85 | : CodeBuilder.CreateBoundBinaryExpression(booleanSide.ExpressionType, BinaryOperatorType.Inequality, nonBooleanSide, CodeBuilder.CreateIntegerLiteral(0)); 86 | 87 | node.Replace(nonBooleanSide, replacementNode); 88 | return true; 89 | } 90 | 91 | return false; 92 | } 93 | 94 | private void FixBooleanExpressionTypeIfRequired(BinaryExpression node) 95 | { 96 | if (AstUtil.GetBinaryOperatorKind(node) != BinaryOperatorKind.Comparison) 97 | return; 98 | 99 | if (node.ExpressionType != TypeSystemServices.BoolType) 100 | node.ExpressionType = TypeSystemServices.BoolType; 101 | } 102 | 103 | private void ConvertExpressionToBooleanIfNecessary(Node parent, Expression expression) 104 | { 105 | var unaryExpression = expression as UnaryExpression; 106 | if (unaryExpression != null && unaryExpression.Operator == UnaryOperatorType.LogicalNot) 107 | { 108 | expression.Accept(this); 109 | return; 110 | } 111 | 112 | if (expression.ExpressionType == TypeSystemServices.BoolType) 113 | { 114 | expression.Accept(this); 115 | return; 116 | } 117 | 118 | var binaryExpression = expression as BinaryExpression; 119 | if (binaryExpression != null && (AstUtil.GetBinaryOperatorKind(binaryExpression) == BinaryOperatorKind.Logical || AstUtil.GetBinaryOperatorKind(binaryExpression) == BinaryOperatorKind.Comparison)) 120 | { 121 | expression.Accept(this); 122 | return; 123 | } 124 | 125 | var literalExpression = LiteralExpressionFor(expression.ExpressionType); 126 | if (literalExpression != null) 127 | parent.Replace(expression, new BinaryExpression(BinaryOperatorType.Inequality, expression, literalExpression)); 128 | } 129 | 130 | private Expression LiteralExpressionFor(IType sourceExpressionType) 131 | { 132 | if (sourceExpressionType == null) 133 | return null; 134 | 135 | if (sourceExpressionType == TypeSystemServices.BoolType) 136 | return null; 137 | 138 | if (sourceExpressionType == TypeSystemServices.IntType) 139 | return CodeBuilder.CreateIntegerLiteral(0); 140 | 141 | if (sourceExpressionType == TypeSystemServices.SingleType) 142 | return new DoubleLiteralExpression(0.0f); 143 | 144 | if (sourceExpressionType.IsClass || sourceExpressionType.IsArray || sourceExpressionType.IsInterface) 145 | return CodeBuilder.CreateNullLiteral(); 146 | 147 | if (sourceExpressionType.IsEnum) 148 | { 149 | var candidateMembers = sourceExpressionType.GetMembers().OfType(); 150 | 151 | var enumMember = candidateMembers.FirstOrDefault(member => member.IsStatic && member.StaticValue != null && Convert.ToInt64(member.StaticValue) == 0); 152 | if (enumMember != null) 153 | return CodeBuilder.CreateMemberReference(enumMember); 154 | 155 | return CodeBuilder.CreateCast(sourceExpressionType, CodeBuilder.CreateIntegerLiteral(0)); 156 | } 157 | 158 | return null; 159 | } 160 | } 161 | 162 | // This visitor's responsability is to take expressions like: 163 | // 164 | // var b = s && s.Length > 10; 165 | // 166 | // and convert them to: 167 | // var b = !string.IsNullOrEmpty(s) ? s.Length > 10 : false; 168 | // 169 | // (note that US compiler already does part of this conversion; we only need 170 | // to clean it up) 171 | // 172 | internal class ConditionalExpressionFixer : DepthFirstTransformer 173 | { 174 | public override void OnReferenceExpression(ReferenceExpression node) 175 | { 176 | if (node.Name[0] == '$' && node.Entity.EntityType == EntityType.Local) 177 | { 178 | Expression replaceWith; 179 | if (AstUtil.IsAssignment(node.ParentNode)) 180 | { 181 | var parentAssignment = (BinaryExpression)node.ParentNode; 182 | node.ParentNode.ParentNode.Replace(parentAssignment, parentAssignment.Right); 183 | _replacements[node.Name] = parentAssignment.Right; 184 | } 185 | else if (_replacements.TryGetValue(node.Name, out replaceWith)) 186 | { 187 | node.ParentNode.Replace(node, new BoolLiteralExpression(false)); 188 | } 189 | } 190 | base.OnReferenceExpression(node); 191 | } 192 | 193 | private IDictionary _replacements = new Dictionary(); 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /UnityScript2CSharp/Steps/RemoveUnnecessaryCastInArrayInstantiation.cs: -------------------------------------------------------------------------------- 1 | using Boo.Lang.Compiler.Ast; 2 | using Boo.Lang.Compiler.Steps; 3 | using UnityScript2CSharp.Extensions; 4 | 5 | namespace UnityScript2CSharp.Steps 6 | { 7 | class RemoveUnnecessaryCastInArrayInstantiation : AbstractTransformerCompilerStep 8 | { 9 | public override void OnGenericReferenceExpression(GenericReferenceExpression node) 10 | { 11 | if (node.IsArrayInstantiation()) 12 | { 13 | var parent = (MethodInvocationExpression)node.ParentNode; 14 | if (parent.Arguments.Count == 1 && parent.Arguments[0].NodeType == NodeType.CastExpression) 15 | { 16 | parent.Arguments[0] = ((CastExpression)parent.Arguments[0]).Target; 17 | } 18 | return; 19 | } 20 | 21 | base.OnGenericReferenceExpression(node); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /UnityScript2CSharp/Steps/RenameArrayDeclaration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Boo.Lang.Compiler.Ast; 3 | using Boo.Lang.Compiler.Steps; 4 | using Boo.Lang.Compiler.TypeSystem; 5 | 6 | namespace UnityScript2CSharp.Steps 7 | { 8 | [Serializable] 9 | public class RenameArrayDeclaration : AbstractTransformerCompilerStep 10 | { 11 | public override void Run() 12 | { 13 | Visit(CompileUnit); 14 | } 15 | 16 | public override void OnMethodInvocationExpression(MethodInvocationExpression node) 17 | { 18 | if (node == null || !(node.Target is GenericReferenceExpression)) return; 19 | var genericReferenceExpression = (GenericReferenceExpression)node.Target; 20 | if (!(genericReferenceExpression.Target is MemberReferenceExpression)) return; 21 | var memberReferenceExpression = (MemberReferenceExpression)genericReferenceExpression.Target; 22 | if (!(memberReferenceExpression.Target is MemberReferenceExpression)) return; 23 | var memberReferenceExpression2 = (MemberReferenceExpression)memberReferenceExpression.Target; 24 | if (!(memberReferenceExpression2.Target is MemberReferenceExpression)) return; 25 | var memberReferenceExpression3 = (MemberReferenceExpression)memberReferenceExpression2.Target; 26 | if (!(memberReferenceExpression3.Target is ReferenceExpression)) return; 27 | var referenceExpression = (ReferenceExpression)memberReferenceExpression3.Target; 28 | if (referenceExpression.Name != "Boo" || memberReferenceExpression3.Name != "Lang" || 29 | memberReferenceExpression2.Name != "Builtins" || memberReferenceExpression.Name != "array" || 30 | 1 != genericReferenceExpression.GenericArguments.Count) return; 31 | 32 | var node2 = genericReferenceExpression.GenericArguments[0]; 33 | if (1 != node.Arguments.Count || !(node.Arguments[0] is CastExpression)) return; 34 | var castExpression = (CastExpression)node.Arguments[0]; 35 | var target = castExpression.Target; 36 | if (!(castExpression.Type is SimpleTypeReference)) return; 37 | var simpleTypeReference = (SimpleTypeReference)castExpression.Type; 38 | if (simpleTypeReference.Name != "int") return; 39 | var methodInvocationExpression = new MethodInvocationExpression(LexicalInfo.Empty); 40 | var genericReferenceExpression2 = new GenericReferenceExpression(LexicalInfo.Empty) { Entity = Error.Default }; 41 | 42 | var referenceExpression2 = new ReferenceExpression(LexicalInfo.Empty) { Name = "array"}; 43 | genericReferenceExpression2.Target = referenceExpression2; 44 | genericReferenceExpression2.GenericArguments = TypeReferenceCollection.FromArray(TypeReference.Lift(node2)); 45 | methodInvocationExpression.Target = genericReferenceExpression2; 46 | methodInvocationExpression.Arguments = ExpressionCollection.FromArray(Expression.Lift(target)); 47 | 48 | ReplaceCurrentNode(methodInvocationExpression); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /UnityScript2CSharp/Steps/ReplaceArrayAndStringMemberReferenceWithCamelCaseVersion.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using Boo.Lang.Compiler.Ast; 4 | using Boo.Lang.Compiler.Steps; 5 | using Boo.Lang.Compiler.TypeSystem.Reflection; 6 | 7 | namespace UnityScript2CSharp.Steps 8 | { 9 | internal class ReplaceArrayAndStringMemberReferenceWithCamelCaseVersion : AbstractTransformerCompilerStep 10 | { 11 | public override void OnMemberReferenceExpression(MemberReferenceExpression node) 12 | { 13 | if (Char.IsUpper(node.Name[0])) 14 | return; 15 | 16 | if (!IsArray(node) && !IsUnityScriptStringType(node)) 17 | return; 18 | 19 | if (node.Name == "get_Item" || node.Name == "set_Item" || node.Name == "get_Chars") 20 | return; 21 | 22 | var name = new StringBuilder(); 23 | name.Append(Char.ToUpper(node.Name[0])); 24 | name.Append(node.Name.Substring(1)); 25 | node.Name = name.ToString(); 26 | } 27 | 28 | static bool IsUnityScriptStringType(MemberReferenceExpression node) 29 | { 30 | return node.Target.ExpressionType != null && node.Target.ExpressionType.FullName == "String"; 31 | } 32 | 33 | private static bool IsArray(MemberReferenceExpression node) 34 | { 35 | if (node.Target.ExpressionType == null) 36 | return false; 37 | 38 | if (node.Target.ExpressionType.IsArray || node.Target.ExpressionType.FullName == typeof(Array).FullName) 39 | return true; 40 | 41 | var expressionType = node.Target.ExpressionType as ExternalType; 42 | 43 | return expressionType != null && expressionType.ActualType.FullName == "UnityScript.Lang.Array"; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /UnityScript2CSharp/Steps/ReplaceGetSetItemMethodsWithOriginalIndexers.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Boo.Lang.Compiler.Ast; 3 | using Boo.Lang.Compiler.Steps; 4 | using Boo.Lang.Compiler.TypeSystem; 5 | 6 | namespace UnityScript2CSharp.Steps 7 | { 8 | class ReplaceGetSetItemMethodsWithOriginalIndexers : AbstractTransformerCompilerStep 9 | { 10 | public override void OnMethodInvocationExpression(MethodInvocationExpression node) 11 | { 12 | base.OnMethodInvocationExpression(node); 13 | 14 | var target = node.Target as MemberReferenceExpression; 15 | if (target == null) 16 | return; 17 | 18 | var method = target.Entity as IMethod; 19 | if (method == null || !method.IsSpecialName) 20 | return; 21 | 22 | if (target.Name == "set_Item") 23 | { 24 | var indexerArgs = node.Arguments.Take(node.Arguments.Count - 1).Select(arg => new Slice(arg)).ToArray(); 25 | var slicingExpression = new SlicingExpression(target.Target, indexerArgs); 26 | var right = node.Arguments.Last(); 27 | 28 | node.ParentNode.Replace(node, new BinaryExpression(BinaryOperatorType.Assign, slicingExpression, right)); 29 | return; 30 | } 31 | 32 | if (target.Name =="get_Item" || target.Name == "get_Chars") 33 | { 34 | var indexerArgs = node.Arguments.Take(node.Arguments.Count).Select(arg => new Slice(arg)).ToArray(); 35 | var slicingExpression = new SlicingExpression(target.Target, indexerArgs); 36 | 37 | node.ParentNode.Replace(node, slicingExpression); 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /UnityScript2CSharp/Steps/ReplaceUnityScriptArrayWithObjectArray.cs: -------------------------------------------------------------------------------- 1 | using Boo.Lang.Compiler; 2 | using Boo.Lang.Compiler.Ast; 3 | using Boo.Lang.Compiler.Steps; 4 | using Boo.Lang.Compiler.TypeSystem; 5 | using Boo.Lang.Compiler.TypeSystem.Internal; 6 | 7 | namespace UnityScript2CSharp.Steps 8 | { 9 | class ReplaceUnityScriptArrayWithObjectArray : AbstractTransformerCompilerStep 10 | { 11 | public override void OnSimpleTypeReference(SimpleTypeReference node) 12 | { 13 | if (node.Name == "Array") 14 | { 15 | var arrayReference = new ArrayTypeReference(CodeBuilder.CreateTypeReference(TypeSystemServices.ObjectType)) { Rank = Context.CodeBuilder.CreateIntegerLiteral(1) }; 16 | ReplaceCurrentNode(arrayReference); 17 | } 18 | } 19 | 20 | public override void OnMethodInvocationExpression(MethodInvocationExpression node) 21 | { 22 | var target = node.Target as ReferenceExpression; 23 | if (target != null && target.Name == "Array" && target.Entity.EntityType == EntityType.Constructor) 24 | { 25 | var invokedMethod = (IMethodBase)node.Target.Entity; 26 | var parameters = invokedMethod.GetParameters(); 27 | 28 | if (parameters.Length == 0) 29 | { 30 | //if we have no parameters we are instantiating an "empty" array which probably will be populated by through the "Add" method. 31 | //this will not work (user will get compilation errors in C#), but lets not crash here anyway. 32 | var array = new GenericReferenceExpression { Target = new ReferenceExpression("array"), GenericArguments = TypeReferenceCollection.FromArray(CodeBuilder.CreateTypeReference(TypeSystemServices.ObjectType)) }; 33 | node.Replace(node.Target, array); 34 | node.Arguments.Add(CodeBuilder.CreateIntegerLiteral(0)); 35 | } 36 | else if (parameters.Length == 1 && parameters[0].Type == TypeSystemServices.IntType) 37 | { 38 | //if we have one parameter of type int, we are invoking the ctor with capacity, which is equivalent to instantiate an array with such size 39 | var array = new GenericReferenceExpression { Target = new ReferenceExpression("array"), GenericArguments = TypeReferenceCollection.FromArray(CodeBuilder.CreateTypeReference(TypeSystemServices.ObjectType)) }; 40 | node.Replace(node.Target, array); 41 | } 42 | else 43 | { 44 | if (node.Arguments[0].ExpressionType == TypeSystemServices.IEnumerableType) 45 | { 46 | Context.Errors.Add(CompilerErrorFactory.InvalidArray(node)); 47 | } 48 | 49 | ReplaceCurrentNode(node.Arguments[0]); 50 | } 51 | } 52 | base.OnMethodInvocationExpression(node); 53 | } 54 | 55 | // Handle "auto local variables" 56 | public override void OnBinaryExpression(BinaryExpression node) 57 | { 58 | var isDeclarationStatement = node.Operator == BinaryOperatorType.Assign && node.Left.NodeType == NodeType.ReferenceExpression && node.IsSynthetic; 59 | if (isDeclarationStatement) 60 | { 61 | var localDeclaration = (InternalLocal)node.Left.Entity; 62 | if (localDeclaration.OriginalDeclaration != null) 63 | localDeclaration.OriginalDeclaration.Accept(this); 64 | } 65 | 66 | base.OnBinaryExpression(node); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /UnityScript2CSharp/Steps/SelectiveUnaryExpressionExpansionProcessUnityScriptMethods.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Boo.Lang.Compiler.Ast; 3 | using Boo.Lang.Compiler.TypeSystem; 4 | using UnityScript.Steps; 5 | 6 | namespace UnityScript2CSharp.Steps 7 | { 8 | class SelectiveUnaryExpressionExpansionProcessUnityScriptMethods : ProcessUnityScriptMethods 9 | { 10 | // if not overriden, some post-increment/decrement are converted to *pre-increment/decrement* 11 | public override bool EnterUnaryExpression(UnaryExpression node) 12 | { 13 | return true; 14 | } 15 | 16 | public override void LeaveUnaryExpression(UnaryExpression node) 17 | { 18 | if (node.Operator == UnaryOperatorType.PostDecrement || 19 | node.Operator == UnaryOperatorType.PostIncrement || 20 | node.Operator == UnaryOperatorType.Increment || 21 | node.Operator == UnaryOperatorType.Decrement) 22 | { 23 | node.ExpressionType = node.Operand.ExpressionType; 24 | return; // do not expand post/pre increment/decrement (the syntax is the same as in C#) 25 | } 26 | 27 | base.LeaveUnaryExpression(node); 28 | } 29 | 30 | // 31 | // US compiler will convert *for()* statements to *while* statements (we don't want that) 32 | // This version simply calls necessary methods to ensure semantic information (aka Entities in boo/us) 33 | // for expression inside the for() body will be calculated correctly, but does not convert 34 | // for -> while 35 | // 36 | public override void OnForStatement(ForStatement node) 37 | { 38 | var method = node.GetAncestor(); 39 | var localsBeforeVisiting = (LocalCollection)method.Locals.Clone(); 40 | 41 | Visit(node.Iterator); 42 | ProcessIterator(node.Iterator, node.Declarations); 43 | VisitForStatementBlock(node); 44 | 45 | // Mark any *local* injected by the above code as *synthetic* in order to 46 | // avoid problems with some for() statemets being duplicated in the converted code 47 | foreach (var current in method.Locals) 48 | { 49 | if (!localsBeforeVisiting.Any(candidate => candidate.Matches(current))) 50 | current.IsSynthetic = true; 51 | } 52 | } 53 | 54 | public override void OnMethodInvocationExpression(MethodInvocationExpression node) 55 | { 56 | var original = node.Target; 57 | base.OnMethodInvocationExpression(node); 58 | 59 | var member = node.Target.Entity as IMember; 60 | if (member == null) 61 | return; 62 | 63 | if (member.DeclaringType is ICallableType && node.Target.ToCodeString().Contains("Invoke")) 64 | { 65 | // Convert explicit delegate Invoke() method invocation to method invocation syntax 66 | // i.e: d.Invoke(p1, p2) => d(p1, p2); 67 | node.Replace(node.Target, original); 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /UnityScript2CSharp/Steps/TransforwmKnownUnityEngineMethods.cs: -------------------------------------------------------------------------------- 1 | using Boo.Lang.Compiler.Ast; 2 | using Boo.Lang.Compiler.Steps; 3 | using Boo.Lang.Compiler.TypeSystem; 4 | 5 | namespace UnityScript2CSharp.Steps 6 | { 7 | class TransforwmKnownUnityEngineMethods : AbstractTransformerCompilerStep 8 | { 9 | public override void OnMethodInvocationExpression(MethodInvocationExpression node) 10 | { 11 | var method = node.Target.Entity as IMethodBase; 12 | if (method != null) 13 | { 14 | if (method.DeclaringType.FullName == "UnityEngine.Component" || method.DeclaringType.FullName == "UnityEngine.GameObject") 15 | { 16 | if (HandleGetComponent(node, method)) 17 | return; 18 | } 19 | } 20 | base.OnMethodInvocationExpression(node); 21 | } 22 | 23 | private bool HandleGetComponent(MethodInvocationExpression node, IMethodBase method) 24 | { 25 | if ((method.Name != "GetComponent" && method.Name != "AddComponent") || node.Arguments.Count == 0) 26 | return false; 27 | 28 | if (node.Arguments[0].ExpressionType != TypeSystemServices.StringType) 29 | return false; 30 | 31 | var be = node.ParentNode as BinaryExpression; 32 | if (be != null && be.Operator == BinaryOperatorType.Assign && be.Right == node) 33 | { 34 | node.ParentNode.Replace(be.Right, CodeBuilder.CreateCast(be.Left.ExpressionType, be.Right)); 35 | return true; 36 | } 37 | 38 | var castTarget = FindRelatedParameterDefinition(node.ParentNode, node); 39 | if (castTarget != null) 40 | node.ParentNode.Replace(node, CodeBuilder.CreateCast(castTarget, node)); 41 | 42 | return false; 43 | } 44 | 45 | private IType FindRelatedParameterDefinition(Node invocationNode, Expression tbf) 46 | { 47 | var mie = invocationNode as MethodInvocationExpression; 48 | if (mie == null) 49 | return null; 50 | 51 | var argIndex = mie.Arguments.IndexOf(tbf); 52 | if (argIndex == -1) // not used as argument.... 53 | return null; 54 | 55 | var method = mie.Target.Entity as IMethodBase; 56 | if (method == null) 57 | return null; 58 | 59 | var parameters = method.GetParameters(); 60 | if (parameters.Length < argIndex || parameters[argIndex].Type.IsAssignableFrom(tbf.ExpressionType)) 61 | return null; // parameter and arguments are compatible 62 | 63 | return parameters[argIndex].Type; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /UnityScript2CSharp/SwitchConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Boo.Lang.Compiler.Ast; 4 | using UnityScript2CSharp.Extensions; 5 | 6 | namespace UnityScript2CSharp 7 | { 8 | internal class SwitchConverter 9 | { 10 | private readonly Writer _writer; 11 | private readonly UnityScript2CSharpConverterVisitor _us2CsVisitor; 12 | 13 | public static bool Convert(Block node, Writer writer, UnityScript2CSharpConverterVisitor us2csVisitor) 14 | { 15 | var handler = new SwitchConverter(writer, us2csVisitor); 16 | return handler.Convert(node); 17 | } 18 | 19 | private SwitchConverter(Writer writer, UnityScript2CSharpConverterVisitor us2CsVisitor) 20 | { 21 | _writer = writer; 22 | _us2CsVisitor = us2CsVisitor; 23 | } 24 | 25 | private bool Convert(Block candidateBlock) 26 | { 27 | BinaryExpression conditionVarInitialization; 28 | IfStatement firstSwitchCheckStatement; 29 | LabelStatement switchEnd; 30 | if (!candidateBlock.TryExtractSwitchStatementDetails(out conditionVarInitialization, out firstSwitchCheckStatement, out switchEnd)) 31 | return false; 32 | 33 | WriteSwitchStatement(candidateBlock, conditionVarInitialization, SwitchConditionCastFor(firstSwitchCheckStatement)); 34 | return true; 35 | } 36 | 37 | private CastExpression SwitchConditionCastFor(IfStatement firstSwitchCheckStatement) 38 | { 39 | var castExpression = ((BinaryExpression) firstSwitchCheckStatement.Condition).Left as CastExpression; 40 | if (castExpression == null) 41 | return null; 42 | 43 | return castExpression; 44 | } 45 | 46 | private void WriteSwitchStatement(Block node, BinaryExpression conditionVarInitialization, CastExpression castFromConditionToCases) 47 | { 48 | _writer.Write("switch ("); 49 | 50 | Expression conditionExpression = conditionVarInitialization.Right; 51 | if (castFromConditionToCases != null) 52 | { 53 | _writer.Write("("); 54 | castFromConditionToCases.Type.Accept(_us2CsVisitor); 55 | _writer.Write(") "); 56 | } 57 | 58 | conditionExpression.Accept(_us2CsVisitor); 59 | 60 | _writer.WriteLine(")"); 61 | _writer.WriteLine("{"); 62 | 63 | var nonConstExpressionCaseEntries = new List(); 64 | using (new BlockIdentation(_writer)) 65 | { 66 | foreach (var caseStatement in node.GetSwitchCases(conditionVarInitialization)) 67 | { 68 | var equalityCheck = (BinaryExpression) caseStatement.Condition; 69 | if (!WriteSwitchCase(equalityCheck, caseStatement.TrueBlock)) 70 | nonConstExpressionCaseEntries.Add(caseStatement); 71 | } 72 | 73 | WriteDefaultCase(node, nonConstExpressionCaseEntries, conditionVarInitialization); 74 | } 75 | _writer.WriteLine("}"); 76 | } 77 | 78 | private void WriteDefaultCase(Block node, IList nonConstExpressionCaseEntries, BinaryExpression conditionVarInitialization) 79 | { 80 | var statementIndex = FindDefaultCase(node, conditionVarInitialization.Left); 81 | if (!statementIndex.Any() && nonConstExpressionCaseEntries.Count == 0) 82 | return; 83 | 84 | _writer.WriteLine("default:"); 85 | using (new BlockIdentation(_writer)) 86 | { 87 | WriteNonConstSwitchCases(nonConstExpressionCaseEntries, conditionVarInitialization.Right); 88 | foreach (var stmt in statementIndex) 89 | { 90 | if (stmt.NodeType == NodeType.LabelStatement || stmt.ContainsAnnotation("BREAK")) 91 | continue; 92 | 93 | stmt.Accept(_us2CsVisitor); 94 | } 95 | _writer.WriteLine("break;"); 96 | } 97 | } 98 | 99 | private static IEnumerable FindDefaultCase(Block node, Expression expectedLocalVarInComparison) 100 | { 101 | var index = -1; 102 | for (int i = node.Statements.Count - 1; i > 0; i--) 103 | { 104 | //TODO: Check false positives 105 | var current = node.Statements[i]; 106 | if (current.NodeType == NodeType.IfStatement && ((IfStatement) current).IsCaseEntry(expectedLocalVarInComparison)) 107 | break; 108 | 109 | // Ignore any "artificial labels" 110 | if (current.NodeType != NodeType.LabelStatement || !current.ToCodeString().StartsWith(":$")) 111 | index = i; 112 | } 113 | 114 | return index != -1 ? node.Statements.Skip(index) : Enumerable.Empty(); 115 | } 116 | 117 | private void WriteNonConstSwitchCases(IList nonConstExpressionCaseEntries, Expression tbc) 118 | { 119 | foreach (var caseEntry in nonConstExpressionCaseEntries) 120 | { 121 | var condition = (BinaryExpression) caseEntry.Condition; 122 | FixNonConstReferencesAsCaseCondition(condition, tbc); 123 | caseEntry.Accept(_us2CsVisitor); 124 | } 125 | } 126 | 127 | private void FixNonConstReferencesAsCaseCondition(BinaryExpression condition, Expression tbc) 128 | { 129 | condition.Accept(new NonConstSwitchConditionFixer(tbc)); 130 | } 131 | 132 | private bool WriteSwitchCase(BinaryExpression equalityCheck, Block caseBlock) 133 | { 134 | if (CaseConstantsFor(equalityCheck, out IList caseConstants)) // no constants found. Most likely this is a case with a "non const expression", like 'case System.Environment.MachineName:' 135 | return false; // we are going to process those in the "default" section, through "if statements" instead. 136 | 137 | foreach (var caseConstant in caseConstants) 138 | { 139 | _writer.WriteLine($"case {caseConstant}:"); 140 | } 141 | 142 | using (new BlockIdentation(_writer)) 143 | { 144 | var caseStatements = caseBlock.Statements.Where(stmt => stmt.NodeType != NodeType.LabelStatement); 145 | foreach (var statement in caseStatements) 146 | { 147 | statement.Accept(_us2CsVisitor); 148 | } 149 | } 150 | 151 | return true; 152 | } 153 | 154 | private bool CaseConstantsFor(BinaryExpression binaryExpression, out IList foundConstants) 155 | { 156 | return LiteralCollector.Collect(binaryExpression, out foundConstants); 157 | } 158 | 159 | private class LiteralCollector : DepthFirstVisitor 160 | { 161 | public override void OnIntegerLiteralExpression(IntegerLiteralExpression node) 162 | { 163 | _literals.Add(node.Value.ToString()); 164 | } 165 | 166 | public override void OnDoubleLiteralExpression(DoubleLiteralExpression node) 167 | { 168 | _literals.Add(node.Value.ToString()); 169 | } 170 | 171 | public override void OnStringLiteralExpression(StringLiteralExpression node) 172 | { 173 | _literals.Add($"\"{node.Value}\""); 174 | } 175 | 176 | public override void OnBoolLiteralExpression(BoolLiteralExpression node) 177 | { 178 | _literals.Add(node.Value.ToString()); 179 | } 180 | 181 | public override void OnCharLiteralExpression(CharLiteralExpression node) 182 | { 183 | _literals.Add(node.Value); 184 | } 185 | 186 | public override void OnMemberReferenceExpression(MemberReferenceExpression node) 187 | { 188 | var target = node.Target as ReferenceExpression; 189 | if (target != null && target.IsEnum()) 190 | { 191 | _literals.Add(node.ToCodeString()); 192 | return; 193 | } 194 | 195 | base.OnMemberReferenceExpression(node); 196 | } 197 | 198 | public override void OnReferenceExpression(ReferenceExpression node) 199 | { 200 | if (node.Name.Contains("$switch$")) 201 | return; 202 | 203 | if (!node.IsEnum()) 204 | hasNonConstInCase = true; 205 | } 206 | 207 | public static bool Collect(BinaryExpression binaryExpression, out IList foundConstants) 208 | { 209 | return _instance.CollectInternal(binaryExpression, out foundConstants); 210 | } 211 | 212 | private bool CollectInternal(BinaryExpression binaryExpression, out IList foundConstants) 213 | { 214 | _literals = foundConstants = new List(); 215 | hasNonConstInCase = false; 216 | 217 | binaryExpression.Accept(this); 218 | return _instance.HasNonConstInCase; 219 | } 220 | 221 | public bool HasNonConstInCase => hasNonConstInCase; 222 | 223 | private static LiteralCollector _instance = new LiteralCollector(); 224 | private IList _literals = new List(); 225 | private bool hasNonConstInCase; 226 | } 227 | } 228 | 229 | internal class NonConstSwitchConditionFixer : DepthFirstTransformer 230 | { 231 | private readonly Expression _tbc; 232 | 233 | public NonConstSwitchConditionFixer(Expression tbc) 234 | { 235 | _tbc = tbc; 236 | } 237 | 238 | public override void OnReferenceExpression(ReferenceExpression node) 239 | { 240 | if (node.Name.Contains("$switch$")) 241 | { 242 | node.ParentNode.Replace(node, _tbc); 243 | } 244 | } 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /UnityScript2CSharp/UnityScript2CSharp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {72C806DB-029C-4435-BA36-677440400E21} 8 | Exe 9 | UnityScript2CSharp 10 | UnityScript2CSharp 11 | v4.6.1 12 | 512 13 | true 14 | 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | false 26 | 27 | 28 | AnyCPU 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | 36 | 37 | 38 | Libs\Boo.Lang.dll 39 | 40 | 41 | Libs\Boo.Lang.Compiler.dll 42 | 43 | 44 | Libs\Boo.Lang.Extensions.dll 45 | 46 | 47 | Libs\Boo.Lang.Parser.dll 48 | 49 | 50 | Libs\Boo.Lang.PatternMatching.dll 51 | 52 | 53 | Libs\Boo.Lang.Useful.dll 54 | 55 | 56 | ..\packages\CommandLineParser21.2.1.0.0\lib\net40\CommandLine.dll 57 | 58 | 59 | ..\packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.dll 60 | 61 | 62 | ..\packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.Mdb.dll 63 | 64 | 65 | ..\packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.Pdb.dll 66 | 67 | 68 | ..\packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.Rocks.dll 69 | 70 | 71 | ..\packages\NLog.4.4.10\lib\net45\NLog.dll 72 | 73 | 74 | 75 | 76 | ..\packages\System.Console.4.0.0-rc2-24027\lib\net46\System.Console.dll 77 | 78 | 79 | 80 | ..\packages\System.IO.4.1.0-rc2-24027\lib\net462\System.IO.dll 81 | 82 | 83 | ..\packages\System.Linq.4.1.0-rc2-24027\lib\net462\System.Linq.dll 84 | 85 | 86 | ..\packages\System.Reflection.4.1.0-rc2-24027\lib\net462\System.Reflection.dll 87 | 88 | 89 | ..\packages\System.Reflection.TypeExtensions.4.1.0-rc2-24027\lib\net462\System.Reflection.TypeExtensions.dll 90 | 91 | 92 | ..\packages\System.Runtime.4.1.0-rc2-24027\lib\net462\System.Runtime.dll 93 | 94 | 95 | ..\packages\System.Runtime.Extensions.4.1.0-rc2-24027\lib\net462\System.Runtime.Extensions.dll 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | Libs\UnityScript.dll 105 | 106 | 107 | Libs\UnityScript.Lang.dll 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | -------------------------------------------------------------------------------- /UnityScript2CSharp/UsingCollector.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Boo.Lang.Compiler.Ast; 4 | using Boo.Lang.Compiler.TypeSystem; 5 | using Boo.Lang.Compiler.TypeSystem.Reflection; 6 | using Attribute = Boo.Lang.Compiler.Ast.Attribute; 7 | 8 | namespace UnityScript2CSharp 9 | { 10 | internal class UsingCollector : DepthFirstVisitor 11 | { 12 | public UsingCollector() 13 | { 14 | Usings = new HashSet(); 15 | } 16 | 17 | public override void OnImport(Import node) 18 | { 19 | if ((node.Namespace.StartsWith("UnityEditor") || node.Namespace.StartsWith("UnityEngine")) && !_namespacesSeen.Contains(node.Namespace)) 20 | return; 21 | 22 | Usings.Add(node.Namespace); 23 | } 24 | 25 | public override void OnCallableTypeReference(CallableTypeReference node) 26 | { 27 | var ns = typeof(Func<>).Namespace; 28 | Usings.Add(ns); 29 | _namespacesSeen.Add(ns); 30 | } 31 | 32 | public override void OnAttribute(Attribute node) 33 | { 34 | var ctor = node.Entity as IMember; 35 | if (ctor != null) 36 | { 37 | AddAsSeen(ctor.DeclaringType); 38 | } 39 | 40 | base.OnAttribute(node); 41 | } 42 | 43 | public override void OnSimpleTypeReference(SimpleTypeReference node) 44 | { 45 | AddAsSeen(node.Entity); 46 | base.OnSimpleTypeReference(node); 47 | } 48 | 49 | public override void OnGenericTypeReference(GenericTypeReference node) 50 | { 51 | AddAsSeen(node.Entity); 52 | base.OnGenericTypeReference(node); 53 | } 54 | 55 | private void AddAsSeen(IEntity entity) 56 | { 57 | 58 | var externalType = entity as ExternalType; 59 | if (externalType != null && externalType.ActualType != null) 60 | { 61 | _namespacesSeen.Add(externalType.ActualType.Namespace); 62 | } 63 | } 64 | 65 | public ISet Usings { get; private set; } 66 | 67 | private ISet _namespacesSeen = new HashSet(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /UnityScript2CSharp/Writer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | namespace UnityScript2CSharp 5 | { 6 | internal class Writer 7 | { 8 | private StringBuilder _builder; 9 | private int _indentation; 10 | private int _mark; 11 | private string _toBeWrittenBeforeNextNewLine; 12 | private static readonly string _newLine = Environment.NewLine; 13 | 14 | public Writer(string contents) 15 | { 16 | _builder = new StringBuilder(contents); 17 | } 18 | 19 | public bool IndentNextWrite { get; set; } 20 | 21 | public string Text { get { return _builder.ToString(); } } 22 | 23 | public int Identation 24 | { 25 | get { return _indentation; } 26 | set 27 | { 28 | _indentation = value; 29 | CurrentIdentation = new String(' ', _indentation * 4); 30 | } 31 | } 32 | 33 | public void Write(string str) 34 | { 35 | IndentIfRequired(); 36 | _builder.Append(str); 37 | } 38 | 39 | public void Write(char ch) 40 | { 41 | IndentIfRequired(); 42 | _builder.Append(ch); 43 | } 44 | 45 | internal void Write(long l) 46 | { 47 | IndentIfRequired(); 48 | _builder.Append(l); 49 | } 50 | 51 | public void WriteLine() 52 | { 53 | if (_toBeWrittenBeforeNextNewLine != null) 54 | { 55 | _builder.Append(_toBeWrittenBeforeNextNewLine); 56 | _toBeWrittenBeforeNextNewLine = null; 57 | } 58 | 59 | _builder.Append(_newLine); 60 | IndentNextWrite = true; 61 | } 62 | 63 | public void WriteLine(string str) 64 | { 65 | Write(str); 66 | WriteLine(); 67 | } 68 | public void WriteBeforeNextNewLine(string text) 69 | { 70 | _toBeWrittenBeforeNextNewLine = text; 71 | } 72 | 73 | public void Mark() 74 | { 75 | _mark = _builder.Length; 76 | } 77 | 78 | public void WriteLineIfChanged() 79 | { 80 | if (_builder.Length > _mark) 81 | { 82 | WriteLine(); 83 | } 84 | 85 | _mark = 0; 86 | } 87 | 88 | public static string NewLine { get { return _newLine; } } 89 | 90 | private void IndentIfRequired() 91 | { 92 | if (IndentNextWrite) 93 | { 94 | _builder.Append(CurrentIdentation); 95 | IndentNextWrite = false; 96 | } 97 | } 98 | 99 | private string CurrentIdentation { get; set; } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /UnityScript2CSharp/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Unity Technologies 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ### What is this 2 | A tool to help the conversion from UnityScript -> C# 3 | 4 | You can read more about in [this blog post](https://blogs.unity3d.com/pt/2017/08/11/unityscripts-long-ride-off-into-the-sunset/). 5 | 6 | ### Where can I get help? 7 | 8 | If you hit any issues or have any questions feel free to send a message in the conversion forum thread: https://forum.unity.com/threads/unityscript-2-csharp-conversion-tool.487753/ 9 | 10 | ### How to use 11 | 12 | First, download the tool from [here](https://github.com/Unity-Technologies/unityscript2csharp/releases). 13 | 14 | Before running the conversion tool: 15 | 16 | 1. Backup your project 17 | 18 | 1. Keep in mind that you'll have best results (i.e, a smoother conversion process) if your UnityScripts have *#pragma strict* applied to them. 19 | 20 | 1. Launch Unity editor (**2017.3 ~ 2018.1.x versions are supported; latest 2018.1.x is recommended**) and make sure you allow APIUpdater to run and update any obsolete API usages. This is necessary to avoid compilation errors during the conversion. 21 | 22 | Next step is to run the converter. For that we recomend trying the _Editor Unity Package Integration_ first: 23 | 24 | 1. Install the package (downloaded from [here](https://github.com/Unity-Technologies/unityscript2csharp/releases)) 25 | 1. Select _Tools/Convert Project from UnityScript to C#_ 26 | 1. Follow the steps. 27 | 28 | If you need more control over the options used during the conversion, you can use the application (UnityScript2CSharp.exe) passing the path to the project (**-p**) the Unity root installation folder (**-u**) and any additional assembly references (**-r**) used by the UnityScript scripts. Bellow you can find a list of valid command line arguments and their meaning: 29 | 30 | | Argument | Meaning | 31 | |----------------|-----------------------------------| 32 | | -u, --unityPath | Required. Unity installation path. | 33 | | -p, --projectPath | Required. Path of project to be converted. | 34 | | -r, --references | Assembly references required by the scripts (space separated list).| 35 | | -g, --gameassemblies | References previously built game assemblies (Assembly-*-firstpass.dll under Library/).| 36 | | -s, –symbols | A (comma separated) list of custom symbols to be defined.| 37 | | -o, –deleteOriginals | Deletes original files (default is to rename to .js.old)| 38 | | -d | Dumps out the list of scripts being processed| 39 | |-i | Ignore compilation errors. This allows the conversion process to continue instead of aborting. | 40 | | --skipcomments | (Default: False) Do not try to preserve comments (Use this option if processing comments cause any issues). 41 | | --showorphancomments | Show a list of comments that were not written to the converted sources (used to help identifying issues with the comment processing code). 42 | |-v, –verbose | Show verbose messages | 43 | |-n, –dry-run | Run the conversion but do not change/create any files on disk. | 44 | |–help | Display this help screen. | 45 | 46 | **Example:** 47 | 48 | UnityScript2CSharp.exe **-p** m:\Work\Repo\4-0_AngryBots **-u** M:\Work\Repo\unity\build **-r** "m:\AngryBot Assemblies\Assembly-CSharp.dll" "m:\AngryBot Assemblies\Assembly-UnityScript.dll" **-s** UNITY_ANDROID,UNITY_EDITOR 49 | 50 | ### Limitations 51 | 52 | * Some comments may not be preserved or be misplaced. 53 | 54 | * *Guarded code* (#if … ) 55 | 56 | * UnityScript parser simply ignores guarded code when the condition evaluates to false leaving no traces of the original code when we are visiting the generated AST. The alternative for now is to run the tool multiple times in order to make sure all guarded code will eventually be processed. Each time the tool is executed user is required to merge the generated code manually. 57 | 58 | * Formatting is not preserved 59 | 60 | * UnityScript.Lang.Array (a.k.a *Array*) methods are not fully supported. We convert such type to *object[]* (this means that if your scripts relies on such methods you'll need to replace the variable declarations / instantiation with some other type (like *List*, *Stack*, etc) and fix the code. 61 | 62 | * Type inference in *anonymous function declarations* are inaccurate in some scenarios and may infer the wrong parameter / return type. 63 | 64 | * Local variable scope sometimes gets messed up due to the way UnityScript scope works. 65 | 66 | * Types with hyphens in the name (like *This-Is-Invalid*) are converted as *as-it-is* but they are not valid in C# 67 | 68 | * Missing return values are not inject automatically (i.e, on a *non void* method, a *return;* statement will cause a compilation error in the converted code) 69 | 70 | * Automatic conversion from **object** to *int/long/float/bool* etc is not supported (limited support for *int* -> *bool* conversion in conditional expressions is in place though). 71 | 72 | * *for( init; condition; increment)* is converted to ***while*** 73 | 74 | * Methods **with same name as the declaring class** (invalid in C#) are converted *as-it-is* 75 | 76 | * Invalid operands to **as** operators (which always yield **null** in US) are considered errors by the C# compiler. 77 | 78 | * Equality comparison against *null* used as **statement expressions** generate errors in C# (eg: *foo == null;*) (this code in meaningless, but harmless in US) 79 | 80 | * Code that changes *foreach* loop variable (which is not allowed in C#) are converted *as-is*, which means the resulting code will not compile cleanly. 81 | 82 | * Not supported features 83 | * Property / Event definition 84 | * Macros 85 | * Literals 86 | * Regular expressions 87 | 88 | 89 | Note that any unsupported language construct (a.k.a, AST node type), will inject a comment in the converted source including the piece of code that is not supported and related source/line information. 90 | 91 | ### How to build 92 | 93 | In case you want to build the tool locally, *"all"* you need to do is: 94 | 95 | 1. Clone the repository 96 | 2. In a console, change directory to the cloned repo folder 97 | 3. Restore **nuget** packages (you can download nuget [here](https://dist.nuget.org/index.html)) 98 | -- run **nuget.exe restore** 99 | 4. Build using **msbuild** 100 | -- msbuild UnityScript2CSharp.sln /target:clean,build 101 | 102 | 103 | ### How to run tests 104 | 105 | All tests (in UnityScript2CSharp.Tests.csproj project) can be run with NUnit runner (recommended to use latest version). 106 | 107 | ### Windows 108 | If you have Unity installed most likely you don't need any extra step; in case the tests fail to find Unity installation you can follow steps similar to the ones required for OSX/Linux 109 | 110 | ### OSX / Linux 111 | The easiest way to get the tests running is by setting the environment variable **UNITY_INSTALL_FOLDER** to point to the Unity installation folder and launch **Unit** rest runner. 112 | 113 | 114 | ### FAQ 115 | 116 | #### **Q**: During conversion, the following error is shown in the console: 117 | "*Conversion aborted due to compilation errors:*" 118 | 119 | And then some compiler errors complaining about types not being valid. 120 | 121 | #### **A**: You are missing some assembly reference; if the type in question is define in the project scripts it is most likely Assembly-CSharp.dll or Assembly-UnityScript.dll (just run the conversion tool again passing **-r** *path_to_assembly_csharp path_to_assembly_unityscript* as an argument. 122 | 123 | ---- 124 | #### **Q**: Some of my UnityScript code is not included in the converted CSharp 125 | 126 | #### **A**: Most likely this is code *guarded* by *SYMBOLS*. Look for **#if ** / **#else** directives in the original UnityScript and run the conversion tool passing the right symbols. Note that in some cases one or more symbols may introduce mutually exclusive source snippets which means that no matter if you specify the symbol or not, necessarily some region of the code will be excluded, as in the example: 127 | 128 | #if !SYMBOL_1 129 | // Snippet 1 130 | #endif 131 | 132 | #if SYMBOL_1 133 | // Snippet 2 134 | #endif 135 | In the example above, if you run the conversion tool specifying the symbol *SYMBOL_1* , **Snippet 1** will be skipped (because it is guarded by a **!SYMBOL_1**) and **Snippet 2** will be included. If you don't, **Snippet 1** will be included but **Snippet 2** will not (because *SYMBOL_1* is not defined). 136 | 137 | The best way to workaround this limitation is to set-up a local VCS repository (git, mercurial or any other of your option) and run the conversion tool with a set of *symbols* then commit the generated code, revert the changes to the UnityScript scripts (i.e, restore the original scripts), run the conversion tool again with a different set of *Symbols* and merge the new version of the converted scripts. 138 | 139 | ---- --------------------------------------------------------------------------------