/// <summary> /// Make a C#-style docstring + function declaration. /// </summary> /// <remarks> /// This is used to generate the functions in <see cref="Image"/>. /// </remarks> /// <param name="operationName">Operation name.</param> /// <param name="indent">Indentation level.</param> /// <param name="mutable">Generate <see cref="MutableImage"/>.</param> /// <param name="outParameters">The out parameters of this function.</param> /// <returns>A C#-style docstring + function declaration.</returns> private string GenerateFunction(string operationName, string indent = " ", bool mutable = false, IReadOnlyList <Introspect.Argument> outParameters = null) { using var op = Operation.NewFromName(operationName); if ((op.GetFlags() & Enums.OperationFlags.DEPRECATED) != 0) { throw new ArgumentException($"No such operator. Operator \"{operationName}\" is deprecated"); } var intro = Introspect.Get(operationName); // we are only interested in non-deprecated args var optionalInput = intro.OptionalInput .Where(arg => (arg.Value.Flags & Enums.ArgumentFlags.DEPRECATED) == 0) .Select(x => x.Value) .ToArray(); var optionalOutput = intro.OptionalOutput .Where(arg => (arg.Value.Flags & Enums.ArgumentFlags.DEPRECATED) == 0) .Select(x => x.Value) .ToArray(); // these are always non-deprecated var requiredInput = intro.RequiredInput.ToArray(); var requiredOutput = intro.RequiredOutput.ToArray(); if (mutable ^ intro.Mutable) { throw new ArgumentException( $"Cannot generate \"{operationName}\" as this is a {(intro.Mutable ? string.Empty : "non-")}mutable operation."); } string[] reservedKeywords = { "in", "ref", "out", "ushort" }; string SafeIdentifier(string name) => reservedKeywords.Contains(name) ? $"@{name}" : name; string SafeCast(string type, string name = "result") { switch (type) { case "GObject": case "Image": case "int[]": case "double[]": case "byte[]": case "Image[]": case "object[]": return($" as {type};"); case "bool": return($" is {type} {name} && {name};"); case "int": return($" is {type} {name} ? {name} : 0;"); case "ulong": return($" is {type} {name} ? {name} : 0ul;"); case "double": return($" is {type} {name} ? {name} : 0d;"); case "string": return($" is {type} {name} ? {name} : null;"); default: return(";"); } } string ExplicitCast(string type) { return(type switch { { } enumString when enumString.StartsWith("Enums.") => $"({type})", _ => string.Empty }); }
/// <summary> /// Make a C#-style docstring + function declaration. /// </summary> /// <remarks> /// This is used to generate the functions in <see cref="Image"/>. /// </remarks> /// <param name="operationName">Operation name.</param> /// <param name="indent">Indentation level.</param> /// <param name="outParameters">The out parameters of this function.</param> /// <returns>A C#-style docstring + function declaration.</returns> private string GenerateFunction(string operationName, string indent = " ", IReadOnlyList <Introspect.Argument> outParameters = null) { var op = Operation.NewFromName(operationName); if ((op.GetFlags() & Enums.OperationFlags.DEPRECATED) != 0) { throw new Exception($"No such operator. Operator \"{operationName}\" is deprecated"); } var intro = Introspect.Get(operationName); // we are only interested in non-deprecated args var optionalInput = intro.OptionalInput .Where(arg => (arg.Value.Flags & Enums.ArgumentFlags.DEPRECATED) == 0) .Select(x => x.Value) .ToList(); var optionalOutput = intro.OptionalOutput .Where(arg => (arg.Value.Flags & Enums.ArgumentFlags.DEPRECATED) == 0) .Select(x => x.Value) .ToList(); string[] reservedKeywords = { "in", "ref", "out", "ushort" }; string SafeIdentifier(string name) => reservedKeywords.Contains(name) ? "@" + name : name; string SafeCast(string type, string name = "result") { switch (type) { case "GObject": case "Image": case "int[]": case "double[]": case "byte[]": case "Image[]": case "object[]": return($" as {type};"); case "bool": return($" is {type} {name} && {name};"); case "int": return($" is {type} {name} ? {name} : 0;"); case "ulong": return($" is {type} {name} ? {name} : 0ul;"); case "double": return($" is {type} {name} ? {name} : 0d;"); case "string": return($" is {type} {name} ? {name} : null;"); default: return(";"); } } string ToNullable(string type, string name) { switch (type) { case "Image[]": case "object[]": case "int[]": case "double[]": case "byte[]": case "GObject": case "Image": case "string": return($"{type} {name} = null"); case "bool": case "int": case "ulong": case "double": return($"{type}? {name} = null"); default: throw new Exception("Unsupported type: " + type); } } string CheckNullable(string type, string name) { switch (type) { case "Image[]": case "object[]": case "int[]": case "double[]": case "byte[]": return($"{name} != null && {name}.Length > 0"); case "GObject": case "Image": case "string": return($"{name} != null"); case "bool": case "int": case "ulong": case "double": return($"{name}.HasValue"); default: throw new Exception("Unsupported type: " + type); } } var result = new StringBuilder($"{indent}/// <summary>\n"); var newOperationName = operationName.ToPascalCase(); var description = op.GetDescription(); result.AppendLine($"{indent}/// {description.FirstLetterToUpper()}.") .AppendLine($"{indent}/// </summary>") .AppendLine($"{indent}/// <example>") .AppendLine($"{indent}/// <code language=\"lang-csharp\">") .Append($"{indent}/// "); if (intro.RequiredOutput.Count == 1) { var arg = intro.RequiredOutput.First(); result.Append( $"{GTypeToCSharp(arg.Type)} {SafeIdentifier(arg.Name).ToPascalCase().FirstLetterToLower()} = "); } else if (intro.RequiredOutput.Count > 1) { result.Append("var output = "); } result.Append(intro.MemberX.HasValue ? intro.MemberX.Value.Name : "NetVips.Image") .Append( $".{newOperationName}({string.Join(", ", intro.RequiredInput.Select(arg => SafeIdentifier(arg.Name).ToPascalCase().FirstLetterToLower()).ToArray())}"); if (outParameters != null) { if (intro.RequiredInput.Count > 0) { result.Append(", "); } result.Append( $"{string.Join(", ", outParameters.Select(arg => $"out var {SafeIdentifier(arg.Name).ToPascalCase().FirstLetterToLower()}").ToArray())}"); } if (optionalInput.Count > 0) { if (intro.RequiredInput.Count > 0 || outParameters != null) { result.Append(", "); } for (var i = 0; i < optionalInput.Count; i++) { var arg = optionalInput[i]; result.Append( $"{SafeIdentifier(arg.Name).ToPascalCase().FirstLetterToLower()}: {GTypeToCSharp(arg.Type)}") .Append(i != optionalInput.Count - 1 ? ", " : string.Empty); } } result.AppendLine(");"); result.AppendLine($"{indent}/// </code>") .AppendLine($"{indent}/// </example>"); foreach (var arg in intro.RequiredInput) { result.AppendLine( $"{indent}/// <param name=\"{arg.Name.ToPascalCase().FirstLetterToLower()}\">{op.GetBlurb(arg.Name)}.</param>"); } if (outParameters != null) { foreach (var outParameter in outParameters) { result.AppendLine( $"{indent}/// <param name=\"{outParameter.Name.ToPascalCase().FirstLetterToLower()}\">{op.GetBlurb(outParameter.Name)}.</param>"); } } foreach (var arg in optionalInput) { result.AppendLine( $"{indent}/// <param name=\"{arg.Name.ToPascalCase().FirstLetterToLower()}\">{op.GetBlurb(arg.Name)}.</param>"); } string outputType; var outputTypes = intro.RequiredOutput.Select(arg => GTypeToCSharp(arg.Type)).ToArray(); switch (outputTypes.Length) { case 0: outputType = "void"; break; case 1: outputType = outputTypes[0]; break; default: outputType = outputTypes.Any(o => o != outputTypes[0]) ? $"{outputTypes[0]}[]" : "object[]"; break; } string ToCref(string name) => name.Equals("Image") || name.Equals("GObject") ? $"new <see cref=\"{name}\"/>" : name; if (outputType.EndsWith("[]")) { result.AppendLine( $"{indent}/// <returns>An array of {ToCref(outputType.Remove(outputType.Length - 2))}s.</returns>"); } else if (!outputType.Equals("void")) { result.AppendLine($"{indent}/// <returns>A {ToCref(outputType)}.</returns>"); } result.Append($"{indent}public ") .Append(intro.MemberX.HasValue ? string.Empty : "static ") .Append(outputType); if (intro.RequiredInput.Count == 1 && outParameters == null && optionalInput.Count == 0 && intro.RequiredInput[0].Type == GValue.ArrayImageType) { // We could safely use the params keyword result.Append( $" {newOperationName}(params Image[] {SafeIdentifier(intro.RequiredInput[0].Name).ToPascalCase().FirstLetterToLower()}"); } else { result.Append( $" {newOperationName}(" + string.Join(", ", intro.RequiredInput.Select(arg => $"{GTypeToCSharp(arg.Type)} {SafeIdentifier(arg.Name).ToPascalCase().FirstLetterToLower()}") .ToArray())); } if (outParameters != null) { if (intro.RequiredInput.Count > 0) { result.Append(", "); } result.Append(string.Join(", ", outParameters.Select(arg => $"out {GTypeToCSharp(arg.Type)} {SafeIdentifier(arg.Name).ToPascalCase().FirstLetterToLower()}") .ToArray())); } if (optionalInput.Count > 0) { if (intro.RequiredInput.Count > 0 || outParameters != null) { result.Append(", "); } result.Append(string.Join(", ", optionalInput.Select(arg => $"{ToNullable(GTypeToCSharp(arg.Type), SafeIdentifier(arg.Name).ToPascalCase().FirstLetterToLower())}") .ToArray())); } result.AppendLine(")") .AppendLine($"{indent}{{"); if (optionalInput.Count > 0) { result.AppendLine($"{indent} var options = new VOption();").AppendLine(); foreach (var arg in optionalInput) { var safeIdentifier = SafeIdentifier(arg.Name).ToPascalCase().FirstLetterToLower(); var optionsName = safeIdentifier == arg.Name ? "nameof(" + arg.Name + ")" : "\"" + arg.Name + "\""; result.Append($"{indent} if (") .Append(CheckNullable(GTypeToCSharp(arg.Type), safeIdentifier)) .AppendLine(")") .AppendLine($"{indent} {{") .AppendLine($"{indent} options.Add({optionsName}, {safeIdentifier});") .AppendLine($"{indent} }}") .AppendLine(); } } if (outParameters != null) { if (optionalInput.Count > 0) { foreach (var arg in outParameters) { result.AppendLine($"{indent} options.Add(\"{arg.Name}\", true);"); } } else { result.AppendLine($"{indent} var optionalOutput = new VOption") .AppendLine($"{indent} {{"); for (var i = 0; i < outParameters.Count; i++) { var arg = outParameters[i]; result.Append($"{indent} {{\"{arg.Name}\", true}}") .AppendLine(i != outParameters.Count - 1 ? "," : string.Empty); } result.AppendLine($"{indent} }};"); } result.AppendLine() .Append($"{indent} var results = ") .Append(intro.MemberX.HasValue ? "this" : "Operation") .Append($".Call(\"{operationName}\"") .Append(optionalInput.Count > 0 ? ", options" : ", optionalOutput"); } else { result.Append($"{indent} "); if (outputType != "void") { result.Append("return "); } result.Append(intro.MemberX.HasValue ? "this" : "Operation") .Append($".Call(\"{operationName}\""); if (optionalInput.Count > 0) { result.Append(", options"); } } if (intro.RequiredInput.Count > 0) { result.Append(", "); // Co-variant array conversion from Image[] to object[] can cause run-time exception on write operation. // So we need to wrap the image array into a object array. var needToWrap = intro.RequiredInput.Count == 1 && GTypeToCSharp(intro.RequiredInput[0].Type).Equals("Image[]"); if (needToWrap) { result.Append("new object[] { "); } result.Append(string.Join(", ", intro.RequiredInput.Select(arg => SafeIdentifier(arg.Name).ToPascalCase().FirstLetterToLower()) .ToArray())); if (needToWrap) { result.Append(" }"); } } result.Append(")"); if (outParameters != null) { result.AppendLine(" as object[];"); if (outputType != "void") { result.Append($"{indent} var finalResult = results?[0]") .Append(SafeCast(outputType)); } } else { result.Append(SafeCast(outputType)) .AppendLine(); } if (outParameters != null) { result.AppendLine() .AppendLine($"{indent} var opts = results?[1] as VOption;"); for (var i = 0; i < outParameters.Count; i++) { var arg = outParameters[i]; result.Append( $"{indent} {SafeIdentifier(arg.Name).ToPascalCase().FirstLetterToLower()} = opts?[\"{arg.Name}\"]") .AppendLine(SafeCast(GTypeToCSharp(arg.Type), $"out{i + 1}")); } if (outputType != "void") { result.AppendLine() .Append($"{indent} return finalResult;") .AppendLine(); } } result.Append($"{indent}}}") .AppendLine(); // Create method overloading if necessary if (optionalOutput.Count > 0 && outParameters == null) { result.AppendLine() .Append(GenerateFunction(operationName, indent, new[] { optionalOutput.ElementAt(0) })); } else if (outParameters != null && outParameters.Count != optionalOutput.Count) { result.AppendLine() .Append(GenerateFunction(operationName, indent, optionalOutput.Take(outParameters.Count + 1).ToArray())); } return(result.ToString()); }