diff --git a/src/IxMilia.Lisp.Test/ExecutionControlTests.cs b/src/IxMilia.Lisp.Test/ExecutionControlTests.cs index 6c2833a..ec907d1 100644 --- a/src/IxMilia.Lisp.Test/ExecutionControlTests.cs +++ b/src/IxMilia.Lisp.Test/ExecutionControlTests.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using System.Linq; using System.Threading.Tasks; using Xunit; @@ -660,5 +661,32 @@ public async Task HandlerCaseCanInterceptAnErrorAfterSkippingALevel() Assert.NotNull(capturedError); Assert.Equal("some error", capturedError.Message); } + + [Fact] + public async Task LoadAndExecuteFile() + { + using var temporaryDirectory = new TemporaryDirectory(); + var previousWorkingDirectory = Environment.CurrentDirectory; + try + { + Environment.CurrentDirectory = temporaryDirectory.DirectoryPath; + await File.WriteAllTextAsync(Path.Combine(temporaryDirectory.DirectoryPath, "external-file.lisp"), """ + (setf some-other-value 42) + """); + var host = await CreateHostAsync(); + var executionState = host.CreateExecutionState(); + var evalResult = await host.EvalAsync("test.lisp", """ + (load "external-file.lisp") + (+ some-other-value 1) + """, + executionState); + EnsureNotError(evalResult.Value); + Assert.Equal(43, ((LispInteger)evalResult.Value).Value); + } + finally + { + Environment.CurrentDirectory = previousWorkingDirectory; + } + } } } diff --git a/src/IxMilia.Lisp.Test/TemporaryDirectory.cs b/src/IxMilia.Lisp.Test/TemporaryDirectory.cs new file mode 100644 index 0000000..e7838fd --- /dev/null +++ b/src/IxMilia.Lisp.Test/TemporaryDirectory.cs @@ -0,0 +1,29 @@ +using System; +using System.IO; + +namespace IxMilia.Lisp.Test +{ + public class TemporaryDirectory : IDisposable + { + public string DirectoryPath { get; } + + public TemporaryDirectory() + { + var parentDir = Path.GetDirectoryName(GetType().Assembly.Location)!; + var tempDirName = $"ixmilia-lisp-{Guid.NewGuid():d}"; + DirectoryPath = Path.Combine(parentDir, "test-data", tempDirName); + Directory.CreateDirectory(DirectoryPath); + } + + public void Dispose() + { + try + { + Directory.Delete(DirectoryPath, true); + } + catch + { + } + } + } +} diff --git a/src/IxMilia.Lisp/LispDefaultContext.cs b/src/IxMilia.Lisp/LispDefaultContext.cs index efe253c..ec40225 100644 --- a/src/IxMilia.Lisp/LispDefaultContext.cs +++ b/src/IxMilia.Lisp/LispDefaultContext.cs @@ -445,6 +445,29 @@ public Task Labels(LispHost host, LispExecutionState executionState, return Task.FromResult(result); } + [LispMacro("LOAD", Signature = "FILESPEC", Documentation = "Loads the file named by _FILESPEC_ into the environment.")] + public async Task Load(LispHost host, LispExecutionState executionState, LispObject[] args, CancellationToken cancellationToken) + { + // TODO: handle full version, not just a single path + if (args.Length == 1 && args[0] is LispString path) + { + var filePath = path.Value; + var fullPath = Path.Combine(Environment.CurrentDirectory, filePath); + if (!File.Exists(fullPath)) + { + executionState.ReportError(new LispError($"File \"{fullPath}\" does not exist"), insertPop: true); + return host.Nil; + } + + var content = File.ReadAllText(fullPath); + var result = await host.EvalAsync(fullPath, content, executionState, cancellationToken); + return result.Value; + } + + executionState.ReportError(new LispError("Expected a file path"), insertPop: true); + return host.Nil; + } + [LispMacro("DEFPACKAGE")] public Task DefPackage(LispHost host, LispExecutionState executionState, LispObject[] args, CancellationToken cancellationToken) {