From 789304e670275f522eb456de56a2a74d5436d6e2 Mon Sep 17 00:00:00 2001 From: vddCore Date: Sun, 12 Nov 2023 22:36:51 +0100 Subject: [PATCH] Add doc generation support, core.fail --- FrontEnd/EVIL.evil/EVIL.evil.csproj | 2 +- FrontEnd/EVIL.evil/EvmFrontEnd.cs | 75 ++++-- Shared/Shared.projitems | 3 + .../Ceres.Runtime/Ceres.Runtime.csproj | 2 +- .../Ceres.Runtime/Modules/CoreModule.cs | 16 ++ .../Ceres.Runtime/Modules/StringModule.cs | 9 +- .../RuntimeModule.DocExtensions.cs | 244 +++++++++--------- .../Ceres.Runtime/UserFailException.cs | 18 ++ 8 files changed, 231 insertions(+), 138 deletions(-) create mode 100644 VirtualMachine/Ceres.Runtime/UserFailException.cs diff --git a/FrontEnd/EVIL.evil/EVIL.evil.csproj b/FrontEnd/EVIL.evil/EVIL.evil.csproj index d57000b4..ef7bdea4 100644 --- a/FrontEnd/EVIL.evil/EVIL.evil.csproj +++ b/FrontEnd/EVIL.evil/EVIL.evil.csproj @@ -4,7 +4,7 @@ net7.0 enable evil - 1.4.0 + 1.5.0 diff --git a/FrontEnd/EVIL.evil/EvmFrontEnd.cs b/FrontEnd/EVIL.evil/EvmFrontEnd.cs index db589884..e06869f3 100644 --- a/FrontEnd/EVIL.evil/EvmFrontEnd.cs +++ b/FrontEnd/EVIL.evil/EvmFrontEnd.cs @@ -30,10 +30,12 @@ public partial class EvmFrontEnd { "h|help", "display this message and quit.", (h) => _displayHelpAndQuit = h != null }, { "v|version", "display compiler and VM version information.", (v) => _displayVersionAndQuit = v != null }, { "I|include-dir=", "add include directory to the list of search paths.", (I) => _includeHandler.AddIncludeSearchPath(I) }, + { "g|gen-docs", "generate documentation for all detected native modules.", (g) => _generateModuleDocsAndQuit = g != null } }; private static bool _displayHelpAndQuit; private static bool _displayVersionAndQuit; + private static bool _generateModuleDocsAndQuit; public async Task Run(string[] args) { @@ -49,6 +51,12 @@ public async Task Run(string[] args) Terminate(BuildVersionBanner()); } + if (_generateModuleDocsAndQuit) + { + GenerateModuleDocs(); + Terminate(); + } + if (!extra.Any()) { Terminate( @@ -127,24 +135,7 @@ public async Task Run(string[] args) Console.WriteLine(_compiler.Log.ToString()); } - _runtime.RegisterBuiltInModules(); - - var moduleStorePath = Path.Combine(AppContext.BaseDirectory, "modules"); - if (Directory.Exists(moduleStorePath)) - { - try - { - _runtimeModuleLoader.RegisterUserRuntimeModules(moduleStorePath); - } - catch (RuntimeModuleLoadException rmle) - { - var sb = new StringBuilder(); - sb.AppendLine($"Failed to load user runtime module '{rmle.FilePath}'."); - sb.AppendLine(rmle.ToString()); - - Terminate(sb.ToString(), exitCode: ExitCode.ModuleLoaderFailed); - } - } + RegisterAllModules(); var initChunks = new List(); foreach (var chunk in script.Chunks) @@ -209,6 +200,54 @@ private List InitializeOptions(string[] args) return fileNames; } + private void GenerateModuleDocs() + { + var modules = RegisterAllModules(); + var docsPath = Path.Combine( + AppContext.BaseDirectory, + "docs" + ); + + Directory.CreateDirectory(docsPath); + + foreach (var module in modules) + { + var filePath = Path.Combine(docsPath, $"evrt_{module.FullyQualifiedName}.doc.md"); + Console.WriteLine($"Generating '{filePath}'..."); + + using (var sw = new StreamWriter(filePath)) + { + sw.WriteLine(module.Describe()); + } + } + } + + private List RegisterAllModules() + { + var ret = new List(); + + ret.AddRange(_runtime.RegisterBuiltInModules()); + + var moduleStorePath = Path.Combine(AppContext.BaseDirectory, "modules"); + if (Directory.Exists(moduleStorePath)) + { + try + { + ret.AddRange(_runtimeModuleLoader.RegisterUserRuntimeModules(moduleStorePath)); + } + catch (RuntimeModuleLoadException rmle) + { + var sb = new StringBuilder(); + sb.AppendLine($"Failed to load user runtime module '{rmle.FilePath}'."); + sb.AppendLine(rmle.ToString()); + + Terminate(sb.ToString(), exitCode: ExitCode.ModuleLoaderFailed); + } + } + + return ret; + } + private bool IsValidInitChunk(Chunk chunk) => chunk.HasAttribute(AttributeNames.VmInit); diff --git a/Shared/Shared.projitems b/Shared/Shared.projitems index f49d2c55..9f63efc8 100644 --- a/Shared/Shared.projitems +++ b/Shared/Shared.projitems @@ -12,6 +12,9 @@ README.md + + .github/workflows/build-frontend-with-runtime.yml + diff --git a/VirtualMachine/Ceres.Runtime/Ceres.Runtime.csproj b/VirtualMachine/Ceres.Runtime/Ceres.Runtime.csproj index 9b6e71fb..2cf56ad6 100644 --- a/VirtualMachine/Ceres.Runtime/Ceres.Runtime.csproj +++ b/VirtualMachine/Ceres.Runtime/Ceres.Runtime.csproj @@ -4,7 +4,7 @@ enable 11.0 - 0.6.0 + 0.7.0 Ceres.Runtime Ceres.Runtime true diff --git a/VirtualMachine/Ceres.Runtime/Modules/CoreModule.cs b/VirtualMachine/Ceres.Runtime/Modules/CoreModule.cs index 6c891d64..2f442e4a 100644 --- a/VirtualMachine/Ceres.Runtime/Modules/CoreModule.cs +++ b/VirtualMachine/Ceres.Runtime/Modules/CoreModule.cs @@ -148,5 +148,21 @@ private static DynamicValue StackTraceString(Fiber fiber, params DynamicValue[] return fiber.StackTrace(skipNativeFrames); } + + [RuntimeModuleFunction("fail")] + [EvilDocFunction( + "Terminates VM execution with a user-requested runtime failure. This function does not return." + )] + [EvilDocArgument( + "message", + "The message that should be displayed for the failure.", + DynamicValueType.String, + DefaultValue = "no details provided" + )] + private static DynamicValue Fail(Fiber fiber, params DynamicValue[] args) + { + args.OptionalStringAt(0, "no details provided", out var message); + throw new UserFailException(message, fiber, args); + } } } \ No newline at end of file diff --git a/VirtualMachine/Ceres.Runtime/Modules/StringModule.cs b/VirtualMachine/Ceres.Runtime/Modules/StringModule.cs index 4a2ba2ba..f9666c40 100644 --- a/VirtualMachine/Ceres.Runtime/Modules/StringModule.cs +++ b/VirtualMachine/Ceres.Runtime/Modules/StringModule.cs @@ -129,7 +129,7 @@ private static DynamicValue Split(Fiber _, params DynamicValue[] args) ReturnType = DynamicValueType.String, IsVariadic = true )] - [EvilDocArgument("separator", "A separator to be inserted between each given value during concatenation.")] + [EvilDocArgument("separator", "A separator to be inserted between each given value during concatenation.", DynamicValueType.String)] [EvilDocArgument("...", "An arbitrary amount of values to be concatenated into a String.")] private static DynamicValue Join(Fiber _, params DynamicValue[] args) { @@ -140,6 +140,13 @@ private static DynamicValue Join(Fiber _, params DynamicValue[] args) } [RuntimeModuleFunction("rep")] + [EvilDocFunction( + "Repeats the provided String `count` times.", + Returns = "A String containing `str` repeated `count` times.", + ReturnType = DynamicValueType.String + )] + [EvilDocArgument("str", "A String to be repeated.", DynamicValueType.String)] + [EvilDocArgument("count", "An integer specifying the amount of times to repeat `str`.", DynamicValueType.Number)] private static DynamicValue Repeat(Fiber _, params DynamicValue[] args) { args.ExpectExactly(2) diff --git a/VirtualMachine/Ceres.Runtime/RuntimeModule.DocExtensions.cs b/VirtualMachine/Ceres.Runtime/RuntimeModule.DocExtensions.cs index be1b35fb..6a419987 100644 --- a/VirtualMachine/Ceres.Runtime/RuntimeModule.DocExtensions.cs +++ b/VirtualMachine/Ceres.Runtime/RuntimeModule.DocExtensions.cs @@ -18,54 +18,59 @@ public static class RuntimeModuleDocExtensions if (runtimeModuleGetterAttribute == null) return null; - if (evilDocPropertyAttribute == null) - return null; sb.AppendLine($"## {module.FullyQualifiedName}.{runtimeModuleGetterAttribute.SubNameSpace}"); - sb.AppendLine("**Synopsis** "); - sb.AppendLine($"**`{module.FullyQualifiedName}.{runtimeModuleGetterAttribute.SubNameSpace}`** "); - - if (evilDocPropertyAttribute.Mode.HasFlag(EvilDocPropertyMode.Get)) + if (evilDocPropertyAttribute != null) { - sb.Append($"**` :: get"); - if (evilDocPropertyAttribute.IsAnyGet) - { - sb.AppendLine($" -> Any`**\n "); - } - else - { - sb.AppendLine($" -> {GetTypeString(evilDocPropertyAttribute.ReturnType)}`**\n "); - } - } + sb.AppendLine("**Synopsis** "); + sb.AppendLine($"**`{module.FullyQualifiedName}.{runtimeModuleGetterAttribute.SubNameSpace}`** "); - if (evilDocPropertyAttribute.Mode.HasFlag(EvilDocPropertyMode.Set)) - { - sb.Append($"**` ::set"); - if (evilDocPropertyAttribute.IsAnySet) + if (evilDocPropertyAttribute.Mode.HasFlag(EvilDocPropertyMode.Get)) { - sb.AppendLine($" <- Any`**\n "); + sb.Append($"**` :: get"); + if (evilDocPropertyAttribute.IsAnyGet) + { + sb.AppendLine($" -> Any`**\n "); + } + else + { + sb.AppendLine($" -> {GetTypeString(evilDocPropertyAttribute.ReturnType)}`**\n "); + } } - else + + if (evilDocPropertyAttribute.Mode.HasFlag(EvilDocPropertyMode.Set)) { - if (evilDocPropertyAttribute.InputTypes.Any()) + sb.Append($"**` ::set"); + if (evilDocPropertyAttribute.IsAnySet) { - if (evilDocPropertyAttribute.InputTypes.Length > 1) - { - sb.AppendLine( - $" <- ({string.Join(',', evilDocPropertyAttribute.InputTypes.Select(GetTypeString))})`**\n "); - } - else + sb.AppendLine($" <- Any`**\n "); + } + else + { + if (evilDocPropertyAttribute.InputTypes.Any()) { - sb.AppendLine($" <- {GetTypeString(evilDocPropertyAttribute.InputTypes[0])}`**\n "); + if (evilDocPropertyAttribute.InputTypes.Length > 1) + { + sb.AppendLine( + $" <- ({string.Join(',', evilDocPropertyAttribute.InputTypes.Select(GetTypeString))})`**\n "); + } + else + { + sb.AppendLine($" <- {GetTypeString(evilDocPropertyAttribute.InputTypes[0])}`**\n "); + } } } } + + sb.AppendLine("**Description** "); + sb.AppendLine(evilDocPropertyAttribute.Description); + sb.AppendLine(); + } + else + { + sb.AppendLine("This property has no available documentation."); } - - sb.AppendLine("**Description** "); - sb.AppendLine(evilDocPropertyAttribute.Description); - sb.AppendLine(); if (!blockQuote) { @@ -99,116 +104,121 @@ public static class RuntimeModuleDocExtensions if (runtimeModuleFunctionAttribute == null) return null; - if (evilDocFunctionAttribute == null) - return null; - sb.AppendLine($"## {module.FullyQualifiedName}.{runtimeModuleFunctionAttribute.SubNameSpace}"); - sb.AppendLine("**Synopsis** "); - sb.Append($"`{module.FullyQualifiedName}.{runtimeModuleFunctionAttribute.SubNameSpace}"); - sb.Append("("); + if (evilDocFunctionAttribute != null) { - for (var i = 0; i < evilDocArgumentAttributes.Length; i++) + sb.AppendLine("**Synopsis** "); + sb.Append($"`{module.FullyQualifiedName}.{runtimeModuleFunctionAttribute.SubNameSpace}"); + sb.Append("("); { - var argInfo = evilDocArgumentAttributes[i]; - - if (argInfo.Name == "...") + for (var i = 0; i < evilDocArgumentAttributes.Length; i++) { - continue; - } + var argInfo = evilDocArgumentAttributes[i]; - var argName = argInfo.Name; - if (argInfo.CanBeNil) - { - argName += "?"; - } - - if (argInfo.DefaultValue != null) - { - var defaultValue = argInfo.DefaultValue; - if (argInfo.PrimaryType == DynamicValueType.String) + if (argInfo.Name == "...") { - defaultValue = $"\"{defaultValue}\""; + continue; + } + + var argName = argInfo.Name; + if (argInfo.CanBeNil) + { + argName += "?"; + } + + if (argInfo.DefaultValue != null) + { + var defaultValue = argInfo.DefaultValue; + if (argInfo.PrimaryType == DynamicValueType.String) + { + defaultValue = $"\"{defaultValue}\""; + } + + sb.Append($"[{argName}] = {defaultValue}"); + } + else + { + sb.Append(argName); + } + + if (i < evilDocArgumentAttributes.Length - 1 || evilDocFunctionAttribute.IsVariadic) + { + sb.Append(", "); } - - sb.Append($"[{argName}] = {defaultValue}"); - } - else - { - sb.Append(argName); - } - - if (i < evilDocArgumentAttributes.Length - 1 || evilDocFunctionAttribute.IsVariadic) - { - sb.Append(", "); } } - } - if (evilDocFunctionAttribute.IsVariadic) - { - sb.Append("..."); - } - sb.Append($") "); + if (evilDocFunctionAttribute.IsVariadic) + { + sb.Append("..."); + } - if (evilDocFunctionAttribute.IsAnyReturn) - { - sb.AppendLine($" -> Any`\n "); - } - else - { - sb.AppendLine($" -> {GetTypeString(evilDocFunctionAttribute.ReturnType)}`\n "); - } + sb.Append($") "); - sb.AppendLine("**Description** "); - sb.AppendLine(evilDocFunctionAttribute.Description); + if (evilDocFunctionAttribute.IsAnyReturn) + { + sb.AppendLine($" -> Any`\n "); + } + else + { + sb.AppendLine($" -> {GetTypeString(evilDocFunctionAttribute.ReturnType)}`\n "); + } - if (string.IsNullOrEmpty(evilDocFunctionAttribute.Returns)) - { - sb.AppendLine(); - sb.AppendLine("**Returns** "); - sb.AppendLine(evilDocFunctionAttribute.Returns); - } + sb.AppendLine("**Description** "); + sb.AppendLine(evilDocFunctionAttribute.Description); - if (evilDocArgumentAttributes.Any()) - { - sb.AppendLine(); - sb.AppendLine("**Arguments** "); - sb.AppendLine("| Name | Description | Type(s) | Can be `nil` | Optional | Default Value | "); - sb.AppendLine("| ---- | ----------- | ------- | ------------ | -------- | ------------- | "); - for (var i = 0; i < evilDocArgumentAttributes.Length; i++) + if (string.IsNullOrEmpty(evilDocFunctionAttribute.Returns)) { - var argInfo = evilDocArgumentAttributes[i]; - var argType = argInfo.IsAnyType ? "Any" : GetTypeString(argInfo.PrimaryType); + sb.AppendLine(); + sb.AppendLine("**Returns** "); + sb.AppendLine(evilDocFunctionAttribute.Returns); + } - if (!argInfo.IsAnyType && argInfo.OtherTypes != null) + if (evilDocArgumentAttributes.Any()) + { + sb.AppendLine(); + sb.AppendLine("**Arguments** "); + sb.AppendLine("| Name | Description | Type(s) | Can be `nil` | Optional | Default Value | "); + sb.AppendLine("| ---- | ----------- | ------- | ------------ | -------- | ------------- | "); + for (var i = 0; i < evilDocArgumentAttributes.Length; i++) { - foreach (var otherType in argInfo.OtherTypes) + var argInfo = evilDocArgumentAttributes[i]; + var argType = argInfo.IsAnyType ? "Any" : GetTypeString(argInfo.PrimaryType); + + if (!argInfo.IsAnyType && argInfo.OtherTypes != null) { - argType += $", {GetTypeString(otherType)}"; + foreach (var otherType in argInfo.OtherTypes) + { + argType += $", {GetTypeString(otherType)}"; + } } - } - var defaultValue = "None"; + var defaultValue = "None"; - if (argInfo.DefaultValue != null) - { - if (argInfo.PrimaryType == DynamicValueType.String) + if (argInfo.DefaultValue != null) { - defaultValue = $"`\"{argInfo.DefaultValue}\"`"; + if (argInfo.PrimaryType == DynamicValueType.String) + { + defaultValue = $"`\"{argInfo.DefaultValue}\"`"; + } + else + { + defaultValue = $"`{argInfo.DefaultValue}`"; + } } - else - { - defaultValue = $"`{argInfo.DefaultValue}`"; - } - } - var optional = argInfo.DefaultValue == null ? "No" : "Yes"; - var canBeNil = argInfo.CanBeNil ? "Yes" : "No"; - - sb.AppendLine($"| `{argInfo.Name}` | {argInfo.Description} | `{argType}` | {canBeNil} | {optional} | {defaultValue} | "); + var optional = argInfo.DefaultValue == null ? "No" : "Yes"; + var canBeNil = argInfo.CanBeNil ? "Yes" : "No"; + + sb.AppendLine($"| `{argInfo.Name}` | {argInfo.Description} | `{argType}` | {canBeNil} | {optional} | {defaultValue} | "); + } } } + else + { + sb.AppendLine("This function has no available documentation."); + } if (!blockQuote) { diff --git a/VirtualMachine/Ceres.Runtime/UserFailException.cs b/VirtualMachine/Ceres.Runtime/UserFailException.cs new file mode 100644 index 00000000..79166055 --- /dev/null +++ b/VirtualMachine/Ceres.Runtime/UserFailException.cs @@ -0,0 +1,18 @@ +using Ceres.ExecutionEngine.Concurrency; +using Ceres.ExecutionEngine.TypeSystem; + +namespace Ceres.Runtime +{ + public class UserFailException : EvilRuntimeException + { + public Fiber Fiber { get; } + public DynamicValue[] Arguments { get; } + + public UserFailException(string message, Fiber fiber, DynamicValue[] arguments) + : base(message) + { + Fiber = fiber; + Arguments = arguments; + } + } +} \ No newline at end of file