From a933df44240c435d1ed52a6a202e42348a3bd51a Mon Sep 17 00:00:00 2001 From: Patrick Stevens <3138005+Smaug123@users.noreply.github.com> Date: Fri, 23 May 2025 14:04:13 +0100 Subject: [PATCH] Tidy up return flow (#12) --- .gitignore | 3 + WoofWare.PawPrint.Test/Roslyn.fs | 9 +-- WoofWare.PawPrint.Test/TestCases.fs | 77 ++++++++++++++++--- .../WoofWare.PawPrint.Test.fsproj | 3 +- .../{HelloWorld.cs => BasicException.cs} | 1 - WoofWare.PawPrint.Test/sources/WriteLine.cs | 13 ++++ WoofWare.PawPrint/AbstractMachine.fs | 50 ++++-------- 7 files changed, 104 insertions(+), 52 deletions(-) rename WoofWare.PawPrint.Test/sources/{HelloWorld.cs => BasicException.cs} (88%) create mode 100644 WoofWare.PawPrint.Test/sources/WriteLine.cs diff --git a/.gitignore b/.gitignore index bcf7e24..132e4f3 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,6 @@ riderModule.iml .direnv/ *.DotSettings.user result + +.fake +.vscode/ diff --git a/WoofWare.PawPrint.Test/Roslyn.fs b/WoofWare.PawPrint.Test/Roslyn.fs index 23fa17e..8781dd8 100644 --- a/WoofWare.PawPrint.Test/Roslyn.fs +++ b/WoofWare.PawPrint.Test/Roslyn.fs @@ -13,7 +13,7 @@ module Roslyn = let compile (sources : string list) : byte[] = // Create a syntax tree per source snippet. let parseOptions = - CSharpParseOptions.Default.WithLanguageVersion (LanguageVersion.Preview) + CSharpParseOptions.Default.WithLanguageVersion LanguageVersion.Preview let syntaxTrees : SyntaxTree[] = sources @@ -25,14 +25,13 @@ module Roslyn = // Reference every assembly found in the runtime directory – crude but // guarantees we can resolve System.* et al. - let runtimeDir = - System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory () + let runtimeDir = Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory () let metadataReferences : MetadataReference[] = Directory.GetFiles (runtimeDir, "*.dll") |> Array.map (fun path -> MetadataReference.CreateFromFile path :> MetadataReference) - let compilationOptions = CSharpCompilationOptions (OutputKind.ConsoleApplication) + let compilationOptions = CSharpCompilationOptions OutputKind.ConsoleApplication let compilation = CSharpCompilation.Create ( @@ -44,7 +43,7 @@ module Roslyn = use peStream = new MemoryStream () - let emitResult = compilation.Emit (peStream) + let emitResult = compilation.Emit peStream if emitResult.Success then peStream.ToArray () diff --git a/WoofWare.PawPrint.Test/TestCases.fs b/WoofWare.PawPrint.Test/TestCases.fs index 9d7e117..ceef97d 100644 --- a/WoofWare.PawPrint.Test/TestCases.fs +++ b/WoofWare.PawPrint.Test/TestCases.fs @@ -12,6 +12,22 @@ open WoofWare.PawPrint.Test module TestCases = let assy = typeof.Assembly + let unimplemented = + [ + { + FileName = "BasicException.cs" + ExpectedReturnCode = 10 + } + { + FileName = "WriteLine.cs" + ExpectedReturnCode = 10 + } + { + FileName = "BasicLock.cs" + ExpectedReturnCode = 10 + } + ] + let cases : TestCase list = [ { @@ -25,7 +41,7 @@ module TestCases = ] [] - let ``Can run a no-op`` (case : TestCase) : unit = + let ``Can evaluate C# files`` (case : TestCase) : unit = let source = Assembly.getEmbeddedResourceAsString case.FileName assy let image = Roslyn.compile [ source ] let messages, loggerFactory = LoggerFactory.makeTest () @@ -35,15 +51,54 @@ module TestCases = use peImage = new MemoryStream (image) - let terminalState, terminatingThread = - Program.run loggerFactory peImage dotnetRuntimes [] + try + let terminalState, terminatingThread = + Program.run loggerFactory peImage dotnetRuntimes [] - let exitCode = - match terminalState.ThreadState.[terminatingThread].MethodState.EvaluationStack.Values with - | [] -> failwith "expected program to return a value, but it returned void" - | head :: _ -> - match head with - | EvalStackValue.Int32 i -> i - | ret -> failwith "expected program to return an int, but it returned %O{ret}" + let exitCode = + match terminalState.ThreadState.[terminatingThread].MethodState.EvaluationStack.Values with + | [] -> failwith "expected program to return a value, but it returned void" + | head :: _ -> + match head with + | EvalStackValue.Int32 i -> i + | ret -> failwith $"expected program to return an int, but it returned %O{ret}" - exitCode |> shouldEqual case.ExpectedReturnCode + exitCode |> shouldEqual case.ExpectedReturnCode + + with _ -> + for message in messages () do + System.Console.Error.WriteLine $"{message}" + + reraise () + + [] + [] + let ``Can evaluate C# files (unimplemented)`` (case : TestCase) : unit = + let source = Assembly.getEmbeddedResourceAsString case.FileName assy + let image = Roslyn.compile [ source ] + let messages, loggerFactory = LoggerFactory.makeTest () + + let dotnetRuntimes = + DotnetRuntime.SelectForDll assy.Location |> ImmutableArray.CreateRange + + use peImage = new MemoryStream (image) + + try + let terminalState, terminatingThread = + Program.run loggerFactory peImage dotnetRuntimes [] + + let exitCode = + match terminalState.ThreadState.[terminatingThread].MethodState.EvaluationStack.Values with + | [] -> failwith "expected program to return a value, but it returned void" + | head :: _ -> + match head with + | EvalStackValue.Int32 i -> i + | ret -> failwith $"expected program to return an int, but it returned %O{ret}" + + exitCode |> shouldEqual case.ExpectedReturnCode + + with _ -> + for message in messages () do + System.Console.Error.WriteLine $"{message}" + + reraise () diff --git a/WoofWare.PawPrint.Test/WoofWare.PawPrint.Test.fsproj b/WoofWare.PawPrint.Test/WoofWare.PawPrint.Test.fsproj index 1d2f0a9..e441d28 100644 --- a/WoofWare.PawPrint.Test/WoofWare.PawPrint.Test.fsproj +++ b/WoofWare.PawPrint.Test/WoofWare.PawPrint.Test.fsproj @@ -19,8 +19,9 @@ - + + diff --git a/WoofWare.PawPrint.Test/sources/HelloWorld.cs b/WoofWare.PawPrint.Test/sources/BasicException.cs similarity index 88% rename from WoofWare.PawPrint.Test/sources/HelloWorld.cs rename to WoofWare.PawPrint.Test/sources/BasicException.cs index 6eeab91..f7a4aaa 100644 --- a/WoofWare.PawPrint.Test/sources/HelloWorld.cs +++ b/WoofWare.PawPrint.Test/sources/BasicException.cs @@ -6,7 +6,6 @@ namespace HelloWorldApp { static int ReallyMain(string[] args) { - Console.WriteLine("Hello, world!"); return 0; } diff --git a/WoofWare.PawPrint.Test/sources/WriteLine.cs b/WoofWare.PawPrint.Test/sources/WriteLine.cs new file mode 100644 index 0000000..b50979a --- /dev/null +++ b/WoofWare.PawPrint.Test/sources/WriteLine.cs @@ -0,0 +1,13 @@ +using System; + +namespace HelloWorldApp +{ + class Program + { + static int Main(string[] args) + { + Console.WriteLine("Hello, world!"); + return 0; + } + } +} diff --git a/WoofWare.PawPrint/AbstractMachine.fs b/WoofWare.PawPrint/AbstractMachine.fs index ccb8767..7b44e42 100644 --- a/WoofWare.PawPrint/AbstractMachine.fs +++ b/WoofWare.PawPrint/AbstractMachine.fs @@ -781,25 +781,6 @@ module IlMachineState = ) } - // TODO: which stack do we actually want to push to? - let pushToStackCoerced (o : EvalStackValue) (targetType : TypeDefn) (thread : ThreadId) (state : IlMachineState) = - { state with - ThreadState = - state.ThreadState - |> Map.change - thread - (fun threadState -> - match threadState with - | None -> failwith "Logic error: tried to push to stack of a nonexistent thread" - | Some threadState -> - { threadState with - ThreadState.MethodState = - threadState.MethodState |> MethodState.pushToEvalStack (failwith "TODO") - } - |> Some - ) - } - let popFromStackToLocalVariable (thread : ThreadId) (localVariableIndex : int) @@ -1018,26 +999,27 @@ module AbstractMachine = let state = match returnState.WasConstructingObj with - | None -> state | Some constructing -> + // Assumption: a constructor can't also return a value. state |> IlMachineState.pushToEvalStack (CliType.OfManagedObject constructing) currentThread + | None -> + match threadStateAtEndOfMethod.MethodState.EvaluationStack.Values with + | [] -> + // no return value + state + | [ retVal ] -> + let retType = + threadStateAtEndOfMethod.MethodState.ExecutingMethod.Signature.ReturnType - match threadStateAtEndOfMethod.MethodState.EvaluationStack.Values with - | [] -> - // no return value - (state, WhatWeDid.Executed) |> ExecutionResult.Stepped - | [ retVal ] -> - let retType = - threadStateAtEndOfMethod.MethodState.ExecutingMethod.Signature.ReturnType + let toPush = EvalStackValue.toCliTypeCoerced (CliType.zeroOf retType) retVal - state - |> IlMachineState.pushToStackCoerced retVal retType currentThread - |> Tuple.withRight WhatWeDid.Executed - |> ExecutionResult.Stepped - | _ -> - failwith - "Unexpected interpretation result has a local evaluation stack with more than one element on RET" + state |> IlMachineState.pushToEvalStack toPush currentThread + | _ -> + failwith + "Unexpected interpretation result has a local evaluation stack with more than one element on RET" + + state |> Tuple.withRight WhatWeDid.Executed |> ExecutionResult.Stepped | LdcI4_0 -> state