This commit is contained in:
Marcin Kostrzewa 2020-10-09 14:05:22 +02:00 committed by GitHub
parent 73c748c4e9
commit 05f4cc2e7c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
50 changed files with 1156 additions and 73 deletions

View File

@ -0,0 +1,6 @@
from Base import all
## Returns the method name of the method that could not be found.
No_Such_Method_Error.method_name : Text
No_Such_Method_Error.method_name =
Meta.get_unresolved_symbol_name this.symbol

View File

@ -2,14 +2,22 @@ import Base.List
import Base.Vector
import Base.Number.Extensions
import Base.Text.Extensions
import Base.System.File
import Base.Meta.Enso_Project
import Base.Error.Extensions
import Base.Polyglot.Java
from Builtins import Unit, Number, Integer, Any, True, False
from Builtins export all
from Base.Meta.Enso_Project export all
from Base.List export Nil, Cons
from Base.Vector export Vector
from Base.Number.Extensions export all hiding Math
from Base.Text.Extensions export Text
from Base.Error.Extensions export all
from Base.Polyglot.Java export all
export Base.System.File
## Represents a right-exclusive range of integer values.
type Range

View File

@ -0,0 +1,10 @@
import Builtins
from Base import all
## Returns the root directory of the project.
Builtins.Enso_Project.root : File.File
Builtins.Enso_Project.root = File.File this.prim_root_file
## Returns the root data directory of the project.
Builtins.Enso_Project.data : File.File
Builtins.Enso_Project.data = this.root / "data"

View File

@ -0,0 +1,8 @@
from Base import all
import Builtins
## Checks whether an object is an instance of a given class.
Builtins.Java.is_instance : Any -> Any -> Boolean
Builtins.Java.is_instance object class =
class_object = Polyglot.get_member class "class"
class_object.isInstance [object]

View File

@ -0,0 +1,272 @@
from Base import all
import Base.System.File.Option
export Base.System.File.Option
polyglot java import java.nio.file.NoSuchFileException
polyglot java import java.nio.file.AccessDeniedException
polyglot java import java.io.IOException
type File_Error
type No_Such_File_Error file
type Access_Denied_Error file
type Io_Error message
## PRIVATE
Utility method for rewrapping Java exceptions into Enso panics.
rethrow_java file exception =
case exception of
Polyglot_Error exc ->
if Java.is_instance exc NoSuchFileException then
Panic.throw (No_Such_File_Error file)
if Java.is_instance exc AccessDeniedException then
Panic.throw (Access_Denied_Error file)
if Java.is_instance exc IOException then
Panic.throw (Io_Error (exc.getMessage []))
Panic.throw exception
_ -> Panic.throw exception
## PRIVATE
Utility method for running an action with Java exceptions mapping.
handle_java_exceptions file ~action =
err = Panic.recover action
r = err.catch (here.rethrow_java file)
r
## PRIVATE
Utility method for closing primitive Java streams. Provided to avoid
accidental scope capture with `Managed_Resource` finalizers.
close_stream : Any -> Unit
close_stream stream =
stream.close []
Unit
## An output stream, allowing for interactive writing of contents into an
open file.
type Output_Stream
type Output_Stream file stream_resource
## Writes a vector of bytes into the file at the current stream position.
write_bytes : Vector -> Unit ! File_Error
write_bytes contents = Managed_Resource.with this.stream_resource java_stream->
here.handle_java_exceptions this.file <|
java_stream.write [contents.to_array]
java_stream.flush []
Unit
## Closes this stream.
Even though Streams are closed automatically upon garbage collection, it
is still advised to close streams manually if they are not used within
a bracket pattern.
close : Unit
close = Managed_Resource.finalize this.stream_resource
## An input stream, allowing for interactive reading of contents from an open
file.
type Input_Stream
type Input_Stream file stream_resource
## Reads all the bytes in this file into a vector of bytes.
read_all_bytes : Vector ! File_Error
read_all_bytes = Managed_Resource.with this.stream_resource java_stream->
here.handle_java_exceptions this.file <|
Vector.from_polyglot_array (java_stream.readAllBytes [])
## Reads _up to_ the provided number of bytes from the stream.
Makes a best-effort to read as many bytes as provided, however fewer
bytes may be read, if end of stream is encountered.
The length of the returned vector is the same as the number of bytes
read.
read_n_bytes : Integer -> Vector ! File_Error
read_n_bytes n = Managed_Resource.with this.stream_resource java_stream->
here.handle_java_exceptions this.file <|
bytes = java_stream.readNBytes [n]
Vector.from_polyglot_array bytes
## Reads the next byte from the stream.
The returned value is an integer in the range 0-255 representing the
next byte of input, or -1 if end of stream is reached.
read_byte : Integer ! File_Error
read_byte = Managed_Resource.with this.stream_resource java_stream->
here.handle_java_exceptions this.file <|
java_stream.read []
## Closes this stream.
Even though Streams are closed automatically upon garbage collection, it
is still advised to close streams manually if they are not used within
a bracket pattern.
close : Unit
close = Managed_Resource.finalize this.stream_resource
type File
type File prim_file
## Returns a new input stream for this file.
The returned stream should be closed as soon as it is not used anymore.
The `with_input_stream` method should be preferred whenever possible.
The `open_options` argument is a vector of `File.Option` objects,
describing the access properties of the created stream.
new_input_stream : Vector -> Input_Stream ! File_Error
new_input_stream open_options =
opts = open_options . map (_.to_java) . to_array
stream = here.handle_java_exceptions this <|
(this.prim_file.newInputStream [opts])
resource = Managed_Resource.register stream here.close_stream
Input_Stream this resource
## Returns a new output stream for this file.
The returned stream should be closed as soon as it is not used anymore.
The `with_output_stream` method should be preferred whenever possible.
The `open_options` argument is a vector of `File.Option` objects,
describing the access properties of the created stream.
new_output_stream : Vector -> Output_Stream ! File_Error
new_output_stream open_options =
opts = open_options . map (_.to_java) . to_array
stream = here.handle_java_exceptions this <|
this.prim_file.newOutputStream [opts]
resource = Managed_Resource.register stream here.close_stream
Output_Stream this resource
## Creates a new output stream for this file and runs the specified action
on it.
The created stream is automatically closed when `action` returns (even
if it returns exceptionally).
The `open_options` argument is a vector of `File.Option` objects,
describing the properties of the created stream.
with_output_stream : Vector -> (Output_Stream -> Any ! File_Error) -> Any ! File_Error
with_output_stream open_options action =
Resource.bracket (this.new_output_stream open_options) (_.close) action
## Creates a new input stream for this file and runs the specified action
on it.
The created stream is automatically closed when `action` returns (even
if it returns exceptionally).
The `open_options` argument is a vector of `File.Option` objects,
describing the properties of the created stream.
with_input_stream : Vector -> (Input_Stream -> Any ! File_Error) -> Any ! File_Error
with_input_stream open_options action =
Resource.bracket (this.new_input_stream open_options) (_.close) action
## Reads all bytes in this file into a byte vector.
read_bytes : Vector ! File_Error
read_bytes =
opts = [Option.Read]
bytes = this.with_input_stream opts (_.read_all_bytes)
bytes
## Reads the whole file into a `Text`, assuming UTF-8 content encoding.
read : Text ! File_Error
read =
bytes = this.read_bytes
Text.from_utf_8 bytes
## Appends a number of bytes at the end of this file.
append_bytes : Vector -> Unit ! File_Error
append_bytes contents =
opts = [Option.Append, Option.Create]
this.with_output_stream opts (_.write_bytes contents)
## Appends a UTF-8 encoded `Text` at the end of this file.
append : Text -> Unit ! File_Error
append contents = this.append_bytes contents.utf_8
## Writes a number of bytes into this file, removing any existing contents.
If the file does not exist, it will be created.
write_bytes : Vector -> Unit ! File_Error
write_bytes contents =
opts = [Option.Write, Option.Create, Option.Truncate_Existing]
this.with_output_stream opts (_.write_bytes contents)
Unit
## Writes a UTF-8 encoded `Text` into this file, removing any existing
contents.
If the file does not exist, it will be created.
write : Text -> Unit ! File_Error
write contents = this.write_bytes contents.utf_8
## Resolve a child file of the given file.
/ : (Text | File) -> File
/ subpath = case subpath of
File prim -> File (this.prim_file.resolve [prim])
_ -> File (this.prim_file.resolve [subpath])
## A text representation of this file.
to_text : Text
to_text = this.prim_file.to_text
## Checks whether the file exists.
exists : Boolean
exists = this.prim_file.exists []
## Checks whether the file exists and is a directory.
is_directory : Boolean
is_directory = this.prim_file.isDirectory []
## Checks whether the file exists and is a regular file.
is_regular_file : Boolean
is_regular_file = this.prim_file.isRegularFile []
## Resolves the parent filesystem node of this file.
parent : File
parent = File (this.prim_file.getParent [])
## Returns the path of this file.
path : Text
path = this.prim_file.getPath []
## Returns the name of this file.
name : Text
name = this.prim_file.getName []
## Converts this file to an equivalent file represented with an absolute
path.
absolute : File
absolute = File (this.prim_file.getAbsoluteFile [])
## Checks is this file's path is absolute.
is_absolute : Boolean
is_absolute = this.prim_file.isAbsolute []
## Normalizes the filepath.
normalize : File
normalize = File (this.prim_file.normalize [])
## Checks if this file has the same `path` as `that`.
== : File -> Boolean
== that = this.prim_file.isEqual [that.prim_file]
## Deletes the file.
If the file is a directory, it must be empty, otherwise a `Panic` will
be thrown.
delete : Unit ! File_Error
delete =
here.handle_java_exceptions this <|
this.prim_file.delete []
Unit
## Creates a new file object, pointing to the given path.
new : Text -> File
new path = File (Prim_Io.get_file path)
## Returns the current working directory (CWD) of the current program.
current_directory : File
current_directory = File (Prim_Io.get_cwd)

View File

@ -0,0 +1,49 @@
from Base import all
polyglot java import java.nio.file.StandardOpenOption
## Represents different options for opening file streams.
This is intended for more advanced users, wishing to obtain lower-level
file access. For most use cases, the default values used in the methods of
the `File` type should be preferred.
type Option
## If the file is opened for `Write` access then bytes will be written to
the end of the file rather than the beginning.
type Append
## Create a new file if it does not exist.
type Create
## Create a new file, failing if the file already exists.
type Create_New
## Delete the underlying file on closing the stream.
type Delete_On_Close
## Requires that every update to the file's content be written
synchronously to the underlying storage device.
type Dsync
## Open for read access.
type Read
## Sparse file.
type Sparse
## Requires that every update to the file's content or metadata be written
synchronously to the underlying storage device.
type Sync
## If the file already exists and is opened for `Write` access,
the original contents will be removed.
type Truncate_Existing
## Open file for write access.
type Write
## PRIVATE
Convert this object into a representation understandable by the JVM.
to_java = case this of
Append -> Polyglot.get_member StandardOpenOption "APPEND"
Create -> Polyglot.get_member StandardOpenOption "CREATE"
Create_New -> Polyglot.get_member StandardOpenOption "CREATE_NEW"
Delete_On_Close -> Polyglot.get_member StandardOpenOption "DELETE_ON_CLOSE"
Dsync -> Polyglot.get_member StandardOpenOption "DSYNC"
Read -> Polyglot.get_member StandardOpenOption "READ"
Sparse -> Polyglot.get_member StandardOpenOption "SPARSE"
Sync -> Polyglot.get_member StandardOpenOption "SYNC"
Truncate_Existing -> Polyglot.get_member StandardOpenOption "TRUNCATE_EXISTING"
Write -> Polyglot.get_member StandardOpenOption "WRITE"

View File

@ -29,6 +29,22 @@ type Assertion
Failure _ -> True
Pending -> False
type Verbs
type Verbs
start_with subject argument =
if subject.starts_with argument then Success else
here.fail (subject.to_text + " did not start with " + argument.to_text))
equal subject argument =
if subject == argument then Success else
msg = this.to_text + " did not equal " + that.to_text + "."
here.fail msg
be subject argument = this.equal subject argument
Any.should verb argument = Verbs.verb this argument
## Fail a test with the given message.
fail message = Panic.throw (Failure message)

View File

@ -103,3 +103,5 @@ Text.codepoints =
Text.from_codepoints : Vector -> Text
Text.from_codepoints codepoints = Text_Utils.from_codepoints [codepoints.to_array]
## Checks whether `this` starts with `prefix`.
Text.starts_with prefix = Text_Utils.starts_with [this, prefix]

View File

@ -161,6 +161,42 @@ type Vector
arr.set_at i (that.at i-this_len)
Vector arr
## When `this` is a vector of text values, concatenates all the values by
interspersing them with `separator`.
> Example
The following code returns "foo, bar, baz"
["foo", "bar", "baz"].join ", "
join : String -> Text
join separator =
if this.length == 0 then "" else
if this.length == 1 then this.at 0 else
this.at 0 + (1.upto this.length . fold "" acc-> i-> acc + separator + this.at i)
## Creates a new vector with the first `count` elements on the left of
`this` removed.
drop : Integer -> Vector
drop count = if count >= this.length then Vector.new 0 (x -> x) else
Vector.new (this.length - count) (i -> this.at i+count)
## Creates a new vector with the last `count` elements on the right of
`this` removed.
drop_right : Integer -> Vector
drop_right count = if count >= this.length then Vector.new 0 (x -> x) else
this.take (this.length - count)
## Creates a new vector, consisting of the first `count` elements on the
left of `this`.
take : Integer -> Vector
take count = if count >= this.length then this else
Vector.new count this.at
## Creates a new vector, consisting of the last `count` elements on the
right of `this`.
take_right : Integer -> Vector
take_right count = if count >= this.length then this else
this.drop (this.length - count)
## A builder type for Enso vectors.
A vector builder is a mutable data structure, that allows to gather a

View File

@ -41,8 +41,9 @@ My_Package
│ └── Sub_Module
│ ├── Helper.enso
│ └── Util.enso
└── visualization (optional)
└── ...
├── visualization (optional)
│ └── ...
└── data (optional)
```
### The `src` Directory
@ -84,6 +85,12 @@ used by the supported polyglot languages. The contents of each subdirectory is
specified on a per-language basis, in the
[polyglot documentation](../polyglot/README.md).
### The `data` Directory
The `data` directory contains any data files and resources that the user needs
quick access to. Allows referring to resource files in a location-independent
way, by using the `Enso_Project.data` method.
### The `package.yaml` File
`package.yaml` describes certain package metadata, such as its name, authors and

View File

@ -352,7 +352,7 @@ public abstract class MethodResolverNode extends Node {
CompilerDirectives.transferToInterpreter();
Context context = lookupContextReference(Language.class).get();
throw new PanicException(
context.getBuiltins().error().makeNoSuchMethodError(_this, symbol.getName()), this);
context.getBuiltins().error().makeNoSuchMethodError(_this, symbol), this);
}
@CompilerDirectives.TruffleBoundary
@ -432,6 +432,8 @@ public abstract class MethodResolverNode extends Node {
return context.getBuiltins().polyglot().getConstructorDispatch();
} else if (symbol.getName().equals("to_text")) {
return context.getBuiltins().polyglot().getPolyglotToTextFunction();
} else if (symbol.getName().equals("catch")) {
return symbol.resolveFor(context.getBuiltins().any());
} else {
return context.getBuiltins().polyglot().buildPolyglotMethodDispatch(symbol);
}
@ -454,7 +456,7 @@ public abstract class MethodResolverNode extends Node {
if (function == null) {
CompilerDirectives.transferToInterpreter();
throw new PanicException(
context.getBuiltins().error().makeNoSuchMethodError(_this, sym.getName()), this);
context.getBuiltins().error().makeNoSuchMethodError(_this, sym), this);
}
return function;
}

View File

@ -33,6 +33,10 @@ public class QualifiedAccessorNode extends RootNode {
*/
public Stateful execute(VirtualFrame frame) {
Object state = Function.ArgumentsHelper.getState(frame.getArguments());
return new Stateful(state, atomConstructor);
if (atomConstructor.getArity() == 0) {
return new Stateful(state, atomConstructor.newInstance());
} else {
return new Stateful(state, atomConstructor);
}
}
}

View File

@ -0,0 +1,11 @@
package org.enso.interpreter.node.expression.builtin.bool;
import com.oracle.truffle.api.nodes.Node;
import org.enso.interpreter.dsl.BuiltinMethod;
@BuiltinMethod(type = "Boolean", name = "==", description = "Computes the equality of two booleans")
public class EqualsNode extends Node {
boolean execute(boolean _this, boolean that) {
return _this == that;
}
}

View File

@ -0,0 +1,39 @@
package org.enso.interpreter.node.expression.builtin.bool;
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.profiles.ConditionProfile;
import org.enso.interpreter.Language;
import org.enso.interpreter.dsl.BuiltinMethod;
import org.enso.interpreter.dsl.MonadicState;
import org.enso.interpreter.node.callable.thunk.ThunkExecutorNode;
import org.enso.interpreter.runtime.Context;
import org.enso.interpreter.runtime.callable.argument.Thunk;
import org.enso.interpreter.runtime.state.Stateful;
@BuiltinMethod(
type = "Boolean",
name = "if_then",
alwaysDirect = false,
description = "Performs the standard if-then control flow operation.")
public abstract class IfThenNode extends Node {
private @Child ThunkExecutorNode leftThunkExecutorNode = ThunkExecutorNode.build();
private final ConditionProfile condProfile = ConditionProfile.createCountingProfile();
static IfThenNode build() {
return IfThenNodeGen.create();
}
abstract Stateful execute(@MonadicState Object state, boolean _this, Thunk if_true);
@Specialization
Stateful doExecute(
Object state, boolean _this, Thunk if_true, @CachedContext(Language.class) Context context) {
if (condProfile.profile(_this)) {
return leftThunkExecutorNode.executeThunk(if_true, state, true);
} else {
return new Stateful(state, context.getUnit().newInstance());
}
}
}

View File

@ -0,0 +1,35 @@
package org.enso.interpreter.node.expression.builtin.io;
import com.oracle.truffle.api.TruffleFile;
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 org.enso.interpreter.Language;
import org.enso.interpreter.dsl.BuiltinMethod;
import org.enso.interpreter.node.expression.builtin.text.util.ToJavaStringNode;
import org.enso.interpreter.runtime.Context;
import org.enso.interpreter.runtime.data.EnsoFile;
import org.enso.interpreter.runtime.data.text.Text;
@BuiltinMethod(
type = "Prim_Io",
name = "get_cwd",
description =
"A file corresponding to the current working directory.")
public abstract class GetCwdNode extends Node {
static GetCwdNode build() {
return GetCwdNodeGen.create();
}
abstract Object execute(Object _this);
@Specialization
Object doExecute(
Object _this,
@CachedContext(Language.class) Context ctx) {
TruffleFile file = ctx.getEnvironment().getCurrentWorkingDirectory();
EnsoFile ensoFile = new EnsoFile(file);
return ctx.getEnvironment().asGuestValue(ensoFile);
}
}

View File

@ -0,0 +1,44 @@
package org.enso.interpreter.node.expression.builtin.io;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.TruffleFile;
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.interop.InteropLibrary;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.nodes.Node;
import org.enso.interpreter.Language;
import org.enso.interpreter.dsl.BuiltinMethod;
import org.enso.interpreter.node.expression.builtin.text.util.ToJavaStringNode;
import org.enso.interpreter.runtime.Context;
import org.enso.interpreter.runtime.data.EnsoFile;
import org.enso.interpreter.runtime.data.text.Text;
import java.io.IOException;
import java.util.stream.Collectors;
@BuiltinMethod(
type = "Prim_Io",
name = "get_file",
description =
"Takes the text representation of a path and returns a TruffleFile corresponding to it.")
public abstract class GetFileNode extends Node {
static GetFileNode build() {
return GetFileNodeGen.create();
}
abstract Object execute(Object _this, Text path);
@Specialization
Object doGetFile(
Object _this,
Text path,
@CachedContext(Language.class) Context ctx,
@Cached("build()") ToJavaStringNode toJavaStringNode) {
String pathStr = toJavaStringNode.execute(path);
TruffleFile file = ctx.getEnvironment().getPublicTruffleFile(pathStr);
EnsoFile ensoFile = new EnsoFile(file);
return ctx.getEnvironment().asGuestValue(ensoFile);
}
}

View File

@ -0,0 +1,20 @@
package org.enso.interpreter.node.expression.builtin.meta;
import com.oracle.truffle.api.nodes.Node;
import org.enso.interpreter.dsl.BuiltinMethod;
import org.enso.interpreter.node.expression.builtin.text.util.ToJavaStringNode;
import org.enso.interpreter.runtime.callable.UnresolvedSymbol;
import org.enso.interpreter.runtime.data.text.Text;
import org.enso.interpreter.runtime.scope.ModuleScope;
@BuiltinMethod(
type = "Meta",
name = "create_unresolved_symbol",
description = "Creates a new unresolved symbol node")
public class CreateUnresolvedSymbolNode extends Node {
private @Child ToJavaStringNode toJavaStringNode = ToJavaStringNode.build();
UnresolvedSymbol execute(Object _this, Text name, ModuleScope scope) {
return UnresolvedSymbol.build(toJavaStringNode.execute(name), scope);
}
}

View File

@ -0,0 +1,16 @@
package org.enso.interpreter.node.expression.builtin.meta;
import com.oracle.truffle.api.nodes.Node;
import org.enso.interpreter.dsl.BuiltinMethod;
import org.enso.interpreter.runtime.callable.UnresolvedSymbol;
import org.enso.interpreter.runtime.data.text.Text;
@BuiltinMethod(
type = "Meta",
name = "get_unresolved_symbol_name",
description = "Gets the name of an unresolved symbol")
public class GetUnresolvedSymbolNameNode extends Node {
Text execute(Object _this, UnresolvedSymbol symbol) {
return Text.create(symbol.getName());
}
}

View File

@ -0,0 +1,17 @@
package org.enso.interpreter.node.expression.builtin.meta;
import com.oracle.truffle.api.nodes.Node;
import org.enso.interpreter.dsl.BuiltinMethod;
import org.enso.interpreter.runtime.callable.UnresolvedSymbol;
import org.enso.interpreter.runtime.data.text.Text;
import org.enso.interpreter.runtime.scope.ModuleScope;
@BuiltinMethod(
type = "Meta",
name = "get_unresolved_symbol_scope",
description = "Gets the scope of an unresolved symbol")
public class GetUnresolvedSymbolScopeNode extends Node {
ModuleScope execute(Object _this, UnresolvedSymbol symbol) {
return symbol.getScope();
}
}

View File

@ -0,0 +1,75 @@
package org.enso.interpreter.node.expression.builtin.resource;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.Node;
import org.enso.interpreter.dsl.BuiltinMethod;
import org.enso.interpreter.dsl.MonadicState;
import org.enso.interpreter.node.callable.InvokeCallableNode;
import org.enso.interpreter.node.callable.thunk.ThunkExecutorNode;
import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo;
import org.enso.interpreter.runtime.callable.argument.Thunk;
import org.enso.interpreter.runtime.state.Stateful;
/**
* The basic bracket construct for resource management.
*
* <p>Even though it could be implemented in Enso, using lower-level primitives, implementing it in
* Java gives us the best correctness guarantees, even in the presence of serious interpreter
* failures.
*/
@BuiltinMethod(
type = "Resource",
name = "bracket",
description =
"Takes a computation acquiring a resource, a function taking the resource and closing it,"
+ " and a function performing arbitrary operations on the resource. Ensures closing"
+ " the resource, even if an exception is raised in the computation.")
public abstract class BracketNode extends Node {
private @Child ThunkExecutorNode invokeConstructorNode = ThunkExecutorNode.build();
private @Child InvokeCallableNode invokeDestructorNode =
InvokeCallableNode.build(
new CallArgumentInfo[] {new CallArgumentInfo()},
InvokeCallableNode.DefaultsExecutionMode.EXECUTE,
InvokeCallableNode.ArgumentsExecutionMode.PRE_EXECUTED);
private @Child InvokeCallableNode invokeActionNode =
InvokeCallableNode.build(
new CallArgumentInfo[] {new CallArgumentInfo()},
InvokeCallableNode.DefaultsExecutionMode.EXECUTE,
InvokeCallableNode.ArgumentsExecutionMode.PRE_EXECUTED);
static BracketNode build() {
return BracketNodeGen.create();
}
abstract Stateful execute(
@MonadicState Object state,
VirtualFrame frame,
Object _this,
Thunk constructor,
Object destructor,
Object action);
@Specialization
Stateful doBracket(
Object state,
VirtualFrame frame,
Object _this,
Thunk constructor,
Object destructor,
Object action) {
Stateful resourceStateful = invokeConstructorNode.executeThunk(constructor, state, false);
Object resource = resourceStateful.getValue();
state = resourceStateful.getState();
try {
Stateful result = invokeActionNode.execute(action, frame, state, new Object[] {resource});
state = result.getState();
return result;
} finally {
invokeDestructorNode.execute(destructor, frame, state, new Object[] {resource});
}
}
}

View File

@ -1,4 +1,4 @@
package org.enso.interpreter.node.expression.builtin.managedResource;
package org.enso.interpreter.node.expression.builtin.resource;
import com.oracle.truffle.api.dsl.CachedContext;
import com.oracle.truffle.api.dsl.Specialization;

View File

@ -1,4 +1,4 @@
package org.enso.interpreter.node.expression.builtin.managedResource;
package org.enso.interpreter.node.expression.builtin.resource;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.dsl.CachedContext;

View File

@ -1,4 +1,4 @@
package org.enso.interpreter.node.expression.builtin.managedResource;
package org.enso.interpreter.node.expression.builtin.resource;
import com.oracle.truffle.api.dsl.CachedContext;
import com.oracle.truffle.api.dsl.Specialization;

View File

@ -1,4 +1,4 @@
package org.enso.interpreter.node.expression.builtin.managedResource;
package org.enso.interpreter.node.expression.builtin.resource;
import com.oracle.truffle.api.dsl.CachedContext;
import com.oracle.truffle.api.dsl.Specialization;

View File

@ -0,0 +1,40 @@
package org.enso.interpreter.node.expression.constant;
import com.oracle.truffle.api.TruffleFile;
import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.NodeInfo;
import com.oracle.truffle.api.nodes.RootNode;
import org.enso.interpreter.runtime.Context;
import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.data.EnsoFile;
import org.enso.interpreter.runtime.error.RuntimeError;
import org.enso.interpreter.runtime.state.Stateful;
import org.enso.pkg.Package;
import java.util.Optional;
@NodeInfo(description = "Returns a language-level representation of the given Enso project.")
public class EnsoProjectNode extends RootNode {
private final Object result;
public EnsoProjectNode(
TruffleLanguage<?> language, Context context, Optional<Package<TruffleFile>> pkgOpt) {
super(language);
if (pkgOpt.isPresent()) {
Package<TruffleFile> pkg = pkgOpt.get();
Object rootPath = context.getEnvironment().asGuestValue(new EnsoFile(pkg.root().normalize()));
result = context.getBuiltins().getEnsoProject().newInstance(rootPath);
} else {
result =
new RuntimeError(
context.getBuiltins().error().moduleNotInPackageError().newInstance());
}
}
@Override
public Stateful execute(VirtualFrame frame) {
Object state = Function.ArgumentsHelper.getState(frame.getArguments());
return new Stateful(state, result);
}
}

View File

@ -228,6 +228,23 @@ public class Context {
return getTopScope().getModule(moduleName);
}
/**
* Finds the package the provided module belongs to.
*
* @param module the module to find the package of
* @return {@code module}'s package, if exists
*/
public Optional<Package<TruffleFile>> getPackageOf(Module module) {
if (module.getSourceFile() == null) {
return Optional.empty();
}
return packages.stream()
.filter(
pkg ->
module.getSourceFile().getAbsoluteFile().startsWith(pkg.root().getAbsoluteFile()))
.findFirst();
}
/**
* Registers a new module corresponding to a given file.
*

View File

@ -304,6 +304,11 @@ public class Module implements TruffleObject {
isIndexed = indexed;
}
/** @return the source file of this module. */
public TruffleFile getSourceFile() {
return sourceFile;
}
/**
* Builds an IR stub for this module.
*

View File

@ -21,9 +21,11 @@ public class Bool {
bool = new AtomConstructor("Boolean", scope).initializeFields();
scope.registerConstructor(bool);
scope.registerMethod(bool, "if_then_else", IfThenElseMethodGen.makeFunction(language));
scope.registerMethod(bool, "if_then", IfThenMethodGen.makeFunction(language));
scope.registerMethod(bool, "to_text", ToTextMethodGen.makeFunction(language));
scope.registerMethod(bool, "&&", AndMethodGen.makeFunction(language));
scope.registerMethod(bool, "||", OrMethodGen.makeFunction(language));
scope.registerMethod(bool, "==", EqualsMethodGen.makeFunction(language));
scope.registerMethod(bool, "not", NotMethodGen.makeFunction(language));
tru = new AtomConstructor("True", scope).initializeFields();
scope.registerConstructor(tru);

View File

@ -11,9 +11,7 @@ import org.enso.interpreter.node.expression.builtin.function.ApplicationOperator
import org.enso.interpreter.node.expression.builtin.function.ExplicitCallFunctionMethodGen;
import org.enso.interpreter.node.expression.builtin.interop.java.AddToClassPathMethodGen;
import org.enso.interpreter.node.expression.builtin.interop.java.LookupClassMethodGen;
import org.enso.interpreter.node.expression.builtin.io.PrintErrMethodGen;
import org.enso.interpreter.node.expression.builtin.io.PrintlnMethodGen;
import org.enso.interpreter.node.expression.builtin.io.ReadlnMethodGen;
import org.enso.interpreter.node.expression.builtin.io.*;
import org.enso.interpreter.node.expression.builtin.runtime.GCMethodGen;
import org.enso.interpreter.node.expression.builtin.runtime.NoInlineMethodGen;
import org.enso.interpreter.node.expression.builtin.state.GetStateMethodGen;
@ -48,13 +46,15 @@ public class Builtins {
private final Number number;
private final AtomConstructor function;
private final AtomConstructor debug;
private final AtomConstructor ensoProject;
private final Text text;
private final Error error;
private final Bool bool;
private final System system;
private final Array array;
private final Polyglot polyglot;
private final ManagedResource managedResource;
private final Resource resource;
private final Meta meta;
/**
* Creates an instance with builtin methods installed.
@ -74,10 +74,16 @@ public class Builtins {
function = new AtomConstructor("Function", scope).initializeFields();
text = new Text(language, scope);
debug = new AtomConstructor("Debug", scope).initializeFields();
ensoProject =
new AtomConstructor("Enso_Project", scope)
.initializeFields(
new ArgumentDefinition(
0, "prim_root_file", ArgumentDefinition.ExecutionMode.EXECUTE));
system = new System(language, scope);
number = new Number(language, scope);
polyglot = new Polyglot(language, scope);
managedResource = new ManagedResource(language, scope);
resource = new Resource(language, scope);
meta = new Meta(language, scope);
AtomConstructor nil = new AtomConstructor("Nil", scope).initializeFields();
AtomConstructor cons =
@ -86,6 +92,7 @@ public class Builtins {
new ArgumentDefinition(0, "head", ArgumentDefinition.ExecutionMode.EXECUTE),
new ArgumentDefinition(1, "tail", ArgumentDefinition.ExecutionMode.EXECUTE));
AtomConstructor io = new AtomConstructor("IO", scope).initializeFields();
AtomConstructor primIo = new AtomConstructor("Prim_Io", scope).initializeFields();
AtomConstructor runtime = new AtomConstructor("Runtime", scope).initializeFields();
AtomConstructor panic = new AtomConstructor("Panic", scope).initializeFields();
AtomConstructor error = new AtomConstructor("Error", scope).initializeFields();
@ -102,10 +109,12 @@ public class Builtins {
scope.registerConstructor(cons);
scope.registerConstructor(nil);
scope.registerConstructor(io);
scope.registerConstructor(primIo);
scope.registerConstructor(panic);
scope.registerConstructor(error);
scope.registerConstructor(state);
scope.registerConstructor(debug);
scope.registerConstructor(ensoProject);
scope.registerConstructor(runtime);
scope.registerConstructor(java);
@ -116,6 +125,8 @@ public class Builtins {
scope.registerMethod(io, "println", PrintlnMethodGen.makeFunction(language));
scope.registerMethod(io, "print_err", PrintErrMethodGen.makeFunction(language));
scope.registerMethod(io, "readln", ReadlnMethodGen.makeFunction(language));
scope.registerMethod(primIo, "get_file", GetFileMethodGen.makeFunction(language));
scope.registerMethod(primIo, "get_cwd", GetCwdMethodGen.makeFunction(language));
scope.registerMethod(runtime, "no_inline", NoInlineMethodGen.makeFunction(language));
scope.registerMethod(runtime, "gc", GCMethodGen.makeFunction(language));
@ -213,6 +224,11 @@ public class Builtins {
return debug;
}
/** @return the {@code Enso_Project} atom constructor */
public AtomConstructor getEnsoProject() {
return ensoProject;
}
/** @return the {@code System} atom constructor. */
public System system() {
return system;

View File

@ -1,10 +1,10 @@
package org.enso.interpreter.runtime.builtin;
import org.enso.interpreter.Language;
import org.enso.interpreter.runtime.callable.UnresolvedSymbol;
import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition;
import org.enso.interpreter.runtime.callable.atom.Atom;
import org.enso.interpreter.runtime.callable.atom.AtomConstructor;
import org.enso.interpreter.runtime.data.text.Text;
import org.enso.interpreter.runtime.scope.ModuleScope;
/** Container for builtin Error types */
@ -15,6 +15,7 @@ public class Error {
private final AtomConstructor uninitializedState;
private final AtomConstructor noSuchMethodError;
private final AtomConstructor polyglotError;
private final AtomConstructor moduleNotInPackageError;
/**
* Creates and registers the relevant constructors.
@ -43,11 +44,13 @@ public class Error {
new AtomConstructor("No_Such_Method_Error", scope)
.initializeFields(
new ArgumentDefinition(0, "target", ArgumentDefinition.ExecutionMode.EXECUTE),
new ArgumentDefinition(1, "method_name", ArgumentDefinition.ExecutionMode.EXECUTE));
new ArgumentDefinition(1, "symbol", ArgumentDefinition.ExecutionMode.EXECUTE));
polyglotError =
new AtomConstructor("Polyglot_Error", scope)
.initializeFields(
new ArgumentDefinition(0, "cause", ArgumentDefinition.ExecutionMode.EXECUTE));
moduleNotInPackageError =
new AtomConstructor("Module_Not_In_Package_Error", scope).initializeFields();
scope.registerConstructor(syntaxError);
scope.registerConstructor(compileError);
@ -55,6 +58,7 @@ public class Error {
scope.registerConstructor(uninitializedState);
scope.registerConstructor(noSuchMethodError);
scope.registerConstructor(polyglotError);
scope.registerConstructor(moduleNotInPackageError);
}
/** @return the builtin {@code Syntax_Error} atom constructor. */
@ -77,15 +81,20 @@ public class Error {
return uninitializedState;
}
/** @return the builtin {@code Module_Not_In_Package_Error} atom constructor. */
public AtomConstructor moduleNotInPackageError() {
return moduleNotInPackageError;
}
/**
* Creates an instance of the runtime representation of a {@code No_Such_Method_Error}.
*
* @param target the method call target
* @param name the method name
* @param symbol the method being called
* @return a runtime representation of the error
*/
public Atom makeNoSuchMethodError(Object target, String name) {
return noSuchMethodError.newInstance(target, Text.create(name));
public Atom makeNoSuchMethodError(Object target, UnresolvedSymbol symbol) {
return noSuchMethodError.newInstance(target, symbol);
}
/**

View File

@ -1,28 +0,0 @@
package org.enso.interpreter.runtime.builtin;
import org.enso.interpreter.Language;
import org.enso.interpreter.node.expression.builtin.managedResource.FinalizeMethodGen;
import org.enso.interpreter.node.expression.builtin.managedResource.RegisterMethodGen;
import org.enso.interpreter.node.expression.builtin.managedResource.TakeMethodGen;
import org.enso.interpreter.node.expression.builtin.managedResource.WithMethodGen;
import org.enso.interpreter.runtime.callable.atom.AtomConstructor;
import org.enso.interpreter.runtime.scope.ModuleScope;
/** Container for builtin Managed_Resource types */
public class ManagedResource {
/**
* Creates and registers the relevant constructors.
*
* @param language the current language instance.
* @param scope the scope to register constructors in.
*/
public ManagedResource(Language language, ModuleScope scope) {
AtomConstructor resource = new AtomConstructor("Managed_Resource", scope).initializeFields();
scope.registerConstructor(resource);
scope.registerMethod(resource, "register", RegisterMethodGen.makeFunction(language));
scope.registerMethod(resource, "with", WithMethodGen.makeFunction(language));
scope.registerMethod(resource, "take", TakeMethodGen.makeFunction(language));
scope.registerMethod(resource, "finalize", FinalizeMethodGen.makeFunction(language));
}
}

View File

@ -0,0 +1,34 @@
package org.enso.interpreter.runtime.builtin;
import org.enso.interpreter.Language;
import org.enso.interpreter.node.expression.builtin.meta.CreateUnresolvedSymbolMethodGen;
import org.enso.interpreter.node.expression.builtin.meta.GetUnresolvedSymbolNameMethodGen;
import org.enso.interpreter.node.expression.builtin.meta.GetUnresolvedSymbolScopeMethodGen;
import org.enso.interpreter.runtime.callable.atom.AtomConstructor;
import org.enso.interpreter.runtime.scope.ModuleScope;
/** Container for builtin Meta programming capabilities */
public class Meta {
/**
* Creates and registers the relevant constructors.
*
* @param language the current language instance.
* @param scope the scope to register constructors in.
*/
public Meta(Language language, ModuleScope scope) {
AtomConstructor meta = new AtomConstructor("Meta", scope).initializeFields();
scope.registerConstructor(meta);
scope.registerMethod(
meta,
"get_unresolved_symbol_name",
GetUnresolvedSymbolNameMethodGen.makeFunction(language));
scope.registerMethod(
meta,
"get_unresolved_symbol_scope",
GetUnresolvedSymbolScopeMethodGen.makeFunction(language));
scope.registerMethod(
meta, "create_unresolved_symbol", CreateUnresolvedSymbolMethodGen.makeFunction(language));
}
}

View File

@ -0,0 +1,29 @@
package org.enso.interpreter.runtime.builtin;
import org.enso.interpreter.Language;
import org.enso.interpreter.node.expression.builtin.resource.*;
import org.enso.interpreter.runtime.callable.atom.AtomConstructor;
import org.enso.interpreter.runtime.scope.ModuleScope;
/** Container for builtin Managed_Resource types */
public class Resource {
/**
* Creates and registers the relevant constructors.
*
* @param language the current language instance.
* @param scope the scope to register constructors in.
*/
public Resource(Language language, ModuleScope scope) {
AtomConstructor managedResource =
new AtomConstructor("Managed_Resource", scope).initializeFields();
scope.registerConstructor(managedResource);
AtomConstructor resource = new AtomConstructor("Resource", scope).initializeFields();
scope.registerConstructor(resource);
scope.registerMethod(resource, "bracket", BracketMethodGen.makeFunction(language));
scope.registerMethod(managedResource, "register", RegisterMethodGen.makeFunction(language));
scope.registerMethod(managedResource, "with", WithMethodGen.makeFunction(language));
scope.registerMethod(managedResource, "take", TakeMethodGen.makeFunction(language));
scope.registerMethod(managedResource, "finalize", FinalizeMethodGen.makeFunction(language));
}
}

View File

@ -70,6 +70,11 @@ public class UnresolvedSymbol implements TruffleObject {
return "UnresolvedSymbol<" + this.name + ">";
}
@ExportMessage
String toDisplayString(boolean allowSideEffects) {
return this.toString();
}
/**
* Creates an instance of this node.
*

View File

@ -0,0 +1,85 @@
package org.enso.interpreter.runtime.data;
import com.oracle.truffle.api.TruffleFile;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.OpenOption;
/**
* A wrapper for {@link TruffleFile} objects exposed to the language. For methods documentation
* please refer to {@link TruffleFile}.
*/
public class EnsoFile {
private final TruffleFile truffleFile;
public EnsoFile(TruffleFile truffleFile) {
this.truffleFile = truffleFile;
}
public OutputStream newOutputStream(OpenOption[] opts) throws IOException {
return truffleFile.newOutputStream(opts);
}
public InputStream newInputStream(OpenOption[] opts) throws IOException {
return truffleFile.newInputStream(opts);
}
public EnsoFile resolve(String subPath) {
return new EnsoFile(this.truffleFile.resolve(subPath));
}
public EnsoFile resolve(EnsoFile subPath) {
return new EnsoFile(this.truffleFile.resolve(subPath.truffleFile.getPath()));
}
public boolean exists() {
return truffleFile.exists();
}
public EnsoFile getParent() {
return new EnsoFile(this.truffleFile.getParent());
}
public EnsoFile getAbsoluteFile() {
return new EnsoFile(this.truffleFile.getAbsoluteFile());
}
public String getPath() {
return this.truffleFile.getPath();
}
public boolean isAbsolute() {
return this.truffleFile.isAbsolute();
}
public boolean isDirectory() {
return this.truffleFile.isDirectory();
}
public boolean isRegularFile() {
return this.truffleFile.isRegularFile();
}
public String getName() {
return this.truffleFile.getName();
}
public boolean isEqual(EnsoFile that) {
return this.truffleFile.equals(that.truffleFile);
}
public EnsoFile normalize() {
return new EnsoFile(truffleFile.normalize());
}
public void delete() throws IOException {
truffleFile.delete();
}
@Override
public String toString() {
return "(File " + truffleFile.getPath() + ")";
}
}

View File

@ -7,13 +7,15 @@ import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import com.oracle.truffle.api.interop.TruffleObject;
import org.enso.interpreter.runtime.Module;
import org.enso.interpreter.runtime.callable.atom.AtomConstructor;
import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.error.RedefinedMethodException;
/** A representation of Enso's per-file top-level scope. */
public class ModuleScope {
public class ModuleScope implements TruffleObject {
private final AtomConstructor associatedType;
private final Module module;
private Map<String, Object> polyglotSymbols = new HashMap<>();

View File

@ -15,6 +15,7 @@ import org.enso.interpreter.runtime.data.ManagedResource;
import org.enso.interpreter.runtime.data.text.Text;
import org.enso.interpreter.runtime.error.RuntimeError;
import org.enso.interpreter.runtime.number.EnsoBigInteger;
import org.enso.interpreter.runtime.scope.ModuleScope;
/**
* This class defines the interpreter-level type system for Enso.
@ -39,7 +40,8 @@ import org.enso.interpreter.runtime.number.EnsoBigInteger;
UnresolvedSymbol.class,
Array.class,
EnsoBigInteger.class,
ManagedResource.class
ManagedResource.class,
ModuleScope.class
})
public class Types {

View File

@ -40,6 +40,7 @@ import org.enso.interpreter.node.expression.constant.{
ConstantObjectNode,
ConstructorNode,
DynamicSymbolNode,
EnsoProjectNode,
ErrorNode
}
import org.enso.interpreter.node.expression.literal.{
@ -139,6 +140,7 @@ class IrToTruffle(
* @param module the module for which code should be generated
*/
private def processModule(module: IR.Module): Unit = {
generateMethods()
generateReExportBindings(module)
module
.unsafeGetMetadata(
@ -320,6 +322,28 @@ class IrToTruffle(
expr
}
private def generateMethods(): Unit = {
generateEnsoProjectMethod()
}
private def generateEnsoProjectMethod(): Unit = {
val name = BindingsMap.Generated.ensoProjectMethodName
val pkg = context.getPackageOf(moduleScope.getModule)
val body = Truffle.getRuntime.createCallTarget(
new EnsoProjectNode(language, context, pkg)
)
val schema = new FunctionSchema(
FunctionSchema.CallStrategy.ALWAYS_DIRECT,
new ArgumentDefinition(
0,
"this",
ArgumentDefinition.ExecutionMode.EXECUTE
)
)
val fun = new RuntimeFunction(body, null, schema)
moduleScope.registerMethod(moduleScope.getAssociatedType, name, fun)
}
private def generateReExportBindings(module: IR.Module): Unit = {
def mkConsGetter(constructor: AtomConstructor): RuntimeFunction = {
new RuntimeFunction(

View File

@ -181,10 +181,17 @@ case class BindingsMap(
def getDirectlyExportedModules: List[ExportedModule] =
resolvedImports.collect {
case ResolvedImport(_, Some(exp), mod) =>
val hidingEnsoProject =
SymbolRestriction.Hiding(Set(Generated.ensoProjectMethodName))
val restriction = if (exp.isAll) {
if (exp.onlyNames.isDefined) {
val definedRestriction = if (exp.onlyNames.isDefined) {
SymbolRestriction.Only(
exp.onlyNames.get.map(_.name.toLowerCase).toSet
exp.onlyNames.get
.map(name =>
SymbolRestriction
.AllowedResolution(name.name.toLowerCase, None)
)
.toSet
)
} else if (exp.hiddenNames.isDefined) {
SymbolRestriction.Hiding(
@ -193,8 +200,18 @@ case class BindingsMap(
} else {
SymbolRestriction.All
}
SymbolRestriction.Intersect(
List(hidingEnsoProject, definedRestriction)
)
} else {
SymbolRestriction.Only(Set(exp.getSimpleName.name.toLowerCase))
SymbolRestriction.Only(
Set(
SymbolRestriction.AllowedResolution(
exp.getSimpleName.name.toLowerCase,
Some(ResolvedModule(mod))
)
)
)
}
val rename = if (!exp.isAll) {
Some(exp.getSimpleName.name)
@ -207,6 +224,17 @@ case class BindingsMap(
object BindingsMap {
/**
* Utilities for methods automatically generated by the compiler.
*/
object Generated {
/**
* The name of the builtin `enso_project` method.
*/
val ensoProjectMethodName: String = "enso_project"
}
/** Represents a symbol restriction on symbols exported from a module. */
sealed trait SymbolRestriction {
@ -214,9 +242,10 @@ object BindingsMap {
* Whether the export statement allows accessing the given name.
*
* @param symbol the name to check
* @param resolution the particular resolution of `symbol`
* @return whether access to the symbol is permitted by this restriction.
*/
def canAccess(symbol: String): Boolean
def canAccess(symbol: String, resolution: ResolvedName): Boolean
/**
* Performs static optimizations on the restriction, simplifying
@ -230,14 +259,44 @@ object BindingsMap {
case object SymbolRestriction {
/**
* A representation of allowed symbol. An allowed symbol consists of
* a name and an optional resolution refinement.
*
* @param symbol the symbol name
* @param resolution the only allowed resolution of `symbol`
*/
case class AllowedResolution(
symbol: String,
resolution: Option[ResolvedName]
) {
/**
* Checks if the `symbol` is visible under this restriction, with
* a given resolution.
* @param symbol the symbol
* @param resolution `symbol`'s resolution
* @return `true` if the symbol is visible, `false` otherwise
*/
def allows(symbol: String, resolution: ResolvedName): Boolean = {
val symbolMatch = this.symbol == symbol.toLowerCase
val resolutionMatch =
this.resolution.isEmpty || this.resolution.get == resolution
symbolMatch && resolutionMatch
}
}
/**
* A restriction representing a set of allowed symbols.
*
* @param symbols the allowed symbols.
*/
case class Only(symbols: Set[String]) extends SymbolRestriction {
override def canAccess(symbol: String): Boolean =
symbols.contains(symbol.toLowerCase)
case class Only(symbols: Set[AllowedResolution]) extends SymbolRestriction {
override def canAccess(
symbol: String,
resolution: ResolvedName
): Boolean =
symbols.exists(_.allows(symbol, resolution))
override def optimize: SymbolRestriction = this
}
@ -247,7 +306,10 @@ object BindingsMap {
* @param symbols the excluded symbols.
*/
case class Hiding(symbols: Set[String]) extends SymbolRestriction {
override def canAccess(symbol: String): Boolean = {
override def canAccess(
symbol: String,
resolution: ResolvedName
): Boolean = {
!symbols.contains(symbol.toLowerCase)
}
override def optimize: SymbolRestriction = this
@ -257,16 +319,22 @@ object BindingsMap {
* A restriction meaning there's no restriction at all.
*/
case object All extends SymbolRestriction {
override def canAccess(symbol: String): Boolean = true
override def optimize: SymbolRestriction = this
override def canAccess(
symbol: String,
resolution: ResolvedName
): Boolean = true
override def optimize: SymbolRestriction = this
}
/**
* A complete restriction no symbols are permitted
*/
case object Empty extends SymbolRestriction {
override def canAccess(symbol: String): Boolean = false
override def optimize: SymbolRestriction = this
override def canAccess(
symbol: String,
resolution: ResolvedName
): Boolean = false
override def optimize: SymbolRestriction = this
}
/**
@ -277,8 +345,11 @@ object BindingsMap {
*/
case class Intersect(restrictions: List[SymbolRestriction])
extends SymbolRestriction {
override def canAccess(symbol: String): Boolean = {
restrictions.forall(_.canAccess(symbol))
override def canAccess(
symbol: String,
resolution: ResolvedName
): Boolean = {
restrictions.forall(_.canAccess(symbol, resolution))
}
override def optimize: SymbolRestriction = {
@ -321,8 +392,11 @@ object BindingsMap {
*/
case class Union(restrictions: List[SymbolRestriction])
extends SymbolRestriction {
override def canAccess(symbol: String): Boolean =
restrictions.exists(_.canAccess(symbol))
override def canAccess(
symbol: String,
resolution: ResolvedName
): Boolean =
restrictions.exists(_.canAccess(symbol, resolution))
override def optimize: SymbolRestriction = {
val optimizedTerms = restrictions.map(_.optimize)

View File

@ -74,11 +74,15 @@ case object BindingAnalysis extends IRPass {
}
.flatten
.map(BindingsMap.ModuleMethod)
val methodsWithAutogen =
BindingsMap.ModuleMethod(
BindingsMap.Generated.ensoProjectMethodName
) :: moduleMethods
ir.updateMetadata(
this -->> BindingsMap(
definedConstructors,
importedPolyglot,
moduleMethods,
methodsWithAutogen,
moduleContext.module
)
)

View File

@ -177,8 +177,12 @@ class ExportsResolution {
(name.toLowerCase, List(ResolvedModule(mod)))
}
val reExportedSymbols = bindings.resolvedExports.flatMap { export =>
getBindings(export.module).exportedSymbols.toList.filter {
case (sym, _) => export.symbols.canAccess(sym)
getBindings(export.module).exportedSymbols.toList.flatMap {
case (sym, resolutions) =>
val allowedResolutions =
resolutions.filter(res => export.symbols.canAccess(sym, res))
if (allowedResolutions.isEmpty) None
else Some((sym, allowedResolutions))
}
}
bindings.exportedSymbols = List(

View File

@ -67,7 +67,7 @@ class BindingAnalysisTest extends CompilerTest {
BindingsMap(
List(Cons("Foo", 3), Cons("Bar", 0), Cons("Baz", 2)),
List(PolyglotSymbol("MyClass"), PolyglotSymbol("Renamed_Class")),
List(ModuleMethod("foo")),
List(ModuleMethod("enso_project"), ModuleMethod("foo")),
ctx.module
)
)
@ -87,6 +87,7 @@ class BindingAnalysisTest extends CompilerTest {
|$moduleName.baz = 65
|""".stripMargin.preprocessModule.analyse
ir.getMetadata(BindingAnalysis).get.moduleMethods shouldEqual List(
ModuleMethod("enso_project"),
ModuleMethod("bar")
)

View File

@ -2732,7 +2732,7 @@ class RuntimeServerTest
)
context.receive(2) should contain theSameElementsAs Seq(
Api.Response(requestId, Api.PushContextResponse(contextId)),
Api.Response(Api.ExecutionFailed(contextId, "No_Such_Method_Error UnresolvedSymbol<x> +"))
Api.Response(Api.ExecutionFailed(contextId, "No_Such_Method_Error UnresolvedSymbol<x> UnresolvedSymbol<+>"))
)
}
@ -2831,7 +2831,7 @@ class RuntimeServerTest
Api.Response(
Api.ExecutionFailed(
contextId,
"No_Such_Method_Error Number pi"
"No_Such_Method_Error Number UnresolvedSymbol<pi>"
)
)
)

View File

@ -14,7 +14,7 @@ class ImportsTest extends PackageTest {
"Overloaded methods" should "not be visible when not imported" in {
the[InterpreterException] thrownBy evalTestProject(
"TestNonImportedOverloads"
) should have message "No_Such_Method_Error (X 1) method"
) should have message "No_Such_Method_Error (X 1) UnresolvedSymbol<method>"
}
"Symbols from imported modules" should "not be visible when imported qualified" in {

View File

@ -129,7 +129,7 @@ class MethodsTest extends InterpreterTest {
|""".stripMargin
the[InterpreterException] thrownBy eval(
code
) should have message "No_Such_Method_Error 7 foo"
) should have message "No_Such_Method_Error 7 UnresolvedSymbol<foo>"
}
"be callable for any type when defined on Any" in {

View File

@ -84,4 +84,15 @@ public class Text_Utils {
public static String from_utf_8(byte[] bytes) {
return new String(bytes, StandardCharsets.UTF_8);
}
/**
* Checks whether {@code prefix} is a prefix of {@code str}.
*
* @param str the string to check
* @param prefix the potential prefix
* @return whether {@code prefix} is a prefix of {@code str}
*/
public static boolean starts_with(String str, String prefix) {
return str.startsWith(prefix);
}
}

View File

@ -0,0 +1,3 @@
Cupcake ipsum dolor sit amet. Caramels tootsie roll cake ice cream. Carrot cake apple pie gingerbread chocolate cake pudding tart soufflé jelly beans gummies.
Tootsie roll chupa chups muffin croissant fruitcake jujubes danish cotton candy danish. Oat cake chocolate fruitcake halvah icing oat cake toffee powder. Pastry dragée croissant. Ice cream candy canes dessert muffin sugar plum tart jujubes.

View File

@ -13,6 +13,7 @@ import Test.Vector.Spec as Vector_Spec
import Test.Numbers.Spec as Numbers_Spec
import Test.Text.Spec as Text_Spec
import Test.Time.Spec as Time_Spec
import Test.System.File_Spec
main = Test.Suite.runMain <|
List_Spec.spec
@ -27,3 +28,4 @@ main = Test.Suite.runMain <|
Numbers_Spec.spec
Text_Spec.spec
Time_Spec.spec
File_Spec.spec

View File

@ -0,0 +1,57 @@
from Base import all
import Base.Test
spec =
sample_file = Enso_Project.data / "sample.txt"
non_existent_file = File.new "does_not_exist.txt"
describe "File Operations" <|
it "should allow reading a file" <|
contents = sample_file.read
contents.should start_with "Cupcake ipsum dolor sit amet."
it "should allow reading a file to byte vector" <|
contents = sample_file.read_bytes
contents.take 6 . should_equal [67, 117, 112, 99, 97, 107]
it "should handle exceptions when reading a non-existent file" <|
file = File.new "does_not_exist.txt"
successfully_failed = Panic.recover file.read . catch <| case _ of
File.No_Such_File_Error _ -> True
_ -> False
successfully_failed . should_be_true
it "should check if file exists" <|
non_existent_file.exists.should_be_false
sample_file.exists.should_be_true
it "should check if file is a directory" <|
sample_file.is_directory.should_be_false
Enso_Project.root.is_directory.should_be_true
it "should get file name" <|
sample_file.name.should_equal "sample.txt"
it "should convert a file to absolute" <|
abs = File.new "foo.txt" . absolute
through_cwd = (File.current_directory / "foo.txt")
abs.should_equal through_cwd
it "should normalize file" <|
f_1 = File.new "foo"
f_2 = File.new "bar/../baz/../foo"
f_2.normalize.should_equal f_1
it "should allow reading a file byte by byte" <|
f = Enso_Project.data / "short.txt"
f.exists.should_be_false
f.write "Cup"
f.with_input_stream stream->
stream.read_byte.should_equal 67
stream.read_byte.should_equal 117
stream.read_byte.should_equal 112
stream.read_byte.should_equal -1
f.delete
f.exists.should_be_false
it "should write and append to files" <|
f = Enso_Project.data / "work.txt"
f.exists.should_be_false
f.write "line 1!"
f.exists.should_be_true
f.read.should_equal "line 1!"
f.append '\nline 2!'
f.read.should_equal 'line 1!\nline 2!'
f.delete
f.exists.should_be_false

View File

@ -26,4 +26,12 @@ spec = describe "Vectors" <|
it "should define concatenation" <|
concat = [1, 2, 3] + [4, 5, 6]
concat.should_equal [1, 2, 3, 4, 5, 6]
it "should define take and drop family of operations" <|
vec = [1, 2, 3, 4, 5, 6]
first_four = [1, 2, 3, 4]
last_four = [3, 4, 5, 6]
vec.drop 2 . should_equal last_four
vec.drop_right 2 . should_equal first_four
vec.take 4 . should_equal first_four
vec.take_right 4 . should_equal last_four