diff --git a/CHANGELOG.md b/CHANGELOG.md index 4dda6b82e9..85a36ef39f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,12 +21,14 @@ - [Space-precedence does not apply to value-level operators][10597] - [Must specify `--repl` to enable debug server][10709] - [Improved parser error reporting and performance][10734] +- [Import all available libraries in `--repl` mode][10746] [10468]: https://github.com/enso-org/enso/pull/10468 [10535]: https://github.com/enso-org/enso/pull/10535 [10597]: https://github.com/enso-org/enso/pull/10597 [10709]: https://github.com/enso-org/enso/pull/10709 [10734]: https://github.com/enso-org/enso/pull/10734 +[10746]: https://github.com/enso-org/enso/pull/10746 #### Enso IDE diff --git a/engine/common/src/main/java/org/enso/common/MethodNames.java b/engine/common/src/main/java/org/enso/common/MethodNames.java index 8f3187fc38..a76ef6ccf5 100644 --- a/engine/common/src/main/java/org/enso/common/MethodNames.java +++ b/engine/common/src/main/java/org/enso/common/MethodNames.java @@ -9,6 +9,7 @@ public class MethodNames { public static final String REGISTER_MODULE = "register_module"; public static final String UNREGISTER_MODULE = "unregister_module"; public static final String COMPILE = "compile"; + public static final String LOCAL_LIBRARIES = "local_libraries"; } public static class Module { diff --git a/engine/polyglot-api/src/main/java/org/enso/polyglot/PolyglotContext.java b/engine/polyglot-api/src/main/java/org/enso/polyglot/PolyglotContext.java new file mode 100644 index 0000000000..1ae05343e1 --- /dev/null +++ b/engine/polyglot-api/src/main/java/org/enso/polyglot/PolyglotContext.java @@ -0,0 +1,57 @@ +package org.enso.polyglot; + +import java.io.File; +import java.io.IOException; +import org.enso.common.LanguageInfo; +import org.graalvm.polyglot.Context; +import org.graalvm.polyglot.Source; + +public record PolyglotContext(Context context) { + /** + * Evaluates provided code string as a new module. + * + * @param code the code to evaluate. + * @param moduleName the name for the newly parsed module. + * @return the module representing evaluated code. + */ + public Module evalModule(String code, String moduleName) { + var source = Source.newBuilder(LanguageInfo.ID, code, moduleName).buildLiteral(); + return new Module(context.eval(source)); + } + + /** + * Evaluates provided code file as a new module. + * + * @param codeFile the code to evaluate. + * @return the module representing evaluated code. + */ + public Module evalModule(File codeFile) throws IOException { + var source = Source.newBuilder(LanguageInfo.ID, codeFile).build(); + return new Module(context.eval(source)); + } + + /** + * Generates and evaluates default repl script for this context. + * + * @param mainMethodName name of the main method + * @return module representing evaluated code + */ + public Module evalReplModule(String mainMethodName) { + var replModuleName = "Internal_Repl_Module___"; + var sb = new StringBuilder(); + sb.append("import Standard.Base.Runtime.Debug\n"); + for (var libName : getTopScope().getLibraries()) { + sb.append("from ").append(libName).append(" import all\n"); + } + sb.append("\n"); + sb.append(mainMethodName).append(" = Debug.breakpoint"); + return evalModule(sb.toString(), replModuleName); + } + + /** + * @return the top scope of Enso execution context + */ + public TopScope getTopScope() { + return new TopScope(context.getBindings(LanguageInfo.ID)); + } +} diff --git a/engine/polyglot-api/src/main/scala/org/enso/polyglot/PolyglotContext.scala b/engine/polyglot-api/src/main/scala/org/enso/polyglot/PolyglotContext.scala deleted file mode 100644 index 5ea0992e97..0000000000 --- a/engine/polyglot-api/src/main/scala/org/enso/polyglot/PolyglotContext.scala +++ /dev/null @@ -1,40 +0,0 @@ -package org.enso.polyglot -import java.io.File -import org.enso.common.LanguageInfo - -import org.graalvm.polyglot.{Context, Source} - -/** Exposes language specific aliases for generic polyglot context operations. - * @param context the Graal polyglot context to use. - */ -class PolyglotContext(val context: Context) { - - /** Evaluates provided code string as a new module. - * - * @param code the code to evaluate. - * @param moduleName the name for the newly parsed module. - * @return the module representing evaluated code. - */ - def evalModule(code: String, moduleName: String): Module = { - val source = Source - .newBuilder(LanguageInfo.ID, code, moduleName) - .build() - new Module(context.eval(source)) - } - - /** Evaluates provided code file as a new module. - * - * @param codeFile the code to evaluate. - * @return the module representing evaluated code. - */ - def evalModule(codeFile: File): Module = { - val source = Source.newBuilder(LanguageInfo.ID, codeFile).build - new Module(context.eval(source)) - } - - /** @return the top scope of Enso execution context - */ - def getTopScope: TopScope = { - new TopScope(context.getBindings(LanguageInfo.ID)) - } -} diff --git a/engine/polyglot-api/src/main/scala/org/enso/polyglot/TopScope.scala b/engine/polyglot-api/src/main/scala/org/enso/polyglot/TopScope.scala index 3b3150f737..cee11f5a40 100644 --- a/engine/polyglot-api/src/main/scala/org/enso/polyglot/TopScope.scala +++ b/engine/polyglot-api/src/main/scala/org/enso/polyglot/TopScope.scala @@ -37,4 +37,7 @@ class TopScope(private val value: Value) { def compile(shouldCompileDependencies: Boolean): Unit = { value.invokeMember(COMPILE, shouldCompileDependencies) } + + def getLibraries(): Array[String] = + value.invokeMember(LOCAL_LIBRARIES).as(classOf[Array[String]]) } diff --git a/engine/runner/src/main/java/org/enso/runner/Main.java b/engine/runner/src/main/java/org/enso/runner/Main.java index 98864a28c5..cb9b51fcc3 100644 --- a/engine/runner/src/main/java/org/enso/runner/Main.java +++ b/engine/runner/src/main/java/org/enso/runner/Main.java @@ -818,7 +818,8 @@ public class Main { } private void runSingleFile( - PolyglotContext context, File file, java.util.List additionalArgs) { + PolyglotContext context, File file, java.util.List additionalArgs) + throws IOException { var mainModule = context.evalModule(file); runMain(mainModule, file, additionalArgs, "main"); } @@ -893,15 +894,6 @@ public class Main { boolean enableIrCaches, boolean enableStaticAnalysis) { var mainMethodName = "internal_repl_entry_point___"; - var dummySourceToTriggerRepl = - """ - from Standard.Base import all - import Standard.Base.Runtime.Debug - - $mainMethodName = Debug.breakpoint - """ - .replace("$mainMethodName", mainMethodName); - var replModuleName = "Internal_Repl_Module___"; var projectRoot = projectPath != null ? projectPath : ""; var options = Collections.singletonMap(DebugServerInfo.ENABLE_OPTION, "true"); @@ -917,7 +909,8 @@ public class Main { .disableLinting(true) .enableStaticAnalysis(enableStaticAnalysis) .build()); - var mainModule = context.evalModule(dummySourceToTriggerRepl, replModuleName); + + var mainModule = context.evalReplModule(mainMethodName); runMain(mainModule, null, Collections.emptyList(), mainMethodName); throw exitSuccess(); } diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/PackageRepository.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/PackageRepository.scala index eff0d52a83..4bf271c6c5 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/PackageRepository.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/PackageRepository.scala @@ -29,6 +29,10 @@ trait PackageRepository { libraryName: LibraryName ): Either[PackageRepository.Error, Unit] + /** Iterates over all installed libraries. + */ + def findAvailableLocalLibraries(): Seq[LibraryName] + /** Checks if the library has already been loaded */ def isPackageLoaded(libraryName: LibraryName): Boolean diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/runtime/ListLibrariesTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/runtime/ListLibrariesTest.java new file mode 100644 index 0000000000..c01dfe2b0e --- /dev/null +++ b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/runtime/ListLibrariesTest.java @@ -0,0 +1,52 @@ +package org.enso.interpreter.runtime; + +import static org.junit.Assert.assertTrue; + +import java.util.List; +import org.enso.common.LanguageInfo; +import org.enso.common.MethodNames; +import org.enso.polyglot.PolyglotContext; +import org.enso.test.utils.ContextUtils; +import org.graalvm.polyglot.Context; +import org.graalvm.polyglot.TypeLiteral; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +public class ListLibrariesTest { + private static Context ctx; + + @BeforeClass + public static void initCtx() { + ctx = ContextUtils.createDefaultContext(); + } + + @AfterClass + public static void closeCtx() { + ctx.close(); + } + + @Test + public void listLibraries() { + var b = ctx.getBindings(LanguageInfo.ID); + var libs = b.invokeMember(MethodNames.TopScope.LOCAL_LIBRARIES); + assertTrue("Array of lib names: " + libs, libs.hasArrayElements()); + var list = libs.as(new TypeLiteral>() {}); + assertTrue("At least five libs: " + list, list.size() >= 5); + + assertTrue("Base found " + list, list.contains("Standard.Base")); + assertTrue("Table found " + list, list.contains("Standard.Table")); + assertTrue("DB found " + list, list.contains("Standard.Database")); + assertTrue("AWS found " + list, list.contains("Standard.AWS")); + assertTrue("Geo found " + list, list.contains("Standard.Geo")); + } + + @Test + public void evaluateDefaultReplScript() { + var pc = new PolyglotContext(ctx); + final var fnName = "main_fn_name__"; + var module = pc.evalReplModule(fnName); + var result = module.evalExpression(fnName); + assertTrue("Returns Nothing", result.isNull()); + } +} diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/ReplTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/ReplTest.scala index 66227c76a1..7bcc1cda01 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/ReplTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/ReplTest.scala @@ -21,14 +21,16 @@ class ReplTest ): Unit = { "initialize properly" in { - val code = - """ - |import Standard.Base.Runtime.Debug - | - |main = Debug.breakpoint - |""".stripMargin - setSessionManager(executor => executor.exit()) - eval(code) + var counter = 0; + setSessionManager(executor => { + counter = counter + 1 + executor.exit() + }) + val mainFn = "my_main_fn__" + val replModule = + interpreterContext.executionContext.evalReplModule(mainFn) + replModule.evalExpression(mainFn) + counter shouldEqual 1 } "be able to execute arbitrary code in the caller scope" in { @@ -293,7 +295,7 @@ class ReplTest } eval(code) val errorMsg = - "Compile_Error.Error" + "Compile error: The name `undefined` could not be found." evalResult.left.value.getMessage shouldEqual errorMsg } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/TopLevelScope.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/TopLevelScope.java index 0dd5629983..1f5fb816e6 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/TopLevelScope.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/TopLevelScope.java @@ -117,6 +117,7 @@ public final class TopLevelScope implements EnsoObject { MethodNames.TopScope.CREATE_MODULE, MethodNames.TopScope.REGISTER_MODULE, MethodNames.TopScope.UNREGISTER_MODULE, + MethodNames.TopScope.LOCAL_LIBRARIES, MethodNames.TopScope.COMPILE); } @@ -165,6 +166,18 @@ public final class TopLevelScope implements EnsoObject { return context.getNothing(); } + @CompilerDirectives.TruffleBoundary + private static EnsoObject localLibraries(EnsoContext context) { + var seq = context.getTopScope().packageRepository.findAvailableLocalLibraries(); + String[] names = new String[seq.size()]; + var it = seq.iterator(); + var i = 0; + while (it.hasNext()) { + names[i++] = it.next().toString(); + } + return ArrayLikeHelpers.wrapStrings(names); + } + private static Object leakContext(EnsoContext context) { return context.asGuestValue(context); } @@ -186,22 +199,19 @@ public final class TopLevelScope implements EnsoObject { @Specialization static Object doInvoke(TopLevelScope scope, String member, Object[] arguments) throws UnknownIdentifierException, ArityException, UnsupportedTypeException { - switch (member) { - case MethodNames.TopScope.GET_MODULE: - return getModule(scope, arguments); - case MethodNames.TopScope.CREATE_MODULE: - return createModule(scope, arguments, EnsoContext.get(null)); - case MethodNames.TopScope.REGISTER_MODULE: - return registerModule(scope, arguments, EnsoContext.get(null)); - case MethodNames.TopScope.UNREGISTER_MODULE: - return unregisterModule(scope, arguments, EnsoContext.get(null)); - case MethodNames.TopScope.LEAK_CONTEXT: - return leakContext(EnsoContext.get(null)); - case MethodNames.TopScope.COMPILE: - return compile(arguments, EnsoContext.get(null)); - default: - throw UnknownIdentifierException.create(member); - } + return switch (member) { + case MethodNames.TopScope.GET_MODULE -> getModule(scope, arguments); + case MethodNames.TopScope.CREATE_MODULE -> createModule( + scope, arguments, EnsoContext.get(null)); + case MethodNames.TopScope.REGISTER_MODULE -> registerModule( + scope, arguments, EnsoContext.get(null)); + case MethodNames.TopScope.UNREGISTER_MODULE -> unregisterModule( + scope, arguments, EnsoContext.get(null)); + case MethodNames.TopScope.LEAK_CONTEXT -> leakContext(EnsoContext.get(null)); + case MethodNames.TopScope.LOCAL_LIBRARIES -> localLibraries(EnsoContext.get(null)); + case MethodNames.TopScope.COMPILE -> compile(arguments, EnsoContext.get(null)); + default -> throw UnknownIdentifierException.create(member); + }; } } @@ -218,6 +228,7 @@ public final class TopLevelScope implements EnsoObject { || member.equals(MethodNames.TopScope.REGISTER_MODULE) || member.equals(MethodNames.TopScope.UNREGISTER_MODULE) || member.equals(MethodNames.TopScope.LEAK_CONTEXT) + || member.equals(MethodNames.TopScope.LOCAL_LIBRARIES) || member.equals(MethodNames.TopScope.COMPILE); } diff --git a/engine/runtime/src/main/scala/org/enso/interpreter/runtime/DefaultPackageRepository.scala b/engine/runtime/src/main/scala/org/enso/interpreter/runtime/DefaultPackageRepository.scala index 5c7773944f..0a401b70dd 100644 --- a/engine/runtime/src/main/scala/org/enso/interpreter/runtime/DefaultPackageRepository.scala +++ b/engine/runtime/src/main/scala/org/enso/interpreter/runtime/DefaultPackageRepository.scala @@ -138,6 +138,10 @@ private class DefaultPackageRepository( go(component.name.split(LibraryName.separator)) } + override def findAvailableLocalLibraries(): Seq[LibraryName] = { + libraryProvider.findAvailableLocalLibraries() + } + /** @inheritdoc */ override def getModuleMap: PackageRepository.ModuleMap = loadedModules diff --git a/lib/java/test-utils/src/main/java/org/enso/test/utils/ProjectUtils.java b/lib/java/test-utils/src/main/java/org/enso/test/utils/ProjectUtils.java index 5eb9a183f9..abc9576cdd 100644 --- a/lib/java/test-utils/src/main/java/org/enso/test/utils/ProjectUtils.java +++ b/lib/java/test-utils/src/main/java/org/enso/test/utils/ProjectUtils.java @@ -84,7 +84,7 @@ prefer-local-libraries: true * main} method */ public static void testProjectRun( - Context.Builder ctxBuilder, Path projDir, Consumer resultConsumer) { + Context.Builder ctxBuilder, Path projDir, Consumer resultConsumer) throws IOException { if (!(projDir.toFile().exists() && projDir.toFile().isDirectory())) { throw new IllegalArgumentException( "Project directory " + projDir + " must already be created"); @@ -115,7 +115,8 @@ prefer-local-libraries: true * @param resultConsumer Any action that is to be evaluated on the result of running the {@code * main} method */ - public static void testProjectRun(Path projDir, Consumer resultConsumer) { + public static void testProjectRun(Path projDir, Consumer resultConsumer) + throws IOException { testProjectRun(ContextUtils.defaultContextBuilder(), projDir, resultConsumer); } diff --git a/lib/scala/library-manager/src/main/scala/org/enso/librarymanager/DefaultLibraryProvider.scala b/lib/scala/library-manager/src/main/scala/org/enso/librarymanager/DefaultLibraryProvider.scala index 264fd5a31f..94c67433ac 100644 --- a/lib/scala/library-manager/src/main/scala/org/enso/librarymanager/DefaultLibraryProvider.scala +++ b/lib/scala/library-manager/src/main/scala/org/enso/librarymanager/DefaultLibraryProvider.scala @@ -40,6 +40,11 @@ class DefaultLibraryProvider( private val logger = Logger[DefaultLibraryProvider] private val resolver = LibraryResolver(localLibraryProvider) + override def findAvailableLocalLibraries(): Seq[LibraryName] = { + val libs = edition.getAllDefinedLibraries + libs.keys.toSeq + } + /** Resolves the library version that should be used based on the * configuration and returns its location on the filesystem. * diff --git a/lib/scala/library-manager/src/main/scala/org/enso/librarymanager/ResolvingLibraryProvider.scala b/lib/scala/library-manager/src/main/scala/org/enso/librarymanager/ResolvingLibraryProvider.scala index b596de605b..86e6759f02 100644 --- a/lib/scala/library-manager/src/main/scala/org/enso/librarymanager/ResolvingLibraryProvider.scala +++ b/lib/scala/library-manager/src/main/scala/org/enso/librarymanager/ResolvingLibraryProvider.scala @@ -5,6 +5,9 @@ import org.enso.editions.{LibraryName, LibraryVersion} /** A helper class for resolving libraries. */ trait ResolvingLibraryProvider { + /** Finds available library names */ + def findAvailableLocalLibraries(): Seq[LibraryName] + /** Resolves which library version should be used and finds its path within * local libraries or the cache. *