diff --git a/Colibri.Core/Macros/CoreMacros.cs b/Colibri.Core/Macros/CoreMacros.cs index 4d06552..9f98ae1 100644 --- a/Colibri.Core/Macros/CoreMacros.cs +++ b/Colibri.Core/Macros/CoreMacros.cs @@ -714,19 +714,22 @@ public static object DelayForce(ColibriRuntime runtime, Scope scope, object?[] a foreach (var arg in args) { - var fileName = arg?.ToString(); - - if (string.IsNullOrEmpty(fileName)) - { - throw new ArgumentException("File name given to include cannot be null or empty"); - } + result = IncludeFileInternal(runtime, scope, arg); + } - var fileText = File.ReadAllText(fileName); + return result; + } - result = runtime.EvaluateProgram(scope, fileText); + private static object? IncludeFileInternal(ColibriRuntime runtime, Scope scope, object? arg) + { + if (runtime.Evaluate(scope, arg) is not string fileName || string.IsNullOrEmpty(fileName)) + { + throw new ArgumentException("File name given to include cannot be null or empty"); } - return result; + var fileText = File.ReadAllText(fileName); + + return runtime.EvaluateProgram(scope, fileText); } public static object? Eval(ColibriRuntime runtime, Scope scope, object?[] args) @@ -1242,7 +1245,7 @@ public static object VectorMap(ColibriRuntime runtime, Scope scope, object?[] ar return result; } - public static object? Import(ColibriRuntime runtime, Scope scope, object?[] args) + public static object Import(ColibriRuntime runtime, Scope scope, object?[] args) { if (args.Length == 0) { @@ -1309,4 +1312,30 @@ private static bool RecursivelyParseCondExpandFeatureRequirement(Scope scope, ob _ => throw new ArgumentException($"Unexpected cond-expand clause feature identifier: {pairCar}") }; } + + public static object? Load(ColibriRuntime runtime, Scope scope, object?[] args) + { + if (args.Length is 0 or > 2) + { + throw new ArgumentException("load requires one or two arguments"); + } + + // per spec, by default it's the same as (interaction-environment) + var destScope = runtime.UserScope; + + if (args.Length == 2) + { + if (runtime.Evaluate(scope, args[1]) is not Scope argScope) + { + throw new ArgumentException("load's second argument must be a scope"); + } + + destScope = argScope; + } + + // HACK.PI: since we don't yet support compilation, load is basically the same thing as include, + // but it may be different in the future. The only difference is the support for the optional + // environment argument. + return IncludeFileInternal(runtime, destScope, args[0]); + } } \ No newline at end of file diff --git a/Colibri.Core/Macros/EnvironmentMacros.cs b/Colibri.Core/Macros/EnvironmentMacros.cs index bdfd83e..bea28db 100644 --- a/Colibri.Core/Macros/EnvironmentMacros.cs +++ b/Colibri.Core/Macros/EnvironmentMacros.cs @@ -6,6 +6,15 @@ public static object InteractionEnvironment(ColibriRuntime runtime, Scope scope, => runtime.UserScope; public static object Environment(ColibriRuntime runtime, Scope scope, object?[] args) + { + var newScope = CreateNewEnvironment(runtime, scope, args); + + newScope.Freeze(); + + return newScope; + } + + private static Scope CreateNewEnvironment(ColibriRuntime runtime, Scope scope, object?[] args) { var newScope = new Scope(scope.MaxStackDepth); @@ -17,12 +26,16 @@ public static object Environment(ColibriRuntime runtime, Scope scope, object?[] } var importSet = ImportSet.RecursivelyResolveImportSet(scope, argPair); - + runtime.ImportLibrary(newScope, importSet); } - - newScope.Freeze(); return newScope; } + + public static object CurrentEnvironment(ColibriRuntime runtime, Scope scope, object?[] args) + => scope; + + public static object MutableEnvironment(ColibriRuntime runtime, Scope scope, object?[] args) + => CreateNewEnvironment(runtime, scope, args); } \ No newline at end of file diff --git a/Colibri.Core/StandardLibraries.cs b/Colibri.Core/StandardLibraries.cs index 7170e2d..7b3d5d1 100644 --- a/Colibri.Core/StandardLibraries.cs +++ b/Colibri.Core/StandardLibraries.cs @@ -21,7 +21,7 @@ static StandardLibraries() (new LibraryName("scheme", "file"), File), (new LibraryName("scheme", "inexact"), Inexact), (new LibraryName("scheme", "lazy"), Lazy), - // TODO: (scheme load) library + (new LibraryName("scheme", "load"), Load), (new LibraryName("scheme", "process-context"), ProcessContext), (new LibraryName("scheme", "read"), Read), (new LibraryName("scheme", "repl"), Repl), @@ -423,7 +423,10 @@ static StandardLibraries() ["promise?"] = TypeExpressions.IsPromise, }); - // TODO: (scheme load) library + public static readonly Library Load = new(new Dictionary + { + ["load"] = (MacroExpression)CoreMacros.Load, + }); public static readonly Library ProcessContext = new(new Dictionary { @@ -463,7 +466,9 @@ static StandardLibraries() public static readonly Library ColibriBase = new(new Dictionary { - // in colibri-base.lisp + // in colibri-base.lisp: contains + ["current-environment"] = (MacroExpression)EnvironmentMacros.CurrentEnvironment, + ["mutable-environment"] = (MacroExpression)EnvironmentMacros.MutableEnvironment, }, additionalExports: new List { "contains" diff --git a/Colibri.Tests/LoadTests.cs b/Colibri.Tests/LoadTests.cs new file mode 100644 index 0000000..267ada4 --- /dev/null +++ b/Colibri.Tests/LoadTests.cs @@ -0,0 +1,52 @@ +using Colibri.Core; + +namespace Colibri.Tests; + +public class LoadTests +{ + [Fact] + public void LoadDefaultEnvironmentTest() + { + const string program = @" +(with-output-to-file ""test.txt"" + (lambda () + (display ""(define xyz 42)"") + (newline))) + +(load ""test.txt"") +(define result xyz) +(delete-file ""test.txt"") +result +"; + + var runtime = new ColibriRuntime(); + + var result = runtime.EvaluateProgram(program); + + Assert.Equal(42, result); + } + + [Fact] + public void LoadSpecifiedEnvironment() + { + // HACK.PI: uses (colibri base) method (mutable-environment) + const string program = @" +(with-output-to-file ""test.txt"" + (lambda () + (display ""(define xyz 42)"") + (newline))) + +(define e (mutable-environment '(scheme base))) +(load ""test.txt"" e) +(define result (eval 'xyz e)) +(delete-file ""test.txt"") +result +"; + + var runtime = new ColibriRuntime(); + + var result = runtime.EvaluateProgram(program); + + Assert.Equal(42, result); + } +} \ No newline at end of file diff --git a/Colibri/PromptConfig/PromptCompletions.cs b/Colibri/PromptConfig/PromptCompletions.cs index 0579256..092a33c 100644 --- a/Colibri/PromptConfig/PromptCompletions.cs +++ b/Colibri/PromptConfig/PromptCompletions.cs @@ -14,6 +14,8 @@ public static IReadOnlyList GetCompletionItems( // HACK.PI: this code is _highly_ inefficient var keys = runtime.UserScope.FlattenAllKeys(); var lexer = new ColibriLexer(new AntlrInputStream(text)); + + lexer.RemoveErrorListeners(); var tokens = lexer.GetAllTokens();