private static void GenerateMethodStub(StringBuilder output, ClassDeclarationSyntax classSyntax, JSMethodGroup methodGroup, string returnType = "object") { output.AppendLine(); output.AppendLine($"\t\tprivate static {returnType} {methodGroup.StubName}(ScriptEngine engine, object thisObj, object[] args)"); output.AppendLine("\t\t{"); if (!methodGroup.IsStatic) { output.AppendLine($"\t\t\tthisObj = TypeConverter.ToObject(engine, thisObj);"); output.AppendLine($"\t\t\tif (!(thisObj is {classSyntax.Identifier.ToString()}))"); output.AppendLine($"\t\t\t\tthrow new JavaScriptException(engine, ErrorType.TypeError, \"The method '{methodGroup.First().FunctionName}' is not generic.\");"); } else if (methodGroup.Any(m => m.HasThisObject && m.ThisObjectParameterType != "object")) { output.AppendLine("\t\t\tif (thisObj == null || thisObj == Undefined.Value || thisObj == Null.Value)"); output.AppendLine("\t\t\t\tthrow new JavaScriptException(engine, ErrorType.TypeError, \"Cannot convert undefined or null to object.\");"); } int maxParameterCount = methodGroup.Max(mds => mds.Parameters.Count()); if (maxParameterCount == 0) { output.Append("\t\t\t"); output.AppendLine(GenerateMethodCall(classSyntax, methodGroup.Single(), 0)); } else { output.AppendLine("\t\t\tswitch (args.Length)"); output.AppendLine("\t\t\t{"); for (int i = 0; i <= maxParameterCount; i++) { // If the parameter count is X, then find the method with smallest number of // parameters which has at least X parameters. var method = methodGroup.Where(mds => mds.Parameters.Count() >= i).OrderBy(mds => mds.Parameters.Count()).First(); if (i < maxParameterCount) { output.AppendLine($"\t\t\t\tcase {i}:"); } else { output.AppendLine($"\t\t\t\tdefault:"); } output.Append("\t\t\t\t\t"); if (i < methodGroup.RequiredArgumentCount) { output.AppendLine($"throw new JavaScriptException(engine, ErrorType.TypeError, \"Required argument '{method.Parameters.Skip(i).First().Name}' was not specified.\");"); } else { output.AppendLine(GenerateMethodCall(classSyntax, method, i)); } } output.AppendLine("\t\t\t}"); } output.AppendLine("\t\t}"); }
static void Main(string[] args) { IEnumerable <string> files = Directory.EnumerateFiles(@"..\..\..\..\Jurassic", "*.cs", SearchOption.AllDirectories); files = files.Union(Directory.EnumerateFiles(@"..\..\..\..\Jurassic.Extensions", "*.cs", SearchOption.AllDirectories)); foreach (var csFilePath in files) { var syntaxTree = CSharpSyntaxTree.ParseText(File.ReadAllText(csFilePath)); var classCollector = new ClassCollector(); classCollector.Visit(syntaxTree.GetRoot()); // Construct the output file. var output = new StringBuilder(); output.AppendLine("/*"); output.AppendLine(" * This file is auto-generated, do not modify directly."); output.AppendLine(" */"); output.AppendLine(); output.AppendLine("using System.Collections.Generic;"); output.AppendLine("using Jurassic;"); if (classCollector.Classes.Any(classSyntax => ((NamespaceDeclarationSyntax)classSyntax.Parent).Name.ToString() != "Jurassic.Library")) { output.AppendLine("using Jurassic.Library;"); } output.AppendLine(); bool outputFile = false; foreach (var classSyntax in classCollector.Classes) { // Find all the methods with [JSInternalFunction], [JSCallFunction], [JSConstructorFunction], [JSProperty] or [JSField]. var memberCollector = new ClassMembersCollector(); memberCollector.Visit(classSyntax); if (memberCollector.JSInternalFunctionMethods.Any() == false && memberCollector.JSCallFunctionMethods.Any() == false && memberCollector.JSConstructorFunctionMethods.Any() == false && memberCollector.JSProperties.Any() == false && memberCollector.JSFields.Any() == false) { continue; } Console.WriteLine($"Generating stubs for {classSyntax.Identifier.ToString()}"); outputFile = true; var methodGroups = JSMethodGroup.FromMethods(memberCollector.JSInternalFunctionMethods); output.AppendLine($"namespace {((NamespaceDeclarationSyntax)classSyntax.Parent).Name}"); output.AppendLine("{"); output.AppendLine(); output.AppendLine($"\t{classSyntax.Modifiers} class {classSyntax.Identifier}"); output.AppendLine("\t{"); // Output the PopulateStubs method. if (memberCollector.JSInternalFunctionMethods.Any() || memberCollector.JSProperties.Any() || memberCollector.JSFields.Any()) { output.AppendLine("\t\tprivate static List<PropertyNameAndValue> GetDeclarativeProperties(ScriptEngine engine)"); output.AppendLine("\t\t{"); output.AppendLine($"\t\t\treturn new List<PropertyNameAndValue>({memberCollector.JSInternalFunctionMethods.Count + memberCollector.JSFields.Count + 4})"); output.AppendLine("\t\t\t{"); foreach (var field in memberCollector.JSFields) { foreach (var variable in field.Declaration.Variables) { output.AppendLine($"\t\t\t\tnew PropertyNameAndValue(\"{variable.Identifier.ToString()}\", {variable.Identifier.ToString()}, PropertyAttributes.Sealed),"); } } foreach (var property in memberCollector.JSProperties.Select(p => new JSProperty(p))) { if (property.SetterStubName == null) { output.AppendLine($"\t\t\t\tnew PropertyNameAndValue({property.PropertyKey}, new PropertyDescriptor(" + $"new ClrStubFunction(engine, \"get {property.FunctionName}\", 0, {property.GetterStubName}), " + $"null, {property.JSPropertyAttributes})),"); } else { output.AppendLine($"\t\t\t\tnew PropertyNameAndValue({property.PropertyKey}, new PropertyDescriptor(" + $"new ClrStubFunction(engine, \"get {property.FunctionName}\", 0, {property.GetterStubName}), " + $"new ClrStubFunction(engine, \"set {property.FunctionName}\", 0, {property.GetterStubName}), " + $"{property.JSPropertyAttributes})),"); } } foreach (var methodGroup in methodGroups) { output.AppendLine($"\t\t\t\tnew PropertyNameAndValue({methodGroup.PropertyKey}, " + $"new ClrStubFunction(engine, \"{methodGroup.FunctionName}\", " + $"{methodGroup.JSLength}, {methodGroup.StubName}), {methodGroup.JSPropertyAttributes}),"); } output.AppendLine("\t\t\t};"); output.AppendLine("\t\t}"); } if (memberCollector.JSCallFunctionMethods.Any()) { GenerateMethodStub(output, classSyntax, new JSMethodGroup(memberCollector.JSCallFunctionMethods.Select(mds => new JSMethod(mds)))); } if (memberCollector.JSConstructorFunctionMethods.Any()) { GenerateMethodStub(output, classSyntax, new JSMethodGroup(memberCollector.JSConstructorFunctionMethods.Select(mds => new JSMethod(mds))), "ObjectInstance"); } foreach (var property in memberCollector.JSProperties.Select(p => new JSProperty(p))) { output.AppendLine(); output.AppendLine($"\t\tprivate static object {property.GetterStubName}(ScriptEngine engine, object thisObj, object[] args)"); output.AppendLine("\t\t{"); output.AppendLine($"\t\t\tthisObj = TypeConverter.ToObject(engine, thisObj);"); output.AppendLine($"\t\t\tif (!(thisObj is {classSyntax.Identifier.ToString()}))"); output.AppendLine($"\t\t\t\tthrow new JavaScriptException(engine, ErrorType.TypeError, \"The method 'get {property.FunctionName}' is not generic.\");"); output.AppendLine($"\t\t\treturn (({classSyntax.Identifier.ToString()})thisObj).{property.PropertyName};"); output.AppendLine("\t\t}"); if (property.SetterStubName != null) { output.AppendLine(); output.AppendLine($"\t\tprivate static object {property.SetterStubName}(ScriptEngine engine, object thisObj, object[] args)"); output.AppendLine("\t\t{"); output.AppendLine($"\t\t\tthisObj = TypeConverter.ToObject(engine, thisObj);"); output.AppendLine($"\t\t\tif (!(thisObj is {classSyntax.Identifier.ToString()}))"); output.AppendLine($"\t\t\t\tthrow new JavaScriptException(engine, ErrorType.TypeError, \"The method 'set {property.FunctionName}' is not generic.\");"); output.AppendLine($"\t\t\t(({classSyntax.Identifier.ToString()})thisObj).{property.PropertyName} = {ConvertTo("args.Length > 0 ? args[0] : Undefined.Value", property.ReturnType, null)};"); output.AppendLine("\t\t}"); } } foreach (var methodGroup in methodGroups) { GenerateMethodStub(output, classSyntax, methodGroup); } output.AppendLine("\t}"); output.AppendLine(); output.AppendLine("}"); } if (outputFile) { // Write the output file. File.WriteAllText(Path.Combine(Path.GetDirectoryName(csFilePath), Path.GetFileNameWithoutExtension(csFilePath) + ".g.cs"), output.ToString()); } } }