Import all available libraries in --repl mode (#10746)

Continuation of REPL work (#10709): Import all available libraries when running `--repl`.
This commit is contained in:
Jaroslav Tulach 2024-08-07 07:36:39 +00:00 committed by GitHub
parent 0c55ee50cf
commit 71285e6ff8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 176 additions and 78 deletions

View File

@ -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

View File

@ -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 {

View File

@ -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));
}
}

View File

@ -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))
}
}

View File

@ -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]])
}

View File

@ -818,7 +818,8 @@ public class Main {
}
private void runSingleFile(
PolyglotContext context, File file, java.util.List<String> additionalArgs) {
PolyglotContext context, File file, java.util.List<String> 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();
}

View File

@ -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

View File

@ -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<List<String>>() {});
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());
}
}

View File

@ -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
}

View File

@ -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);
}

View File

@ -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

View File

@ -84,7 +84,7 @@ prefer-local-libraries: true
* main} method
*/
public static void testProjectRun(
Context.Builder ctxBuilder, Path projDir, Consumer<Value> resultConsumer) {
Context.Builder ctxBuilder, Path projDir, Consumer<Value> 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<Value> resultConsumer) {
public static void testProjectRun(Path projDir, Consumer<Value> resultConsumer)
throws IOException {
testProjectRun(ContextUtils.defaultContextBuilder(), projDir, resultConsumer);
}

View File

@ -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.
*

View File

@ -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.
*