mirror of
https://github.com/enso-org/enso.git
synced 2024-12-23 10:05:06 +03:00
Syntactic Java integration (#739)
This commit is contained in:
parent
817b2a9d11
commit
e8ede5114e
@ -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.")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
@ -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);
|
||||
@ -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,
|
||||
|
@ -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>";
|
||||
}
|
||||
}
|
@ -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>";
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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())
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -85,10 +85,18 @@ 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 AST.JavaImport.any(_) => false
|
||||
case _ => true
|
||||
}
|
||||
|
||||
@ -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)
|
||||
)
|
||||
|
@ -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 =
|
||||
@ -544,12 +553,15 @@ class IRToTruffle(
|
||||
|
||||
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)
|
||||
|
@ -267,6 +267,13 @@ object IR {
|
||||
}
|
||||
object Scope {
|
||||
|
||||
/** Module-level import statements. */
|
||||
sealed trait Import extends Scope {
|
||||
override def mapExpressions(fn: Expression => Expression): Import
|
||||
}
|
||||
|
||||
object Import {
|
||||
|
||||
/** An import statement.
|
||||
*
|
||||
* @param name the full `.`-separated path representing the import
|
||||
@ -274,12 +281,12 @@ object IR {
|
||||
* @param passData the pass metadata associated with this node
|
||||
* @param diagnostics compiler diagnostics for this node
|
||||
*/
|
||||
sealed case class Import(
|
||||
sealed case class Module(
|
||||
name: String,
|
||||
override val location: Option[IdentifiedLocation],
|
||||
override val passData: MetadataStorage = MetadataStorage(),
|
||||
override val diagnostics: DiagnosticStorage = DiagnosticStorage()
|
||||
) extends Scope
|
||||
) extends Import
|
||||
with IRKind.Primitive {
|
||||
override protected var id: Identifier = randomId
|
||||
|
||||
@ -298,17 +305,19 @@ object IR {
|
||||
passData: MetadataStorage = passData,
|
||||
diagnostics: DiagnosticStorage = diagnostics,
|
||||
id: Identifier = id
|
||||
): Import = {
|
||||
val res = Import(name, location, passData, diagnostics)
|
||||
): Module = {
|
||||
val res = Module(name, location, passData, diagnostics)
|
||||
res.id = id
|
||||
res
|
||||
}
|
||||
|
||||
override def mapExpressions(fn: Expression => Expression): Import = this
|
||||
override def mapExpressions(
|
||||
fn: Expression => Expression
|
||||
): Module = this
|
||||
|
||||
override def toString: String =
|
||||
s"""
|
||||
|IR.Module.Scope.Import(
|
||||
|IR.Module.Scope.Import.Module(
|
||||
|name = $name,
|
||||
|location = $location,
|
||||
|passData = ${this.showPassData},
|
||||
@ -320,6 +329,76 @@ object IR {
|
||||
override def children: List[IR] = List()
|
||||
}
|
||||
|
||||
object Polyglot {
|
||||
|
||||
/** Represents language-specific polyglot import data. */
|
||||
sealed trait Entity
|
||||
|
||||
/** 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. */
|
||||
sealed trait Definition extends Scope {
|
||||
override def mapExpressions(fn: Expression => Expression): Definition
|
||||
|
@ -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"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
)
|
||||
}
|
||||
}
|
@ -32,6 +32,7 @@ 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 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
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -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 = "."
|
||||
|
@ -349,6 +349,7 @@ object Shape extends ShapeImplicit {
|
||||
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 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 //////////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user