mirror of
https://github.com/enso-org/enso.git
synced 2024-09-11 21:27:15 +03:00
Files API (#1204)
This commit is contained in:
parent
73c748c4e9
commit
05f4cc2e7c
6
distribution/std-lib/Base/src/Error/Extensions.enso
Normal file
6
distribution/std-lib/Base/src/Error/Extensions.enso
Normal 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
|
@ -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
|
||||
|
10
distribution/std-lib/Base/src/Meta/Enso_Project.enso
Normal file
10
distribution/std-lib/Base/src/Meta/Enso_Project.enso
Normal 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"
|
8
distribution/std-lib/Base/src/Polyglot/Java.enso
Normal file
8
distribution/std-lib/Base/src/Polyglot/Java.enso
Normal 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]
|
272
distribution/std-lib/Base/src/System/File.enso
Normal file
272
distribution/std-lib/Base/src/System/File.enso
Normal 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)
|
49
distribution/std-lib/Base/src/System/File/Option.enso
Normal file
49
distribution/std-lib/Base/src/System/File/Option.enso
Normal 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"
|
@ -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)
|
||||
|
||||
|
@ -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]
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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});
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -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);
|
||||
}
|
||||
}
|
@ -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.
|
||||
*
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -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.
|
||||
*
|
||||
|
@ -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() + ")";
|
||||
}
|
||||
}
|
@ -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<>();
|
||||
|
@ -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 {
|
||||
|
||||
|
@ -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(
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
)
|
||||
)
|
||||
|
@ -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(
|
||||
|
@ -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")
|
||||
)
|
||||
|
||||
|
@ -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>"
|
||||
)
|
||||
)
|
||||
)
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
3
test/Test/data/sample.txt
Normal file
3
test/Test/data/sample.txt
Normal 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.
|
@ -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
|
||||
|
57
test/Test/src/System/File_Spec.enso
Normal file
57
test/Test/src/System/File_Spec.enso
Normal 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
|
||||
|
@ -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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user