Syntactic Java integration (#739)

This commit is contained in:
Marcin Kostrzewa 2020-05-12 16:40:03 +02:00 committed by GitHub
parent 817b2a9d11
commit e8ede5114e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 585 additions and 109 deletions

View File

@ -10,7 +10,7 @@ import org.enso.languageserver.boot
import org.enso.languageserver.boot.LanguageServerConfig
import org.enso.pkg.Package
import org.enso.polyglot.{LanguageInfo, Module, PolyglotContext}
import org.graalvm.polyglot.{PolyglotException, Value}
import org.graalvm.polyglot.Value
import scala.annotation.unused
import scala.util.Try
@ -206,16 +206,9 @@ object Main {
}
private def runMain(mainModule: Module): Value = {
try {
val mainCons = mainModule.getAssociatedConstructor
val mainFun = mainModule.getMethod(mainCons, "main")
mainFun.execute(mainCons.newInstance())
} catch {
case e: PolyglotException =>
System.err.println(e.getMessage)
exitFail()
throw new RuntimeException("Impossible to reach here.")
}
val mainCons = mainModule.getAssociatedConstructor
val mainFun = mainModule.getMethod(mainCons, "main")
mainFun.execute(mainCons.newInstance())
}
/**

View File

@ -1,11 +1,14 @@
package org.enso.interpreter.node.callable;
import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.CachedContext;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.NodeInfo;
import org.enso.interpreter.Language;
import org.enso.interpreter.runtime.Builtins;
import org.enso.interpreter.runtime.Context;
import org.enso.interpreter.runtime.callable.UnresolvedSymbol;
import org.enso.interpreter.runtime.callable.atom.Atom;
import org.enso.interpreter.runtime.callable.atom.AtomConstructor;
@ -75,10 +78,10 @@ public abstract class MethodResolverNode extends Node {
@Specialization(guards = "cachedSymbol == symbol")
Function resolveStringCached(
UnresolvedSymbol symbol,
String self,
@Cached("symbol") UnresolvedSymbol cachedSymbol,
@Cached("resolveMethodOnString(cachedSymbol)") Function function) {
UnresolvedSymbol symbol,
String self,
@Cached("symbol") UnresolvedSymbol cachedSymbol,
@Cached("resolveMethodOnString(cachedSymbol)") Function function) {
return function;
}
@ -100,6 +103,16 @@ public abstract class MethodResolverNode extends Node {
return function;
}
@Specialization(guards = {"cachedSymbol == symbol", "ctx.getEnvironment().isHostObject(target)"})
Function resolveHostCached(
UnresolvedSymbol symbol,
Object target,
@Cached("symbol") UnresolvedSymbol cachedSymbol,
@CachedContext(Language.class) Context ctx,
@Cached("buildHostResolver(cachedSymbol, ctx)") Function function) {
return function;
}
private Function ensureMethodExists(Function function, Object target, UnresolvedSymbol symbol) {
if (function == null) {
throw new MethodDoesNotExistException(target, symbol.getName(), this);
@ -118,7 +131,7 @@ public abstract class MethodResolverNode extends Node {
Function resolveMethodOnString(UnresolvedSymbol symbol) {
return ensureMethodExists(
symbol.resolveFor(getBuiltins().text(), getBuiltins().any()), "Text", symbol);
symbol.resolveFor(getBuiltins().text(), getBuiltins().any()), "Text", symbol);
}
Function resolveMethodOnFunction(UnresolvedSymbol symbol) {
@ -130,6 +143,14 @@ public abstract class MethodResolverNode extends Node {
return ensureMethodExists(symbol.resolveFor(getBuiltins().any()), "Error", symbol);
}
Function buildHostResolver(UnresolvedSymbol symbol, Context context) {
if (symbol.getName().equals("new")) {
return context.getBuiltins().getConstructorDispatch();
} else {
return context.getBuiltins().buildPolyglotMethodDispatch(symbol.getName());
}
}
boolean isValidAtomCache(
UnresolvedSymbol symbol,
UnresolvedSymbol cachedSymbol,

View File

@ -0,0 +1,78 @@
package org.enso.interpreter.node.expression.builtin.interop.syntax;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.interop.ArityException;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.interop.UnsupportedTypeException;
import com.oracle.truffle.api.nodes.NodeInfo;
import com.oracle.truffle.api.nodes.UnexpectedResultException;
import com.oracle.truffle.api.profiles.BranchProfile;
import org.enso.interpreter.Constants;
import org.enso.interpreter.Language;
import org.enso.interpreter.node.expression.builtin.BuiltinRootNode;
import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition;
import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.callable.function.FunctionSchema.CallStrategy;
import org.enso.interpreter.runtime.error.PanicException;
import org.enso.interpreter.runtime.state.Stateful;
import org.enso.interpreter.runtime.type.TypesGen;
@NodeInfo(shortName = "<new>", description = "Instantiates a polyglot constructor.")
public class ConstructorDispatchNode extends BuiltinRootNode {
private ConstructorDispatchNode(Language language) {
super(language);
}
private @Child InteropLibrary library =
InteropLibrary.getFactory().createDispatched(Constants.CacheSizes.BUILTIN_INTEROP_DISPATCH);
private final BranchProfile err = BranchProfile.create();
/**
* Creates a function wrapping this node.
*
* @param language the current language instance
* @return a function wrapping this node
*/
public static Function makeFunction(Language language) {
return Function.fromBuiltinRootNode(
new ConstructorDispatchNode(language),
CallStrategy.ALWAYS_DIRECT,
new ArgumentDefinition(0, "this", ArgumentDefinition.ExecutionMode.EXECUTE),
new ArgumentDefinition(1, "arguments", ArgumentDefinition.ExecutionMode.EXECUTE));
}
/**
* Executes the node.
*
* @param frame current execution frame.
* @return the result of converting input into a string.
*/
@Override
public Stateful execute(VirtualFrame frame) {
Object[] args = Function.ArgumentsHelper.getPositionalArguments(frame.getArguments());
Object cons = args[0];
Object state = Function.ArgumentsHelper.getState(frame.getArguments());
try {
Object[] arguments = TypesGen.expectVector(args[1]).getItems();
Object res = library.instantiate(cons, arguments);
return new Stateful(state, res);
} catch (UnsupportedMessageException
| ArityException
| UnsupportedTypeException
| UnexpectedResultException e) {
err.enter();
throw new PanicException(e.getMessage(), this);
}
}
/**
* Returns a language-specific name for this node.
*
* @return the name of this node
*/
@Override
public String getName() {
return "<new>";
}
}

View File

@ -0,0 +1,71 @@
package org.enso.interpreter.node.expression.builtin.interop.syntax;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.interop.*;
import com.oracle.truffle.api.nodes.NodeInfo;
import com.oracle.truffle.api.nodes.UnexpectedResultException;
import com.oracle.truffle.api.profiles.BranchProfile;
import org.enso.interpreter.Constants;
import org.enso.interpreter.Language;
import org.enso.interpreter.node.expression.builtin.BuiltinRootNode;
import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.error.PanicException;
import org.enso.interpreter.runtime.state.Stateful;
import org.enso.interpreter.runtime.type.TypesGen;
@NodeInfo(shortName = "<polyglot_dispatch>", description = "Invokes a polyglot method by name.")
public class MethodDispatchNode extends BuiltinRootNode {
private MethodDispatchNode(Language language) {
super(language);
}
private @Child InteropLibrary library =
InteropLibrary.getFactory().createDispatched(Constants.CacheSizes.BUILTIN_INTEROP_DISPATCH);
private final BranchProfile err = BranchProfile.create();
/**
* Creates an instance of this node.
*
* @param language the current language instance
* @return a function wrapping this node
*/
public static MethodDispatchNode build(Language language) {
return new MethodDispatchNode(language);
}
/**
* Executes the node.
*
* @param frame current execution frame.
* @return the result of converting input into a string.
*/
@Override
public Stateful execute(VirtualFrame frame) {
Object[] args = Function.ArgumentsHelper.getPositionalArguments(frame.getArguments());
Object callable = args[0];
Object state = Function.ArgumentsHelper.getState(frame.getArguments());
try {
String method = TypesGen.expectString(args[1]);
Object[] arguments = TypesGen.expectVector(args[2]).getItems();
Object res = library.invokeMember(callable, method, arguments);
return new Stateful(state, res);
} catch (UnsupportedMessageException
| ArityException
| UnsupportedTypeException
| UnexpectedResultException
| UnknownIdentifierException e) {
err.enter();
throw new PanicException(e.getMessage(), this);
}
}
/**
* Returns a language-specific name for this node.
*
* @return the name of this node
*/
@Override
public String getName() {
return "<polyglot_dispatch>";
}
}

View File

@ -0,0 +1,37 @@
package org.enso.interpreter.node.expression.constant;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.NodeInfo;
import org.enso.interpreter.node.ExpressionNode;
import org.enso.interpreter.runtime.callable.atom.AtomConstructor;
/** Represents a compile-time constant. */
@NodeInfo(shortName = "const", description = "Represents an arbitrary compile-time constant.")
public class ConstantObjectNode extends ExpressionNode {
private final Object object;
private ConstantObjectNode(Object object) {
this.object = object;
}
/**
* Creates an instance of this node.
*
* @param object the constant to represent
* @return a truffle node representing {@code constructor}
*/
public static ConstantObjectNode build(Object object) {
return new ConstantObjectNode(object);
}
/**
* Executes the type constructor definition.
*
* @param frame the frame to execute in
* @return the constructor of the type defined
*/
@Override
public Object executeGeneric(VirtualFrame frame) {
return object;
}
}

View File

@ -1,5 +1,7 @@
package org.enso.interpreter.runtime;
import com.oracle.truffle.api.RootCallTarget;
import com.oracle.truffle.api.Truffle;
import org.enso.interpreter.Language;
import org.enso.interpreter.node.expression.builtin.IfZeroNode;
import org.enso.interpreter.node.expression.builtin.debug.DebugBreakpointNode;
@ -10,6 +12,8 @@ import org.enso.interpreter.node.expression.builtin.error.PanicNode;
import org.enso.interpreter.node.expression.builtin.error.ThrowErrorNode;
import org.enso.interpreter.node.expression.builtin.function.ExplicitCallFunctionNode;
import org.enso.interpreter.node.expression.builtin.interop.generic.*;
import org.enso.interpreter.node.expression.builtin.interop.syntax.MethodDispatchNode;
import org.enso.interpreter.node.expression.builtin.interop.syntax.ConstructorDispatchNode;
import org.enso.interpreter.node.expression.builtin.io.NanoTimeNode;
import org.enso.interpreter.node.expression.builtin.io.PrintNode;
import org.enso.interpreter.node.expression.builtin.interop.java.*;
@ -25,7 +29,10 @@ import org.enso.interpreter.node.expression.builtin.text.AnyToTextNode;
import org.enso.interpreter.node.expression.builtin.text.ConcatNode;
import org.enso.interpreter.node.expression.builtin.text.JsonSerializeNode;
import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition;
import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo;
import org.enso.interpreter.runtime.callable.atom.AtomConstructor;
import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.callable.function.FunctionSchema;
import org.enso.interpreter.runtime.scope.ModuleScope;
import org.enso.pkg.QualifiedName;
@ -51,6 +58,10 @@ public class Builtins {
private final AtomConstructor syntaxError;
private final AtomConstructor compileError;
private final RootCallTarget interopDispatchRoot;
private final FunctionSchema interopDispatchSchema;
private final Function newInstanceFunction;
/**
* Creates an instance with builtin methods installed.
*
@ -138,6 +149,20 @@ public class Builtins {
scope.registerMethod(java, "add_to_class_path", AddToClassPathNode.makeFunction(language));
scope.registerMethod(java, "lookup_class", LookupClassNode.makeFunction(language));
interopDispatchRoot = Truffle.getRuntime().createCallTarget(MethodDispatchNode.build(language));
interopDispatchSchema =
new FunctionSchema(
FunctionSchema.CallStrategy.ALWAYS_DIRECT,
FunctionSchema.CallerFrameAccess.NONE,
new ArgumentDefinition[] {
new ArgumentDefinition(1, "this", ArgumentDefinition.ExecutionMode.EXECUTE),
new ArgumentDefinition(2, "method_name", ArgumentDefinition.ExecutionMode.EXECUTE),
new ArgumentDefinition(3, "arguments", ArgumentDefinition.ExecutionMode.EXECUTE)
},
new boolean[] {false, true, false},
new CallArgumentInfo[0]);
newInstanceFunction = ConstructorDispatchNode.makeFunction(language);
}
private AtomConstructor createPolyglot(Language language) {
@ -228,4 +253,20 @@ public class Builtins {
public Module getModule() {
return module;
}
/**
* Builds a function dispatching to a polyglot method call.
*
* @param method the name of the method this function will dispatch to.
* @return a function calling {@code method} with given arguments.
*/
public Function buildPolyglotMethodDispatch(String method) {
Object[] preAppliedArr = new Object[] {null, method, null};
return new Function(interopDispatchRoot, null, interopDispatchSchema, preAppliedArr, null);
}
/** @return a function executing a constructor with given arguments. */
public Function getConstructorDispatch() {
return newInstanceFunction;
}
}

View File

@ -30,7 +30,7 @@ public class Context {
private final Env environment;
private final Compiler compiler;
private final PrintStream out;
private List<Package> packages;
private final List<Package> packages;
/**
* Creates a new Enso context.
@ -53,6 +53,16 @@ public class Context {
.map(Optional::get)
.collect(Collectors.toList());
packages.forEach(
pkg -> {
List<File> classPathItems = ScalaConversions.asJava(pkg.listPolyglotExtensions("java"));
classPathItems.forEach(
cp -> {
TruffleFile f = getTruffleFile(cp);
getEnvironment().addToHostClassPath(f);
});
});
Map<String, Module> knownFiles =
packages.stream()
.flatMap(p -> ScalaConversions.asJava(p.listSources()).stream())

View File

@ -16,6 +16,7 @@ import org.enso.interpreter.runtime.error.RedefinedMethodException;
public class ModuleScope {
private final AtomConstructor associatedType;
private final Module module;
private Map<String, Object> polyglotSymbols = new HashMap<>();
private Map<String, AtomConstructor> constructors = new HashMap<>();
private Map<AtomConstructor, Map<String, Function>> methods = new HashMap<>();
private Set<ModuleScope> imports = new HashSet<>();
@ -113,6 +114,26 @@ public class ModuleScope {
}
}
/**
* Registers a new symbol in the polyglot namespace.
*
* @param name the name of the symbol
* @param sym the value being exposed
*/
public void registerPolyglotSymbol(String name, Object sym) {
polyglotSymbols.put(name, sym);
}
/**
* Looks up a polyglot symbol by name.
*
* @param name the name of the symbol being looked up
* @return the polyglot value registered for {@code name}, if exists.
*/
public Optional<Object> lookupPolyglotSymbol(String name) {
return Optional.ofNullable(polyglotSymbols.get(name));
}
/**
* Looks up the definition for a given type and method name.
*

View File

@ -85,11 +85,19 @@ object AstToIr {
val imports = presentBlocks.collect {
case AST.Import.any(list) => translateImport(list)
case AST.JavaImport.any(imp) =>
val pkg = imp.path.init.map(_.name)
val cls = imp.path.last.name
Module.Scope.Import.Polyglot(
Module.Scope.Import.Polyglot.Java(pkg.mkString("."), cls),
getIdentifiedLocation(imp)
)
}
val nonImportBlocks = presentBlocks.filter {
case AST.Import.any(_) => false
case _ => true
case AST.Import.any(_) => false
case AST.JavaImport.any(_) => false
case _ => true
}
val statements = nonImportBlocks.map(translateModuleSymbol)
@ -637,8 +645,8 @@ object AstToIr {
* @param imp the import to translate
* @return the [[Core]] representation of `imp`
*/
def translateImport(imp: AST.Import): Module.Scope.Import = {
Module.Scope.Import(
def translateImport(imp: AST.Import): Module.Scope.Import.Module = {
Module.Scope.Import.Module(
imp.path.map(t => t.name).reduceLeft((l, r) => l + "." + r),
getIdentifiedLocation(imp)
)

View File

@ -3,6 +3,7 @@ package org.enso.compiler.codegen
import com.oracle.truffle.api.Truffle
import com.oracle.truffle.api.source.{Source, SourceSection}
import org.enso.compiler.core.IR
import org.enso.compiler.core.IR.Module.Scope.Import
import org.enso.compiler.core.IR.{Error, IdentifiedLocation}
import org.enso.compiler.exception.{CompilerError, UnhandledEntity}
import org.enso.compiler.pass.analyse.AliasAnalysis.Graph.{Scope => AliasScope}
@ -26,6 +27,7 @@ import org.enso.interpreter.node.callable.{
}
import org.enso.interpreter.node.controlflow._
import org.enso.interpreter.node.expression.constant.{
ConstantObjectNode,
ConstructorNode,
DynamicSymbolNode,
ErrorNode
@ -136,9 +138,16 @@ class IRToTruffle(
}
// Register the imports in scope
imports.foreach(i =>
this.moduleScope.addImport(context.getCompiler.processImport(i.name))
)
imports.foreach {
case Import.Polyglot(Import.Polyglot.Java(pkg, cls), _, _, _) =>
val fullName = s"$pkg.$cls"
this.moduleScope.registerPolyglotSymbol(
cls,
context.getEnvironment.lookupHostSymbol(fullName)
)
case i: Import.Module =>
this.moduleScope.addImport(context.getCompiler.processImport(i.name))
}
// Register the atoms and their constructors in scope
val atomConstructors =
@ -542,14 +551,17 @@ class IRToTruffle(
)
.unsafeAs[AliasAnalysis.Info.Occurrence]
val slot = scope.getFramePointer(useInfo.id)
val atomCons = moduleScope.getConstructor(nameStr).toScala
val slot = scope.getFramePointer(useInfo.id)
val atomCons = moduleScope.getConstructor(nameStr).toScala
val polySymbol = moduleScope.lookupPolyglotSymbol(nameStr).toScala
if (nameStr == Constants.Names.CURRENT_MODULE) {
ConstructorNode.build(moduleScope.getAssociatedType)
} else if (slot.isDefined) {
ReadLocalVariableNode.build(slot.get)
} else if (atomCons.isDefined) {
ConstructorNode.build(atomCons.get)
} else if (polySymbol.isDefined) {
ConstantObjectNode.build(polySymbol.get)
} else {
DynamicSymbolNode.build(
UnresolvedSymbol.build(nameStr, moduleScope)

View File

@ -267,57 +267,136 @@ object IR {
}
object Scope {
/** An import statement.
*
* @param name the full `.`-separated path representing the import
* @param location the source location that the node corresponds to
* @param passData the pass metadata associated with this node
* @param diagnostics compiler diagnostics for this node
*/
sealed case class Import(
name: String,
override val location: Option[IdentifiedLocation],
override val passData: MetadataStorage = MetadataStorage(),
override val diagnostics: DiagnosticStorage = DiagnosticStorage()
) extends Scope
with IRKind.Primitive {
override protected var id: Identifier = randomId
/** Module-level import statements. */
sealed trait Import extends Scope {
override def mapExpressions(fn: Expression => Expression): Import
}
/** Creates a copy of `this`.
object Import {
/** An import statement.
*
* @param name the full `.`-separated path representing the import
* @param location the source location that the node corresponds to
* @param passData the pass metadata associated with this node
* @param diagnostics compiler diagnostics for this node
* @param id the identifier for the new node
* @return a copy of `this`, updated with the specified values
*/
def copy(
name: String = name,
location: Option[IdentifiedLocation] = location,
passData: MetadataStorage = passData,
diagnostics: DiagnosticStorage = diagnostics,
id: Identifier = id
): Import = {
val res = Import(name, location, passData, diagnostics)
res.id = id
res
sealed case class Module(
name: String,
override val location: Option[IdentifiedLocation],
override val passData: MetadataStorage = MetadataStorage(),
override val diagnostics: DiagnosticStorage = DiagnosticStorage()
) extends Import
with IRKind.Primitive {
override protected var id: Identifier = randomId
/** Creates a copy of `this`.
*
* @param name the full `.`-separated path representing the import
* @param location the source location that the node corresponds to
* @param passData the pass metadata associated with this node
* @param diagnostics compiler diagnostics for this node
* @param id the identifier for the new node
* @return a copy of `this`, updated with the specified values
*/
def copy(
name: String = name,
location: Option[IdentifiedLocation] = location,
passData: MetadataStorage = passData,
diagnostics: DiagnosticStorage = diagnostics,
id: Identifier = id
): Module = {
val res = Module(name, location, passData, diagnostics)
res.id = id
res
}
override def mapExpressions(
fn: Expression => Expression
): Module = this
override def toString: String =
s"""
|IR.Module.Scope.Import.Module(
|name = $name,
|location = $location,
|passData = ${this.showPassData},
|diagnostics = $diagnostics,
|id = $id
|)
|""".toSingleLine
override def children: List[IR] = List()
}
override def mapExpressions(fn: Expression => Expression): Import = this
object Polyglot {
override def toString: String =
s"""
|IR.Module.Scope.Import(
|name = $name,
|location = $location,
|passData = ${this.showPassData},
|diagnostics = $diagnostics,
|id = $id
|)
|""".toSingleLine
/** Represents language-specific polyglot import data. */
sealed trait Entity
override def children: List[IR] = List()
/** Represents an import of a Java class.
*
* @param packageName the name of the package containing the imported
* class
* @param className the class name
*/
case class Java(packageName: String, className: String) extends Entity
}
/** An import of a polyglot class.
*
* @param entity language-specific information on the imported entity
* @param location the source location that the node corresponds to
* @param passData the pass metadata associated with this node
* @param diagnostics compiler diagnostics for this node
*/
sealed case class Polyglot(
entity: Polyglot.Entity,
override val location: Option[IdentifiedLocation],
override val passData: MetadataStorage = MetadataStorage(),
override val diagnostics: DiagnosticStorage = DiagnosticStorage()
) extends Import
with IRKind.Primitive {
override protected var id: Identifier = randomId
/** Creates a copy of `this`.
*
* @param entity language-specific information on the imported entity
* @param location the source location that the node corresponds to
* @param passData the pass metadata associated with this node
* @param diagnostics compiler diagnostics for this node
* @param id the identifier for the new node
* @return a copy of `this`, updated with the specified values
*/
def copy(
entity: Polyglot.Entity,
location: Option[IdentifiedLocation] = location,
passData: MetadataStorage = passData,
diagnostics: DiagnosticStorage = diagnostics,
id: Identifier = id
): Polyglot = {
val res =
Polyglot(entity, location, passData, diagnostics)
res.id = id
res
}
override def mapExpressions(fn: Expression => Expression): Polyglot =
this
override def toString: String =
s"""
|IR.Module.Scope.Import.Polyglot(
|entity = $entity,
|location = $location,
|passData = ${this.showPassData},
|diagnostics = $diagnostics,
|id = $id
|)
|""".toSingleLine
override def children: List[IR] = List()
}
}
/** A representation of top-level definitions. */

View File

@ -3,13 +3,12 @@ package org.enso.interpreter.test.semantic
import org.enso.interpreter.test.InterpreterTest
class JavaInteropTest extends InterpreterTest {
"Java interop" should "allow calling static methods from java classes" in {
"Java interop" should "allow importing classes and calling methods on them" in {
val code =
"""
|main =
| class = Java.lookup_class "org.enso.example.TestClass"
| method = Polyglot.get_member class "add"
| Polyglot.execute method [1, 2]
|polyglot java import org.enso.example.TestClass
|
|main = TestClass.add [1, 2]
|""".stripMargin
eval(code) shouldEqual 3
@ -18,33 +17,12 @@ class JavaInteropTest extends InterpreterTest {
"Java interop" should "allow instantiating objects and calling methods on them" in {
val code =
"""
|polyglot java import org.enso.example.TestClass
|
|main =
| class = Java.lookup_class "org.enso.example.TestClass"
| instance = Polyglot.new class [x -> x * 2]
| Polyglot.invoke instance "callFunctionAndIncrement" [10]
| instance = TestClass.new [x -> x * 2]
| instance.callFunctionAndIncrement [10]
|""".stripMargin
eval(code) shouldEqual 21
}
"Java interop" should "allow listing available members of an object" in {
val code =
"""
|main =
| class = Java.lookup_class "org.enso.example.TestClass"
| instance = Polyglot.new class []
| members = Polyglot.get_members instance
| IO.println (Polyglot.get_array_size members)
| IO.println (Polyglot.get_array_element members 0)
| IO.println (Polyglot.get_array_element members 1)
| IO.println (Polyglot.get_array_element members 2)
|""".stripMargin
eval(code)
val count :: methods = consumeOut
count shouldEqual "3"
methods.toSet shouldEqual Set(
"method1",
"method2",
"callFunctionAndIncrement"
)
}
}

View File

@ -0,0 +1,50 @@
package org.enso.interpreter.test.semantic
import org.enso.interpreter.test.InterpreterTest
class PolyglotTest extends InterpreterTest {
"Polyglot" should "allow calling methods on static objects" in {
val code =
"""
|main =
| class = Java.lookup_class "org.enso.example.TestClass"
| method = Polyglot.get_member class "add"
| Polyglot.execute method [1, 2]
|""".stripMargin
eval(code) shouldEqual 3
}
"Polyglot" should "allow instantiating objects and calling methods on them" in {
val code =
"""
|main =
| class = Java.lookup_class "org.enso.example.TestClass"
| instance = Polyglot.new class [x -> x * 2]
| Polyglot.invoke instance "callFunctionAndIncrement" [10]
|""".stripMargin
eval(code) shouldEqual 21
}
"Polyglot" should "allow listing available members of an object" in {
val code =
"""
|main =
| class = Java.lookup_class "org.enso.example.TestClass"
| instance = Polyglot.new class []
| members = Polyglot.get_members instance
| IO.println (Polyglot.get_array_size members)
| IO.println (Polyglot.get_array_element members 0)
| IO.println (Polyglot.get_array_element members 1)
| IO.println (Polyglot.get_array_element members 2)
|""".stripMargin
eval(code)
val count :: methods = consumeOut
count shouldEqual "3"
methods.toSet shouldEqual Set(
"method1",
"method2",
"callFunctionAndIncrement"
)
}
}

View File

@ -29,9 +29,10 @@ case class SourceFile(qualifiedName: QualifiedName, file: File)
*/
case class Package(root: File, config: Config) {
val sourceDir = new File(root, Package.sourceDirName)
val configFile = new File(root, Package.configFileName)
val thumbFile = new File(root, Package.thumbFileName)
val sourceDir = new File(root, Package.sourceDirName)
val configFile = new File(root, Package.configFileName)
val thumbFile = new File(root, Package.thumbFileName)
val polyglotDir = new File(root, Package.polyglotExtensionsDirName)
/**
* Stores the package metadata on the hard drive. If the package does not exist,
@ -171,6 +172,24 @@ case class Package(root: File, config: Config) {
SourceFile(moduleNameForFile(path.toFile), path.toFile)
}
}
/**
* Lists contents of the polyglot extensions directory for a given language.
*
* @param languageName the language to list extenstions for
* @return a list of files and directories contained in the relevant
* polyglot extensions directory.
*/
def listPolyglotExtensions(languageName: String): List[File] = {
val dir = new File(polyglotDir, languageName)
if (!dir.isDirectory) return List()
Files
.list(dir.toPath)
.map(_.toFile)
.iterator()
.asScala
.toList
}
}
/**
@ -185,19 +204,19 @@ case class QualifiedName(path: List[String], module: String) {
(path :+ module).mkString(qualifiedNameSeparator)
/**
* Get the parent of this qualified name.
*
* @return the parent of this qualified name.
*/
* Get the parent of this qualified name.
*
* @return the parent of this qualified name.
*/
def getParent: Option[QualifiedName] =
path.lastOption.map(QualifiedName(path.init, _))
/**
* Create a child qualified name taking this name as a parent.
*
* @param name the name of a child node.
* @return a new qualified name based on this name.
*/
* Create a child qualified name taking this name as a parent.
*
* @param name the name of a child node.
* @return a new qualified name based on this name.
*/
def createChild(name: String): QualifiedName =
QualifiedName(path :+ module, name)
}
@ -231,6 +250,7 @@ object Package {
val fileExtension = "enso"
val configFileName = "package.yaml"
val sourceDirName = "src"
val polyglotExtensionsDirName = "polyglot"
val mainFileName = "Main.enso"
val thumbFileName = "thumb.png"
val qualifiedNameSeparator = "."

View File

@ -348,7 +348,8 @@ object Shape extends ShapeImplicit {
with Phantom
final case class Documented[T](doc: Doc, emptyLinesBetween: Int, ast: T)
extends SpacelessAST[T]
final case class Import[T](path: List1[AST.Cons]) extends SpacelessAST[T]
final case class Import[T](path: List1[AST.Cons]) extends SpacelessAST[T]
final case class JavaImport[T](path: List1[AST.Ident]) extends SpacelessAST[T]
final case class Mixfix[T](name: List1[AST.Ident], args: List1[T])
extends SpacelessAST[T]
final case class Group[T](body: Option[T]) extends SpacelessAST[T]
@ -958,6 +959,21 @@ object Shape extends ShapeImplicit {
implicit def span[T]: HasSpan[Import[T]] = _ => 0
}
object JavaImport {
implicit def ftor: Functor[JavaImport] = semi.functor
implicit def fold: Foldable[JavaImport] = semi.foldable
implicit def repr[T]: Repr[JavaImport[T]] =
t =>
R + ("polyglot java import " + t.path
.map(_.repr.build())
.toList
.mkString("."))
// FIXME: How to make it automatic for non-spaced AST?
implicit def ozip[T]: OffsetZip[JavaImport, T] = _.map(Index.Start -> _)
implicit def span[T]: HasSpan[JavaImport[T]] = _ => 0
}
object Mixfix {
implicit def ftor: Functor[Mixfix] = semi.functor
implicit def fold: Foldable[Mixfix] = semi.foldable
@ -1083,6 +1099,7 @@ sealed trait ShapeImplicit {
case s: Comment[T] => s.repr
case s: Documented[T] => s.repr
case s: Import[T] => s.repr
case s: JavaImport[T] => s.repr
case s: Mixfix[T] => s.repr
case s: Group[T] => s.repr
case s: SequenceLiteral[T] => s.repr
@ -1120,6 +1137,7 @@ sealed trait ShapeImplicit {
case s: Comment[T] => OffsetZip[Comment, T].zipWithOffset(s)
case s: Documented[T] => OffsetZip[Documented, T].zipWithOffset(s)
case s: Import[T] => OffsetZip[Import, T].zipWithOffset(s)
case s: JavaImport[T] => OffsetZip[JavaImport, T].zipWithOffset(s)
case s: Mixfix[T] => OffsetZip[Mixfix, T].zipWithOffset(s)
case s: Group[T] => OffsetZip[Group, T].zipWithOffset(s)
case s: SequenceLiteral[T] => OffsetZip[SequenceLiteral, T].zipWithOffset(s)
@ -1158,6 +1176,7 @@ sealed trait ShapeImplicit {
case s: Comment[T] => s.span()
case s: Documented[T] => s.span()
case s: Import[T] => s.span()
case s: JavaImport[T] => s.span()
case s: Mixfix[T] => s.span()
case s: Group[T] => s.span()
case s: SequenceLiteral[T] => s.span()
@ -2273,6 +2292,19 @@ object AST {
val any = UnapplyByType[Import]
}
//////////////////////////////////////////////////////////////////////////////
//// Java Import /////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
type JavaImport = ASTOf[Shape.JavaImport]
object JavaImport {
def apply(path: List1[Ident]): JavaImport = Shape.JavaImport[AST](path)
def unapply(t: AST): Option[List1[Ident]] =
Unapply[JavaImport].run(t => t.path)(t)
val any = UnapplyByType[JavaImport]
}
//////////////////////////////////////////////////////////////////////////////
//// Mixfix //////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////

View File

@ -76,6 +76,30 @@ object Builtin {
}
}
val polyglotJavaImport = {
val javaQualName =
Pattern.SepList(Pattern.Cons().or(Pattern.Var()), AST.Opr("."))
Definition(
Var("polyglot") -> Pattern.fromAST(Var("java")),
Var("import") -> javaQualName
) { ctx =>
ctx.body match {
case List(_, segments) =>
val nonDot: List[AST.Ident] =
segments.body.toStream.map(_.wrapped).collect {
case AST.Ident.Var.any(v) => v: AST.Ident
case AST.Ident.Cons.any(c) => c: AST.Ident
}
// The optional unwrap is safe by construction - the pattern
// guarantees at least one Var or Cons in the match result.
AST.JavaImport(
List1.fromListOption(nonDot).getOrElse(internalError)
)
case _ => internalError
}
}
}
val imp = Definition(
Var("import") -> Pattern
.SepList(Pattern.Cons(), AST.Opr("."): AST, "expected module name")
@ -266,6 +290,7 @@ object Builtin {
case_of,
if_then,
if_then_else,
polyglotJavaImport,
imp,
defn,
arrow,