Run whole test/Base_Tests in native image runner (#10296)

This commit is contained in:
Jaroslav Tulach 2024-06-21 06:03:53 +02:00 committed by GitHub
parent b5641aa3bd
commit fe2cf49568
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
48 changed files with 1562 additions and 270 deletions

2
.vscode/launch.json vendored
View File

@ -33,7 +33,7 @@
"request": "launch",
"name": "Launch Native Image",
"nativeImagePath": "${workspaceFolder}/runner",
"args": "--help"
"args": "--run ${file}"
}
]
}

View File

@ -2549,8 +2549,10 @@ lazy val `engine-runner` = project
if (smallJdkDirectory.exists()) {
IO.delete(smallJdkDirectory)
}
val JS_MODULES =
"org.graalvm.nativeimage,org.graalvm.nativeimage.builder,org.graalvm.nativeimage.base,org.graalvm.nativeimage.driver,org.graalvm.nativeimage.librarysupport,org.graalvm.nativeimage.objectfile,org.graalvm.nativeimage.pointsto,com.oracle.graal.graal_enterprise,com.oracle.svm.svm_enterprise,jdk.compiler.graal,jdk.httpserver,java.naming,java.net.http"
val NI_MODULES =
"org.graalvm.nativeimage,org.graalvm.nativeimage.builder,org.graalvm.nativeimage.base,org.graalvm.nativeimage.driver,org.graalvm.nativeimage.librarysupport,org.graalvm.nativeimage.objectfile,org.graalvm.nativeimage.pointsto,com.oracle.graal.graal_enterprise,com.oracle.svm.svm_enterprise"
val JDK_MODULES =
"jdk.localedata,jdk.compiler.graal,jdk.httpserver,java.naming,java.net.http"
val DEBUG_MODULES = "jdk.jdwp.agent"
val PYTHON_MODULES = "jdk.security.auth,java.naming"
@ -2578,7 +2580,7 @@ lazy val `engine-runner` = project
}
val exec =
s"$jlink --module-path ${modules.mkString(":")} --output $smallJdkDirectory --add-modules $JS_MODULES,$DEBUG_MODULES,$PYTHON_MODULES"
s"$jlink --module-path ${modules.mkString(":")} --output $smallJdkDirectory --add-modules $NI_MODULES,$JDK_MODULES,$DEBUG_MODULES,$PYTHON_MODULES"
val exitCode = scala.sys.process.Process(exec).!
if (exitCode != 0) {
@ -2610,6 +2612,9 @@ lazy val `engine-runner` = project
additionalOptions = Seq(
"-Dorg.apache.commons.logging.Log=org.apache.commons.logging.impl.NoOpLog",
"-H:IncludeResources=.*Main.enso$",
"-H:+AddAllCharsets",
"-H:+IncludeAllLocales",
"-ea",
// useful perf & debug switches:
// "-g",
// "-H:+SourceLevelDebug",
@ -2660,6 +2665,7 @@ lazy val `engine-runner` = project
.dependsOn(`logging-service`)
.dependsOn(`logging-service-logback` % Runtime)
.dependsOn(`polyglot-api`)
.dependsOn(`enso-test-java-helpers`)
lazy val buildSmallJdk =
taskKey[File]("Build a minimal JDK used for native image generation")
@ -3567,7 +3573,7 @@ ThisBuild / buildEngineDistributionNoIndex := {
lazy val runEngineDistribution =
inputKey[Unit]("Run or --debug the engine distribution with arguments")
runEngineDistribution := {
buildEngineDistribution.value
buildEngineDistributionNoIndex.value
val args: Seq[String] = spaceDelimited("<arg>").parsed
DistributionPackage.runEnginePackage(
engineDistributionRoot.value,

View File

@ -630,28 +630,12 @@ pub async fn runner_sanity_test(
repo_root: &crate::paths::generated::RepoRoot,
enso_java: Option<&str>,
) -> Result {
let factorial_input = "6";
let factorial_expected_output = "720";
let engine_package = repo_root.built_distribution.enso_engine_triple.engine_package.as_path();
// The engine package is necessary for running the native runner.
ide_ci::fs::tokio::require_exist(engine_package).await?;
let output = Command::new(&repo_root.runner)
.args([
"--run",
repo_root.engine.runner.src.test.resources.factorial_enso.as_str(),
factorial_input,
])
.set_env_opt(ENSO_JAVA, enso_java)?
.set_env(ENSO_DATA_DIRECTORY, engine_package)?
.run_stdout()
.await?;
ensure!(
output.contains(factorial_expected_output),
"Native runner output does not contain expected result '{factorial_expected_output}'. Output:\n{output}",
);
if enso_java.is_none() {
let test_base = Command::new(&repo_root.runner)
.args(["--run", repo_root.test.join("Base_Tests").as_str(), "^Text"])
.args(["--run", repo_root.test.join("Base_Tests").as_str()])
.set_env_opt(ENSO_JAVA, enso_java)?
.set_env(ENSO_DATA_DIRECTORY, engine_package)?
.run_stdout()

View File

@ -4,6 +4,8 @@ import project.Errors.Encoding_Error.Encoding_Error
from project.Data.Text.Extensions import all
polyglot java import java.util.Base64
polyglot java import java.util.Base64.Decoder
polyglot java import java.util.Base64.Encoder
## A helper utility for handling base64 encoding.
type Base_64

View File

@ -30,6 +30,7 @@ from project.Data.Text.Extensions import all
from project.Metadata.Choice import Option
from project.Metadata.Widget import Single_Choice
polyglot java import com.fasterxml.jackson.core.JsonLocation
polyglot java import com.fasterxml.jackson.core.JsonProcessingException
polyglot java import com.fasterxml.jackson.databind.JsonNode
polyglot java import com.fasterxml.jackson.databind.node.ArrayNode

View File

@ -9,6 +9,7 @@ import project.Nothing.Nothing
import project.Panic.Panic
polyglot java import org.enso.base.statistics.FitError
polyglot java import org.enso.base.statistics.LinearModel
polyglot java import org.enso.base.statistics.Regression
type Model

View File

@ -544,7 +544,7 @@ Text.replace self term:(Text | Regex) replacement:Text (case_sensitivity:Case_Se
Applies the specified cleansings to the text.
Arguments:
- remove: A vector of the named patterns to cleanse from the text. The named patterns are
- remove: A vector of the named patterns to cleanse from the text. The named patterns are
applied in the order they are provided. The same named pattern can be used multiple
times. The named patterns are:
- ..Leading_Whitespace: Removes all whitespace from the start of the string.

View File

@ -34,6 +34,7 @@ polyglot java import java.time.DateTimeException
polyglot java import java.time.temporal.ChronoField
polyglot java import java.time.temporal.IsoFields
polyglot java import org.enso.base.Time_Utils
polyglot java import org.enso.base.Time_Utils.AdjustOp
## PRIVATE
Constructs a new Date from a year, month, and day.

View File

@ -11,7 +11,10 @@ polyglot java import java.time.temporal.TemporalAdjuster
polyglot java import java.time.temporal.TemporalAdjusters
polyglot java import java.time.temporal.TemporalUnit
polyglot java import org.enso.base.time.CustomTemporalUnits
polyglot java import org.enso.base.time.Date_Utils
polyglot java import org.enso.base.time.Date_Period_Utils
polyglot java import org.enso.base.time.Date_Time_Utils
polyglot java import org.enso.base.time.Time_Of_Day_Utils
polyglot java import org.enso.base.Time_Utils
## Represents a unit of time longer on the scale of days (longer than a day).

View File

@ -25,19 +25,18 @@ from project.Data.Range.Extensions import all
from project.Data.Text.Extensions import all
from project.Metadata import Display, make_single_choice, Widget
polyglot java import java.io.StringReader
polyglot java import java.lang.Exception as JException
polyglot java import javax.xml.parsers.DocumentBuilder
polyglot java import javax.xml.parsers.DocumentBuilderFactory
polyglot java import javax.xml.xpath.XPath
polyglot java import javax.xml.xpath.XPathConstants
polyglot java import javax.xml.xpath.XPathFactory
polyglot java import org.enso.base.XML_Utils
polyglot java import org.w3c.dom.Attr
polyglot java import org.w3c.dom.Document
polyglot java import org.w3c.dom.Element
polyglot java import org.w3c.dom.Node
polyglot java import org.w3c.dom.NodeList
polyglot java import org.w3c.dom.NamedNodeMap
polyglot java import org.w3c.dom.Text as Java_Text
polyglot java import org.xml.sax.InputSource
polyglot java import org.xml.sax.SAXException
polyglot java import org.xml.sax.SAXParseException
@ -75,7 +74,8 @@ type XML_Document
from_stream : Input_Stream -> XML_Document ! XML_Error
from_stream input_stream:Input_Stream =
XML_Error.handle_java_exceptions <|
input_stream.with_java_stream java_stream-> XML_Document.from_source java_stream
input_stream.with_java_stream java_stream->
XML_Document.Value (XML_Utils.parseStream java_stream)
## GROUP Conversions
ICON convert
@ -93,20 +93,13 @@ type XML_Document
from_text : Text -> XML_Document ! XML_Error
from_text xml_string:Text =
XML_Error.handle_java_exceptions <|
string_reader = StringReader.new xml_string
XML_Document.from_source (InputSource.new string_reader)
XML_Document.Value (XML_Utils.parseString xml_string)
## PRIVATE
Read XML from an input source.
from_source : Any -> XML_Document ! XML_Error
from_source input_source =
document_builder_factory = DocumentBuilderFactory.newInstance
document_builder = document_builder_factory.newDocumentBuilder
XML_Utils.setCustomErrorHandler document_builder
XML_Document.Value (document_builder.parse input_source)
Wrap Java's Document to XML_Document
new doc:Document = XML_Document.Value doc
## PRIVATE
Value (java_document:Document)
private Value (java_document:Document)
## GROUP Metadata

View File

@ -24,6 +24,10 @@ from project.Metadata import make_single_choice
polyglot java import org.enso.base.enso_cloud.EnsoSecretHelper
polyglot java import org.enso.base.enso_cloud.HideableValue
polyglot java import org.enso.base.enso_cloud.HideableValue.PlainValue
polyglot java import org.enso.base.enso_cloud.HideableValue.SecretValue
polyglot java import org.enso.base.enso_cloud.HideableValue.ConcatValues
polyglot java import org.enso.base.enso_cloud.HideableValue.Base64EncodeValue
## A reference to a secret stored in the Enso Cloud.
type Enso_Secret

View File

@ -78,7 +78,7 @@ slice vector start end = @Builtin_Method "Array_Like_Helpers.slice"
`Map_Error`.
- No_Wrap: The first error is thrown, and is not wrapped in
`Map_Error`.
- Report_Warning: The result for that element is `Nothing`,
- Report_Warning: The result for that element is `Nothing`,
the error is attached as a warning. Currently unimplemented.
- Ignore: The result is `Nothing`, and the error is
ignored.

View File

@ -14,3 +14,18 @@ polyglot java import org.enso.base.text.CaseFoldedString.Grapheme
# needed by Comparator_Spec:
polyglot java import org.enso.base.ObjectComparator
# often used in tests
polyglot java import java.util.ArrayList
polyglot java import java.util.Map
polyglot java import java.nio.CharBuffer
polyglot java import java.nio.file.Path
polyglot java import java.io.FileInputStream
polyglot java import java.io.FileOutputStream
polyglot java import java.math.BigInteger
polyglot java import java.time.LocalDate
polyglot java import java.time.LocalDateTime
polyglot java import java.util.function.Function
polyglot java import java.lang.Thread
polyglot java import java.lang.Thread.State
polyglot java import java.lang.Float

View File

@ -45,7 +45,7 @@ type Project_Description
Arguments:
- prim_root_file: The primitive root file of the project.
- prim_config: The primitive config of the project.
private Value prim_root_file prim_config
private Value prim_root_file ns:Text n:Text
## GROUP Metadata
ICON folder
@ -78,7 +78,7 @@ type Project_Description
enso_project.name
name : Text
name self = self.prim_config.name
name self = self.n
## GROUP Metadata
ICON metadata
@ -89,7 +89,7 @@ type Project_Description
enso_project.namespace
namespace : Text
namespace self = self.prim_config.namespace
namespace self = self.ns
## ICON enso_icon
Returns the Enso project description for the project that the engine was

View File

@ -30,12 +30,17 @@ from project.Data.Json.Extensions import all
polyglot java import java.lang.Exception as JException
polyglot java import java.net.http.HttpClient
polyglot java import java.net.http.HttpClient.Redirect
polyglot java import java.net.http.HttpClient.Version
polyglot java import java.net.http.HttpClient.Builder as ClientBuilder
polyglot java import java.net.http.HttpRequest
polyglot java import java.net.http.HttpRequest.BodyPublisher
polyglot java import java.net.http.HttpRequest.BodyPublishers
polyglot java import java.net.http.HttpRequest.Builder
polyglot java import java.net.InetSocketAddress
polyglot java import java.net.ProxySelector
polyglot java import java.nio.file.Path
polyglot java import javax.net.ssl.SSLContext
polyglot java import org.enso.base.file_system.File_Utils
polyglot java import org.enso.base.enso_cloud.EnsoSecretHelper
polyglot java import org.enso.base.net.http.MultipartBodyBuilder
polyglot java import org.enso.base.net.http.UrlencodedBodyBuilder
@ -242,7 +247,7 @@ resolve_body_to_publisher_and_boundary body:Request_Body =
json.if_not_error <|
Pair.new (body_publishers.ofString json) Nothing
Request_Body.Binary file ->
path = Path.of file.path
path = File_Utils.toPath file.path
Pair.new (body_publishers.ofFile path) Nothing
Request_Body.Form_Data form_data url_encoded ->
build_form_body_publisher form_data url_encoded

View File

@ -25,6 +25,10 @@ from project.Data.Text.Extensions import all
from project.Metadata import Display, Widget
from project.Network.HTTP.Response_Body import decode_format_selector
polyglot java import java.net.http.HttpHeaders
polyglot java import org.enso.base.enso_cloud.EnsoHttpResponse
polyglot java import java.util.Optional
type Response
## PRIVATE

View File

@ -42,13 +42,12 @@ from project.System.File_Format import Auto_Detect, File_Format
polyglot java import java.io.File as Java_File
polyglot java import java.io.InputStream as Java_Input_Stream
polyglot java import java.io.OutputStream as Java_Output_Stream
polyglot java import java.nio.file.FileSystems
polyglot java import java.nio.file.Path
polyglot java import java.nio.file.StandardCopyOption
polyglot java import java.nio.file.StandardOpenOption
polyglot java import java.time.ZonedDateTime
polyglot java import org.enso.base.DryRunFileManager
polyglot java import org.enso.base.file_system.FileSystemSPI
polyglot java import org.enso.base.file_system.File_Utils
## PRIVATE
file_types : Vector
@ -795,11 +794,10 @@ type File
_ ->
used_filter = if recursive.not || name_filter.contains "**" then name_filter else
(if name_filter.starts_with "*" then "*" else "**/") + name_filter
fs = FileSystems.getDefault
matcher = fs.getPathMatcher "glob:"+used_filter
matcher = File_Utils.matchPath "glob:"+used_filter
all_files.filter file->
pathStr = self.relativize file . path
matcher.matches (Path.of pathStr)
File_Utils.matches matcher pathStr
## GROUP Metadata
ICON metadata
@ -894,11 +892,11 @@ Writable_File.from (that : File) = if Data_Link.is_data_link that then Data_Link
## PRIVATE
local_file_copy (source : File) (destination : File) (replace_existing : Boolean) -> Nothing =
File_Error.handle_java_exceptions source <|
copy_options = if replace_existing then [StandardCopyOption.REPLACE_EXISTING] else []
copy_options = if replace_existing then [StandardCopyOption.REPLACE_EXISTING.to_text] else []
source.copy_builtin destination copy_options
## PRIVATE
local_file_move (source : File) (destination : File) (replace_existing : Boolean) -> Nothing =
File_Error.handle_java_exceptions source <|
copy_options = if replace_existing then [StandardCopyOption.REPLACE_EXISTING] else []
copy_options = if replace_existing then [StandardCopyOption.REPLACE_EXISTING.to_text] else []
source.move_builtin destination copy_options

View File

@ -49,18 +49,20 @@ type File_Access
## PRIVATE
Convert this object into a representation understandable by the JVM.
to_java : StandardOpenOption
to_java self = case self of
File_Access.Append -> StandardOpenOption.APPEND
File_Access.Create -> StandardOpenOption.CREATE
File_Access.Create_New -> StandardOpenOption.CREATE_NEW
File_Access.Delete_On_Close -> StandardOpenOption.DELETE_ON_CLOSE
File_Access.Dsync -> StandardOpenOption.DSYNC
File_Access.Read -> StandardOpenOption.READ
File_Access.Sparse -> StandardOpenOption.SPARSE
File_Access.Sync -> StandardOpenOption.SYNC
File_Access.Truncate_Existing -> StandardOpenOption.TRUNCATE_EXISTING
File_Access.Write -> StandardOpenOption.WRITE
to_java : Text
to_java self =
java_option = case self of
File_Access.Append -> StandardOpenOption.APPEND
File_Access.Create -> StandardOpenOption.CREATE
File_Access.Create_New -> StandardOpenOption.CREATE_NEW
File_Access.Delete_On_Close -> StandardOpenOption.DELETE_ON_CLOSE
File_Access.Dsync -> StandardOpenOption.DSYNC
File_Access.Read -> StandardOpenOption.READ
File_Access.Sparse -> StandardOpenOption.SPARSE
File_Access.Sync -> StandardOpenOption.SYNC
File_Access.Truncate_Existing -> StandardOpenOption.TRUNCATE_EXISTING
File_Access.Write -> StandardOpenOption.WRITE
java_option.to_text
## PRIVATE
ensure_only_allowed_options (operation_name : Text) (allowed_options : Vector) (got_options : Vector) ~action =

View File

@ -3,6 +3,8 @@ import project.Data.Text.Text
import project.Data.Vector.Vector
polyglot java import java.nio.file.attribute.PosixFilePermission
polyglot java import java.nio.file.attribute.PosixFilePermissions
polyglot java import java.util.Set
type Permission
## Permission for read access for a given entity.
@ -101,7 +103,8 @@ type File_Permissions
## PRIVATE
ADVANCED
Converts a Java `Set` of Java `PosixFilePermission` to `File_Permissions`.
from_java_set java_set =
from_java_set permissions:Text =
java_set = PosixFilePermissions.fromString permissions
vecs = Vector.build_multiple 3 builders->
owner = builders.at 0
group = builders.at 1

View File

@ -23,6 +23,7 @@ polyglot java import java.io.InputStream as Java_Input_Stream
polyglot java import org.enso.base.encoding.Encoding_Utils
polyglot java import org.enso.base.encoding.ReportingStreamDecoder
polyglot java import org.enso.base.Stream_Utils
polyglot java import org.enso.base.Stream_Utils.InputStreamLike
## PRIVATE
An input stream, allowing for interactive reading of contents.
@ -113,7 +114,8 @@ type Input_Stream
Arguments:
- f: Applies a function over the internal java stream.
with_java_stream : (Java_Input_Stream -> Any) -> Any
with_java_stream self f = self.stream_resource . with java_stream->
with_java_stream self f = self.stream_resource . with java_like_stream->
java_stream = Stream_Utils.asInputStream java_like_stream
self.error_handler <| f java_stream
## PRIVATE
@ -158,7 +160,8 @@ type Input_Stream
The current stream may be invalidated after the conversion, and it should
no longer be used - only the returned stream should be used.
as_peekable_stream self -> Input_Stream = if self.is_peekable then self else
raw_java_stream = self.stream_resource.take
raw_stream = self.stream_resource.take
raw_java_stream = Stream_Utils.asInputStream raw_stream
buffered_stream = BufferedInputStream.new raw_java_stream
Input_Stream.new buffered_stream self.error_handler self.associated_source

View File

@ -13,6 +13,7 @@ from project.Data.Boolean import Boolean, False, True
from project.Runtime import assert
polyglot java import org.enso.base.encoding.DecodingProblemAggregator
polyglot java import org.enso.base.encoding.DecodingProblem
polyglot java import org.enso.base.encoding.Encoding_Utils
polyglot java import org.enso.base.encoding.ReportingStreamDecoder

View File

@ -15,6 +15,9 @@ from project.System.Input_Stream import close_stream
polyglot java import java.io.ByteArrayOutputStream
polyglot java import java.io.OutputStream as Java_Output_Stream
polyglot java import org.enso.base.encoding.Encoding_Utils
polyglot java import org.enso.base.Stream_Utils
polyglot java import org.enso.base.Stream_Utils.OutputStreamLike
polyglot java import org.enso.base.encoding.ReportingStreamEncoder
## PRIVATE
@ -57,7 +60,7 @@ type Output_Stream
- stream_resource: The internal resource that represents the underlying
stream.
- error_handler: An error handler for IOExceptions thrown when writing.
Value stream_resource error_handler
private Value stream_resource error_handler
## PRIVATE
ADVANCED
@ -66,7 +69,8 @@ type Output_Stream
Arguments:
- contents: A vector of bytes to write.
write_bytes : Vector Integer -> Nothing
write_bytes self contents = self.stream_resource . with java_stream->
write_bytes self contents = self.stream_resource . with raw_stream->
java_stream = Stream_Utils.asOutputStream raw_stream
self.error_handler <|
java_stream.write contents
java_stream.flush
@ -80,7 +84,8 @@ type Output_Stream
- input_stream: An Input_Stream to write to this stream.
write_stream : Input_Stream -> Nothing
write_stream self input_stream:Input_Stream =
self.stream_resource . with java_output_stream->
self.stream_resource . with raw_stream->
java_output_stream = Stream_Utils.asOutputStream raw_stream
self.error_handler <|
input_stream.with_java_stream java_input_stream->
java_input_stream.transferTo java_output_stream
@ -107,7 +112,9 @@ type Output_Stream
Arguments:
- f: Applies a function over the internal java stream.
with_java_stream : (Java_Output_Stream -> Any) -> Any
with_java_stream self f = self.stream_resource . with f
with_java_stream self f = self.stream_resource . with raw_stream->
java_stream = Stream_Utils.asOutputStream raw_stream
f java_stream
## PRIVATE
ADVANCED

View File

@ -2721,13 +2721,13 @@ type Table
- root_name: The name of the root tag in the XML.
- row_name: The name of the row tag in the XML.
- on_problems: Specifies how to handle warnings if they occur, reporting
them as warnings by default.
them as warnings by default.
! Error Conditions
- If a column in `element_columns`, `attribute_columns` or `value_column` is not in
- If a column in `element_columns`, `attribute_columns` or `value_column` is not in
the input table, a `Missing_Input_Columns` is raised as an error.
- If any incomming columns aree not specified in one of `element_columns`,
- If any incomming columns aree not specified in one of `element_columns`,
`attribute_columns` or `value_column`, a `Unexpected_Extra_Columns`
is reported according to the `on_problems` setting.
@ -2736,10 +2736,10 @@ type Table
Input Table `table`:
Title | Author | Price | Year
Title | Author | Price | Year
------------------------+---------------------+-------+------
A Tale Of Two Cities | Charles Dickens | 9.99 | 1859
The Great Gatsby | F. Scott Fitzgerald | 5.99 | 1925
A Tale Of Two Cities | Charles Dickens | 9.99 | 1859
The Great Gatsby | F. Scott Fitzgerald | 5.99 | 1925
Result `r = t.to_xml ["Year"] ["Author", "Price"] "Title" "Books" "Book"`:
@ -2770,9 +2770,9 @@ type Table
unused_columns = columns_helper.internal_columns.filter (Filter_Condition.Is_In resolved_element_columns+resolved_attribute_columns+[resolved_value_column] Filter_Action.Remove) . map .name
if unused_columns.length > 0 then problem_builder.report_other_warning (Unexpected_Extra_Columns.Warning unused_columns)
problem_builder.attach_problems_before on_problems <|
XML_Document.Value (Java_TableToXml.to_xml self.row_count java_element_columns java_attribute_column java_value_column root_name row_name)
XML_Document.new (Java_TableToXml.to_xml self.row_count java_element_columns java_attribute_column java_value_column root_name row_name)
## PRIVATE
columns_helper : Table_Column_Helper

View File

@ -205,8 +205,5 @@ type Test
if i % 10 == 0 then
IO.println 'Still failing after '+i.to_text+' retries ('+loc.to_display_text+'):\n'+caught_panic.payload.to_display_text
Thread.sleep milliseconds_between_attempts
## TODO This used to be
@Tail_Call go (i+1)
We should re-add the tail call once https://github.com/enso-org/enso/issues/9251 is fixed.
go (i+1)
@Tail_Call go (i+1)
go 1

View File

@ -207,17 +207,16 @@ state. To generate the Native Image for runner simply execute
sbt> engine-runner/buildNativeImage
```
and execute the binary on a sample factorial test program
and execute any program with that binary - for example `test/Base_Tests`
```bash
> runner --run engine/runner/src/test/resources/Factorial.enso 6
$ runner --run test/Base_Tests
```
The task that generates the Native Image, along with all the necessary
configuration, reside in a separate project due to a bug in the currently used
GraalVM version. As September 2023 it can execute all Enso code, but cannot
invoke `IO.println` or other library functions that require
[polyglot java import](../../docs/polyglot/java.md), but read on...
configuration, makes sure that `Standard.Base` library calls into Java via
[polyglot java import](../../docs/polyglot/java.md) are compiled into the binary
and ready to be used.
### Engine with Espresso

View File

@ -9,6 +9,7 @@ import java.util.TreeSet;
import org.enso.compiler.core.ir.module.scope.imports.Polyglot;
import org.enso.pkg.PackageManager$;
import org.graalvm.nativeimage.hosted.Feature;
import org.graalvm.nativeimage.hosted.RuntimeProxyCreation;
import org.graalvm.nativeimage.hosted.RuntimeReflection;
public final class EnsoLibraryFeature implements Feature {
@ -75,6 +76,9 @@ public final class EnsoLibraryFeature implements Feature {
RuntimeReflection.registerAllConstructors(clazz);
RuntimeReflection.registerAllFields(clazz);
RuntimeReflection.registerAllMethods(clazz);
if (clazz.isInterface()) {
RuntimeProxyCreation.register(clazz);
}
}
}
}

View File

@ -1,6 +1,8 @@
{
"resources":{
"includes":[{
"pattern":"\\QMETA-INF/native-image/com.oracle.truffle.espresso/native-image.properties\\E"
}, {
"pattern":"\\QMETA-INF/org/enso/interpreter/node/expression/builtin/BuiltinMethods.metadata\\E"
}, {
"pattern":"\\QMETA-INF/org/enso/interpreter/node/expression/builtin/BuiltinTypes.metadata\\E"
@ -52,6 +54,8 @@
"pattern":"\\QMETA-INF/services/java.time.zone.ZoneRulesProvider\\E"
}, {
"pattern":"\\QMETA-INF/services/javax.xml.parsers.DocumentBuilderFactory\\E"
}, {
"pattern":"\\QMETA-INF/services/javax.xml.parsers.SAXParserFactory\\E"
}, {
"pattern":"\\QMETA-INF/services/javax.xml.xpath.XPathFactory\\E"
}, {
@ -166,6 +170,8 @@
"pattern":"\\Qversion.json\\E"
}, {
"pattern":"\\Qversion.properties\\E"
}, {
"pattern":"java.base:\\Qjdk/internal/icu/impl/data/icudt72b/nfkc.nrm\\E"
}, {
"pattern":"java.base:\\Qjdk/internal/icu/impl/data/icudt72b/uprops.icu\\E"
}, {
@ -597,13 +603,13 @@
}]},
"bundles":[{
"name":"com.oracle.js.parser.resources.Messages",
"locales":[""]
"locales":["", "und"]
}, {
"name":"com.sun.org.apache.xerces.internal.impl.msg.XMLMessages",
"locales":[""]
"locales":["", "und"]
}, {
"name":"com.sun.org.apache.xml.internal.serializer.XMLEntities",
"locales":[""]
"locales":["", "und"]
}, {
"name":"com.sun.org.apache.xml.internal.serializer.utils.SerializerMessages",
"classNames":["com.sun.org.apache.xml.internal.serializer.utils.SerializerMessages", "com.sun.org.apache.xml.internal.serializer.utils.SerializerMessages_en"]

View File

@ -1,12 +0,0 @@
import Standard.Base.Data.Numbers
import Standard.Base.IO
fac n =
facacc n v = if n <= 1 then v else @Tail_Call facacc n-1 n*v
res = facacc n 1
res
main number=5 =
v = fac number
IO.println v

View File

@ -183,13 +183,17 @@ public abstract class HostMethodCallNode extends Node {
return PolyglotCallType.CONVERT_TO_HASH_MAP;
}
String methodName = symbol.getName();
if (library.isMemberInvocable(self, methodName)) {
return PolyglotCallType.CALL_METHOD;
} else if (library.isMemberReadable(self, methodName)) {
return PolyglotCallType.GET_MEMBER;
} else if (library.isInstantiable(self) && methodName.equals(NEW_NAME)) {
return PolyglotCallType.INSTANTIATE;
try {
String methodName = symbol.getName();
if (library.isMemberInvocable(self, methodName)) {
return PolyglotCallType.CALL_METHOD;
} else if (library.isMemberReadable(self, methodName)) {
return PolyglotCallType.GET_MEMBER;
} else if (library.isInstantiable(self) && methodName.equals(NEW_NAME)) {
return PolyglotCallType.INSTANTIATE;
}
} catch (TypeNotPresentException ex) {
// no call, get or instantiate is possible
}
return PolyglotCallType.NOT_SUPPORTED;
}

View File

@ -98,10 +98,11 @@ public abstract class EnsoProjectNode extends Node {
private static Atom createProjectDescriptionAtom(EnsoContext ctx, Package<TruffleFile> pkg) {
var rootPath = new EnsoFile(pkg.root().normalize());
var cfg = ctx.asGuestValue(pkg.getConfig());
var namespace = pkg.getConfig().namespace();
var name = pkg.getConfig().name();
var cons = ctx.getBuiltins().getProjectDescription().getUniqueConstructor();
return AtomNewInstanceNode.getUncached().newInstance(cons, rootPath, cfg);
return AtomNewInstanceNode.getUncached().newInstance(cons, rootPath, namespace, name);
}
private DataflowError unsupportedArgsError(Object moduleActual) {

View File

@ -13,6 +13,6 @@ public class ProjectDescription extends UniquelyConstructibleBuiltin {
@Override
protected List<String> getConstructorParamNames() {
return List.of("prim_root_file", "prim_config");
return List.of("prim_root_file", "ns", "n");
}
}

View File

@ -4,9 +4,13 @@ import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.TruffleFile;
import com.oracle.truffle.api.dsl.Bind;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.interop.ArityException;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.InvalidArrayIndexException;
import com.oracle.truffle.api.interop.UnknownIdentifierException;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.interop.UnsupportedTypeException;
import com.oracle.truffle.api.library.CachedLibrary;
import com.oracle.truffle.api.library.ExportLibrary;
import com.oracle.truffle.api.library.ExportMessage;
@ -22,16 +26,22 @@ import java.nio.file.LinkOption;
import java.nio.file.NoSuchFileException;
import java.nio.file.NotDirectoryException;
import java.nio.file.OpenOption;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.function.IntFunction;
import java.util.function.Function;
import org.enso.interpreter.dsl.Builtin;
import org.enso.interpreter.runtime.EnsoContext;
import org.enso.interpreter.runtime.data.text.Text;
import org.enso.interpreter.runtime.data.vector.ArrayLikeAtNode;
import org.enso.interpreter.runtime.data.vector.ArrayLikeHelpers;
import org.enso.interpreter.runtime.data.vector.ArrayLikeLengthNode;
import org.enso.interpreter.runtime.error.PanicException;
import org.enso.interpreter.runtime.library.dispatch.TypesLibrary;
@ -54,60 +64,273 @@ public final class EnsoFile implements EnsoObject {
@Builtin.Method(name = "output_stream_builtin")
@Builtin.WrapException(from = IOException.class)
@Builtin.ReturningGuestObject
@Builtin.Specialize
@CompilerDirectives.TruffleBoundary
public OutputStream outputStream(Object opts, EnsoContext ctx) throws IOException {
OpenOption[] openOptions =
convertInteropArray(opts, InteropLibrary.getUncached(), ctx, OpenOption[]::new);
return this.truffleFile.newOutputStream(openOptions);
@TruffleBoundary
public EnsoObject outputStream(
Object opts,
@Cached ArrayLikeLengthNode lengthNode,
@Cached ArrayLikeAtNode atNode,
EnsoContext ctx)
throws IOException {
var options = namesToValues(opts, lengthNode, atNode, ctx, StandardOpenOption::valueOf);
var os = this.truffleFile.newOutputStream(options.toArray(OpenOption[]::new));
return new EnsoOutputStream(os);
}
@ExportLibrary(InteropLibrary.class)
static final class EnsoOutputStream implements EnsoObject {
private static final String[] MEMBERS = new String[] {"write", "flush", "close"};
private final OutputStream os;
EnsoOutputStream(OutputStream os) {
this.os = os;
}
@ExportMessage
boolean hasMembers() {
return true;
}
@TruffleBoundary
@ExportMessage
boolean isMemberInvocable(String member) {
return Arrays.asList(MEMBERS).contains(member);
}
@ExportMessage
Object getMembers(boolean includeInternal) throws UnsupportedMessageException {
return ArrayLikeHelpers.wrapStrings(MEMBERS);
}
@TruffleBoundary
@ExportMessage
Object invokeMember(
String name,
Object[] args,
@Cached ArrayLikeLengthNode lengthNode,
@Cached ArrayLikeAtNode atNode,
@CachedLibrary(limit = "3") InteropLibrary iop)
throws ArityException, UnsupportedMessageException, UnknownIdentifierException {
try {
return switch (name) {
case "write" -> {
long from;
long to;
switch (args.length) {
case 1 -> {
from = 0;
to = lengthNode.executeLength(args[0]);
}
case 3 -> {
from = iop.asLong(args[1]);
to = from + iop.asLong(args[2]);
}
default -> {
throw ArityException.create(1, 3, args.length);
}
}
for (long i = from; i < to; i++) {
var elem = atNode.executeAt(args[0], i);
var byt = iop.asInt(elem);
os.write(byt);
}
yield this;
}
case "flush" -> {
os.flush();
yield this;
}
case "close" -> {
os.close();
yield this;
}
default -> throw UnknownIdentifierException.create(name);
};
} catch (IOException ex) {
throw raiseIOException(iop, ex);
} catch (InvalidArrayIndexException ex) {
var ctx = EnsoContext.get(iop);
throw ctx.raiseAssertionPanic(iop, name, ex);
}
}
@Override
public String toString() {
return "EnsoOutputStream";
}
}
@Builtin.Method(name = "input_stream_builtin")
@Builtin.WrapException(from = IOException.class)
@Builtin.Specialize
@Builtin.ReturningGuestObject
@CompilerDirectives.TruffleBoundary
public InputStream inputStream(Object opts, EnsoContext ctx) throws IOException {
OpenOption[] openOptions =
convertInteropArray(opts, InteropLibrary.getUncached(), ctx, OpenOption[]::new);
return this.truffleFile.newInputStream(openOptions);
@TruffleBoundary
public EnsoObject inputStream(
Object opts,
@Cached ArrayLikeLengthNode lengthNode,
@Cached ArrayLikeAtNode atNode,
EnsoContext ctx)
throws IOException {
var options = namesToValues(opts, lengthNode, atNode, ctx, StandardOpenOption::valueOf);
var is = this.truffleFile.newInputStream(options.toArray(OpenOption[]::new));
return new EnsoInputStream(is);
}
@ExportLibrary(InteropLibrary.class)
static final class EnsoInputStream implements EnsoObject {
private static final String[] MEMBERS =
new String[] {
"read", "readAllBytes", "readNBytes", "skipNBytes", "markSupported", "available", "close"
};
private final InputStream is;
EnsoInputStream(InputStream is) {
this.is = is;
}
@ExportMessage
boolean hasMembers() {
return true;
}
@TruffleBoundary
@ExportMessage
boolean isMemberInvocable(String member) {
return Arrays.asList(MEMBERS).contains(member);
}
@ExportMessage
Object getMembers(boolean includeInternal) throws UnsupportedMessageException {
return ArrayLikeHelpers.wrapStrings(MEMBERS);
}
@TruffleBoundary
@ExportMessage
Object invokeMember(String name, Object[] args, @CachedLibrary(limit = "3") InteropLibrary iop)
throws UnknownIdentifierException,
UnsupportedMessageException,
ArityException,
UnsupportedTypeException {
try {
return switch (name) {
case "read" -> {
if (args.length == 0) {
yield is.read();
}
long from;
long to;
switch (args.length) {
case 1 -> {
from = 0;
to = iop.getArraySize(args[0]);
}
case 3 -> {
from = iop.asLong(args[1]);
to = from + iop.asLong(args[2]);
}
default -> throw ArityException.create(0, 3, args.length);
}
for (var i = from; i < to; i++) {
var b = is.read();
if (b == -1) {
var count = i - from;
yield count > 0 ? count : -1;
}
iop.writeArrayElement(args[0], i, (byte) b);
}
yield to - from;
}
case "readAllBytes" -> {
if (args.length != 0) {
throw ArityException.create(0, 0, args.length);
}
var arr = is.readAllBytes();
var buf = ByteBuffer.wrap(arr);
yield ArrayLikeHelpers.wrapBuffer(buf);
}
case "readNBytes" -> {
if (args.length != 1) {
throw ArityException.create(1, 1, args.length);
}
var len = iop.asInt(args[0]);
var arr = is.readNBytes(len);
var buf = ByteBuffer.wrap(arr);
yield ArrayLikeHelpers.wrapBuffer(buf);
}
case "skipNBytes" -> {
if (args.length != 1) {
throw ArityException.create(1, 1, args.length);
}
var len = iop.asInt(args[0]);
is.skipNBytes(len);
yield this;
}
case "markSupported" -> {
if (args.length != 0) {
throw ArityException.create(0, 0, args.length);
}
yield is.markSupported();
}
case "available" -> {
if (args.length != 0) {
throw ArityException.create(0, 0, args.length);
}
yield is.available();
}
case "close" -> {
if (args.length != 0) {
throw ArityException.create(0, 0, args.length);
}
is.close();
yield this;
}
default -> throw UnknownIdentifierException.create(name);
};
} catch (IOException ex) {
throw raiseIOException(iop, ex);
} catch (InvalidArrayIndexException ex) {
var ctx = EnsoContext.get(iop);
throw ctx.raiseAssertionPanic(iop, name, ex);
}
}
@Override
public String toString() {
return "EnsoInputStream";
}
}
@SuppressWarnings("unchecked")
private static <T> T[] convertInteropArray(
Object arr, InteropLibrary interop, EnsoContext ctx, IntFunction<T[]> hostArrayCtor) {
if (!interop.hasArrayElements(arr)) {
var vecType = ctx.getBuiltins().vector().getType();
var typeError = ctx.getBuiltins().error().makeTypeError(vecType, arr, "opts");
throw new PanicException(typeError, interop);
}
T[] hostArr;
@TruffleBoundary
private static <T> List<T> namesToValues(
Object arr,
ArrayLikeLengthNode lengthNode,
ArrayLikeAtNode atNode,
EnsoContext ctx,
Function<String, T> convertor) {
var size = (int) lengthNode.executeLength(arr);
List<T> hostArr = new ArrayList<>();
try {
int size = Math.toIntExact(interop.getArraySize(arr));
hostArr = hostArrayCtor.apply(size);
for (int i = 0; i < size; i++) {
Object elem = interop.readArrayElement(arr, i);
if (!ctx.isJavaPolyglotObject(elem)) {
for (var i = 0; i < size; i++) {
var elem = atNode.executeAt(arr, i);
if (elem instanceof Text name) {
hostArr.add(convertor.apply(name.toString()));
} else {
var err =
ctx.getBuiltins()
.error()
.makeUnsupportedArgumentsError(
new Object[] {arr},
"Arguments to opts should be host objects from java.io package");
throw new PanicException(err, interop);
.makeTypeError(ctx.getBuiltins().text(), elem, "File_Access permissions");
throw new PanicException(err, lengthNode);
}
hostArr[i] = (T) ctx.asJavaPolyglotObject(elem);
}
} catch (ClassCastException | UnsupportedMessageException | InvalidArrayIndexException e) {
throw EnsoContext.get(interop).raiseAssertionPanic(interop, null, e);
} catch (ClassCastException | InvalidArrayIndexException e) {
throw EnsoContext.get(lengthNode).raiseAssertionPanic(lengthNode, null, e);
}
return hostArr;
}
@Builtin.Method(name = "read_last_bytes_builtin")
@Builtin.WrapException(from = IOException.class)
@CompilerDirectives.TruffleBoundary
@TruffleBoundary
public EnsoObject readLastBytes(long n) throws IOException {
try (SeekableByteChannel channel =
this.truffleFile.newByteChannel(Set.of(StandardOpenOption.READ))) {
@ -136,7 +359,7 @@ public final class EnsoFile implements EnsoObject {
@Builtin.Method(name = "creation_time_builtin")
@Builtin.WrapException(from = IOException.class)
@CompilerDirectives.TruffleBoundary
@TruffleBoundary
public EnsoDateTime getCreationTime() throws IOException {
return new EnsoDateTime(
ZonedDateTime.ofInstant(truffleFile.getCreationTime().toInstant(), ZoneOffset.UTC));
@ -144,7 +367,7 @@ public final class EnsoFile implements EnsoObject {
@Builtin.Method(name = "last_modified_time_builtin")
@Builtin.WrapException(from = IOException.class)
@CompilerDirectives.TruffleBoundary
@TruffleBoundary
public EnsoDateTime getLastModifiedTime() throws IOException {
return new EnsoDateTime(
ZonedDateTime.ofInstant(truffleFile.getLastModifiedTime().toInstant(), ZoneOffset.UTC));
@ -152,14 +375,13 @@ public final class EnsoFile implements EnsoObject {
@Builtin.Method(name = "posix_permissions_builtin")
@Builtin.WrapException(from = IOException.class)
@Builtin.ReturningGuestObject
@CompilerDirectives.TruffleBoundary
public Set<PosixFilePermission> getPosixPermissions() throws IOException {
return truffleFile.getPosixPermissions();
@TruffleBoundary
public Text getPosixPermissions() throws IOException {
return Text.create(PosixFilePermissions.toString(truffleFile.getPosixPermissions()));
}
@Builtin.Method(name = "parent")
@CompilerDirectives.TruffleBoundary
@TruffleBoundary
public EnsoObject getParent() {
// Normalization is needed to correctly handle paths containing `..` and `.`.
var parentOrNull = this.normalize().truffleFile.getParent();
@ -179,32 +401,32 @@ public final class EnsoFile implements EnsoObject {
}
@Builtin.Method(name = "absolute")
@CompilerDirectives.TruffleBoundary
@TruffleBoundary
public EnsoFile getAbsoluteFile() {
return new EnsoFile(this.truffleFile.getAbsoluteFile());
}
@Builtin.Method(name = "path")
@CompilerDirectives.TruffleBoundary
@TruffleBoundary
public Text getPath() {
return Text.create(this.truffleFile.getPath());
}
@Builtin.Method
@CompilerDirectives.TruffleBoundary
@TruffleBoundary
public boolean isAbsolute() {
return this.truffleFile.isAbsolute();
}
@Builtin.Method
@CompilerDirectives.TruffleBoundary
@TruffleBoundary
public boolean isDirectory() {
return this.truffleFile.isDirectory();
}
@Builtin.Method(name = "create_directory_builtin")
@Builtin.WrapException(from = IOException.class)
@CompilerDirectives.TruffleBoundary
@TruffleBoundary
public void createDirectories() throws IOException {
try {
this.truffleFile.createDirectories();
@ -282,32 +504,32 @@ public final class EnsoFile implements EnsoObject {
@Builtin.Method(name = "list_immediate_children_array")
@Builtin.WrapException(from = IOException.class)
@CompilerDirectives.TruffleBoundary
@TruffleBoundary
public EnsoObject list() throws IOException {
return ArrayLikeHelpers.wrapEnsoObjects(
this.truffleFile.list().stream().map(EnsoFile::new).toArray(EnsoFile[]::new));
}
@Builtin.Method
@CompilerDirectives.TruffleBoundary
@TruffleBoundary
public EnsoFile relativize(EnsoFile other) {
return new EnsoFile(this.truffleFile.relativize(other.truffleFile));
}
@Builtin.Method
@CompilerDirectives.TruffleBoundary
@TruffleBoundary
public boolean isRegularFile() {
return this.truffleFile.isRegularFile();
}
@Builtin.Method
@CompilerDirectives.TruffleBoundary
@TruffleBoundary
public boolean isWritable() {
return this.truffleFile.isWritable();
}
@Builtin.Method(name = "name")
@CompilerDirectives.TruffleBoundary
@TruffleBoundary
public Text getName() {
var name = this.normalize().truffleFile.getName();
return Text.create(name == null ? "/" : name);
@ -315,7 +537,7 @@ public final class EnsoFile implements EnsoObject {
@Builtin.Method(name = "size_builtin")
@Builtin.WrapException(from = IOException.class)
@CompilerDirectives.TruffleBoundary
@TruffleBoundary
public long getSize() throws IOException {
if (this.truffleFile.isDirectory()) {
throw new IOException("size can only be called on files.");
@ -334,7 +556,7 @@ public final class EnsoFile implements EnsoObject {
}
@Builtin.Method
@CompilerDirectives.TruffleBoundary
@TruffleBoundary
public EnsoFile normalize() {
TruffleFile simplyNormalized = truffleFile.normalize();
String name = simplyNormalized.getName();
@ -347,7 +569,7 @@ public final class EnsoFile implements EnsoObject {
@Builtin.Method(name = "delete_builtin")
@Builtin.WrapException(from = IOException.class)
@CompilerDirectives.TruffleBoundary
@TruffleBoundary
public void delete(boolean recursive) throws IOException {
if (recursive && truffleFile.isDirectory(LinkOption.NOFOLLOW_LINKS)) {
deleteRecursively(truffleFile);
@ -368,25 +590,35 @@ public final class EnsoFile implements EnsoObject {
@Builtin.Method(name = "copy_builtin", description = "Copy this file to a target destination")
@Builtin.WrapException(from = IOException.class)
@Builtin.Specialize
@CompilerDirectives.TruffleBoundary
public void copy(EnsoFile target, Object options, EnsoContext ctx) throws IOException {
CopyOption[] copyOptions =
convertInteropArray(options, InteropLibrary.getUncached(), ctx, CopyOption[]::new);
truffleFile.copy(target.truffleFile, copyOptions);
@TruffleBoundary
public void copy(
EnsoFile target,
Object options,
@Cached ArrayLikeLengthNode lengthNode,
@Cached ArrayLikeAtNode atNode,
EnsoContext ctx)
throws IOException {
var copyOptions = namesToValues(options, lengthNode, atNode, ctx, StandardCopyOption::valueOf);
truffleFile.copy(target.truffleFile, copyOptions.toArray(CopyOption[]::new));
}
@Builtin.Method(name = "move_builtin", description = "Move this file to a target destination")
@Builtin.WrapException(from = IOException.class)
@Builtin.Specialize
@CompilerDirectives.TruffleBoundary
public void move(EnsoFile target, Object options, EnsoContext ctx) throws IOException {
CopyOption[] copyOptions =
convertInteropArray(options, InteropLibrary.getUncached(), ctx, CopyOption[]::new);
truffleFile.move(target.truffleFile, copyOptions);
@TruffleBoundary
public void move(
EnsoFile target,
Object options,
@Cached ArrayLikeLengthNode lengthNode,
@Cached ArrayLikeAtNode atNode,
EnsoContext ctx)
throws IOException {
var copyOptions = namesToValues(options, lengthNode, atNode, ctx, StandardCopyOption::valueOf);
truffleFile.move(target.truffleFile, copyOptions.toArray(CopyOption[]::new));
}
@Builtin.Method
@CompilerDirectives.TruffleBoundary
@TruffleBoundary
public boolean startsWith(EnsoFile parent) {
return truffleFile.startsWith(parent.truffleFile);
}
@ -397,7 +629,7 @@ public final class EnsoFile implements EnsoObject {
"Takes the text representation of a path and returns a TruffleFile corresponding to it.",
autoRegister = false)
@Builtin.Specialize
@CompilerDirectives.TruffleBoundary
@TruffleBoundary
public static EnsoFile fromString(EnsoContext context, String path) {
TruffleFile file = context.getPublicTruffleFile(path);
return new EnsoFile(file);
@ -408,7 +640,7 @@ public final class EnsoFile implements EnsoObject {
description = "A file corresponding to the current working directory.",
autoRegister = false)
@Builtin.Specialize
@CompilerDirectives.TruffleBoundary
@TruffleBoundary
public static EnsoFile currentDirectory(EnsoContext context) {
TruffleFile file = context.getCurrentWorkingDirectory();
return new EnsoFile(file);
@ -419,13 +651,13 @@ public final class EnsoFile implements EnsoObject {
description = "Gets the user's system-defined home directory.",
autoRegister = false)
@Builtin.Specialize
@CompilerDirectives.TruffleBoundary
@TruffleBoundary
public static EnsoFile userHome(EnsoContext context) {
return fromString(context, System.getProperty("user.home"));
}
@Override
@CompilerDirectives.TruffleBoundary
@TruffleBoundary
public String toString() {
return "(File " + truffleFile.getPath() + ")";
}
@ -449,4 +681,10 @@ public final class EnsoFile implements EnsoObject {
Type getType(@Bind("$node") Node node) {
return EnsoContext.get(node).getBuiltins().file();
}
static RuntimeException raiseIOException(Node where, IOException ex) {
var ctx = EnsoContext.get(where);
var guestEx = ctx.asGuestValue(ex);
throw new PanicException(guestEx, where);
}
}

View File

@ -111,8 +111,12 @@ public final class PanicException extends AbstractTruffleException implements En
}
@NeverDefault
static UnresolvedSymbol toDisplayText(IndirectInvokeMethodNode payloads) {
static UnresolvedSymbol toDisplayText(IndirectInvokeMethodNode payloads)
throws UnsupportedMessageException {
var ctx = EnsoContext.get(payloads);
if (ctx == null) {
throw UnsupportedMessageException.create();
}
var scope = ctx.getBuiltins().panic().getDefinitionScope();
return UnresolvedSymbol.build("to_display_text", scope);
}

View File

@ -24,26 +24,31 @@ public final class Parser implements AutoCloseable {
name = "libenso_parser.so";
}
File parser = null;
var whereAmI = Parser.class.getProtectionDomain().getCodeSource().getLocation();
File root;
try {
var whereAmI = Parser.class.getProtectionDomain().getCodeSource().getLocation();
var d = new File(whereAmI.toURI()).getParentFile();
root = new File(whereAmI.toURI()).getParentFile();
} catch (URISyntaxException ex) {
root = new File(".").getAbsoluteFile();
}
try {
var d = root;
File path = null;
while (d != null) {
path = new File(d, name);
if (path.exists()) break;
d = d.getParentFile();
}
if (d == null) {
throw new LinkageError(
"Cannot find parser in " + new File(whereAmI.toURI()).getParentFile());
if (d == null || path == null) {
throw new LinkageError("Cannot find parser in " + root);
}
parser = path;
System.load(parser.getAbsolutePath());
} catch (IllegalArgumentException | URISyntaxException | LinkageError e) {
File root = new File(".").getAbsoluteFile();
System.load(path.getAbsolutePath());
} catch (NullPointerException | IllegalArgumentException | LinkageError e) {
if (!searchFromDirToTop(e, root, "target", "rust", "debug", name)) {
throw new IllegalStateException("Cannot load parser from " + parser, e);
if (!searchFromDirToTop(
e, new File(".").getAbsoluteFile(), "target", "rust", "debug", name)) {
throw new IllegalStateException("Cannot load parser from " + root, e);
}
}
}
}

View File

@ -289,48 +289,6 @@ public @interface Builtin {
WrapException[] value() default {};
}
/**
* Annotation approving implicit {@link
* com.oracle.truffle.api.TruffleLanguage#asGuestValue(Object)} translation done on the return
* object. The conversion is generated automatically, depending on the type of the value.
*
* <p>Note that while explicit translations to interop value are discouraged, we still want to
* occasionally support it to easy builtins-writing process. The presence of the {@link
* ReturningGuestObject} only ensures that it is intentional.
*
* <p>Consider a method returning an {@link java.io.OutputStream} which is not an interop value,
* for the sake of the example:
*
* <pre>
* class Foo {
* {@link Builtin.Method @Builtin.Method}
* {@link Builtin.ReturningGuestObject @Builtin.ReturningGuestObject}
* java.lang.OutputStream foo(Object item) {
* return // ...
* }
* }
* </pre>
*
* The processor will detect the return type of method {@code foo} and perform an automatic
* conversion:
*
* <pre>
* {@link BuiltinMethod @BuiltinMethod}(type = "Foo", name = "create")
* public class CreateFooNode extends Node {
* java.lang.Object execute(Foo self, Object item) {
* return context
* .asGuestValue(self.foo(item));
* }
* }
* </pre>
*
* Without converting the object to the guest language value, it would crash during runtime.
* Without the presence of the annotation, the processor would detect the potential value
* requiring {@link com.oracle.truffle.api.TruffleLanguage#asGuestValue(Object)} translation but
* stop and report the error since it didn't seem to be intended by the user.
*/
@interface ReturningGuestObject {}
/**
* A Method marked with {@link Builtin.Specialize} annotation will generate specializations for
* overloaded and non-overloaded methods. The annotation requires presence of {@link

View File

@ -180,10 +180,6 @@ public class BuiltinsProcessor extends AbstractProcessor {
OK:
if (!TypeWithKind.isValidGuestType(processingEnv, method.getReturnType())) {
if (method.getAnnotation(Builtin.ReturningGuestObject.class) != null) {
// guest objects can be of any type
break OK;
}
if (method.getAnnotation(SuppressWarnings.class) instanceof SuppressWarnings sw
&& Arrays.asList(sw.value()).contains("generic-enso-builtin-type")) {
// assume the case was review

View File

@ -105,8 +105,7 @@ public abstract class MethodGenerator {
Kind.ERROR,
"Automatic conversion of value of type "
+ tpe.baseType()
+ " to guest value requires explicit '@Builtin.ReturningGuestObject'"
+ " annotation");
+ " is no longer supported.");
}
return "Object";
}

View File

@ -8,7 +8,6 @@ import java.util.List;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
import javax.tools.JavaFileObject;
import org.enso.interpreter.dsl.Builtin;
public abstract class MethodNodeClassGenerator {
ClassName builtinNode;
@ -29,7 +28,7 @@ public abstract class MethodNodeClassGenerator {
* @return true if the annotation exists, false otherwise
*/
protected boolean needsGuestValueConversion(Element origin) {
return origin.getAnnotation(Builtin.ReturningGuestObject.class) != null;
return false;
}
public void generate(

View File

@ -41,9 +41,7 @@ public final class NoSpecializationClassGenerator extends MethodNodeClassGenerat
processingEnvironment
.getMessager()
.printMessage(
Kind.ERROR,
"Value is already TruffleObject, don't use @Builtin.ReturningGuestObject",
origin);
Kind.ERROR, "Value is already TruffleObject, don't need any conversions", origin);
}
return new ExecuteMethodImplGenerator(
processingEnvironment, origin, asGuestValue, varArgExpansion);

View File

@ -38,14 +38,12 @@ public final class SpecializedMethodsGenerator extends MethodGenerator {
elements,
first.getModifiers().contains(Modifier.STATIC),
first.getKind() == ElementKind.CONSTRUCTOR,
first.getAnnotation(Builtin.ReturningGuestObject.class) != null,
false,
TypeWithKind.createFromTpe(first.getReturnType().toString()));
// Make sure all methods were defined the same way, except for paramters' types
assert (allEqual(elements.stream().map(e -> e.getModifiers().contains(Modifier.STATIC))));
assert (allEqual(elements.stream().map(e -> e.getKind() == ElementKind.CONSTRUCTOR)));
assert (allEqual(
elements.stream().map(e -> e.getAnnotation(Builtin.ReturningGuestObject.class) != null)));
assert (allEqual(
elements.stream().map(e -> TypeWithKind.createFromTpe(e.getReturnType().toString()))));
}

View File

@ -2,9 +2,84 @@ package org.enso.base;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
public class Stream_Utils {
public final class Stream_Utils {
private Stream_Utils() {}
/**
* Conversion interface. Any Enso/Truffle object with invocable {@read} member that takes three
* arguments is eligible for being treated as host Java {@link InputStream}. There are two
* overloaded {@link #asInputStream conversion methods}. The <em>hosted Java interop</em> system
* of Truffle will pick the more suitable one depending on the type of argument.
*
* @see #asInputStream
*/
public static interface InputStreamLike {
public int read(byte[] arr, int off, int len) throws IOException;
public default int available() {
return 0;
}
}
/**
* No conversion conversion. When the argument is already {@link InputStream} there is no need for
* doing any further conversions.
*
* @param is
* @return the {@code is} itself
*/
public static InputStream asInputStream(InputStream is) {
return is;
}
/**
* Conversion to {@link InputStream}. When the argument <em>looks like</em> an input stream, let's
* wrap it.
*
* @param inputStreamLike any guest object with {@code read} method
* @return proper
*/
public static InputStream asInputStream(InputStreamLike inputStreamLike) {
return new GuestInputStream(inputStreamLike);
}
/**
* Conversion interface. Any Enso/Truffle object with invocable {@write} member that takes three
* arguments is eligible for being treated as host Java {@link OutputStream}. There are two
* overloaded {@link #asOutputStream conversion methods}. The <em>hosted Java interop</em> system
* of Truffle will pick the more suitable one depending on the type of argument.
*
* @see #asOutputStream
*/
public static interface OutputStreamLike {
public void write(byte[] arr, int off, int len) throws IOException;
}
/**
* No conversion conversion. When the argument is already {@link OutputStream} there is no need
* for doing any further conversions.
*
* @param os
* @return the {@code is} itself
*/
public static OutputStream asOutputStream(OutputStream os) {
return os;
}
/**
* Conversion to {@link OutputStream}. When the argument <em>looks like</em> an output stream,
* let's wrap it.
*
* @param outputStreamLike any guest object with {@code write} method
* @return proper
*/
public static OutputStream asOutputStream(OutputStreamLike outputStreamLike) {
return new GuestOutputStream(outputStreamLike);
}
public static byte[] peek(InputStream stream, int n) throws IOException {
assert n >= 0;
assert stream.markSupported();
@ -25,4 +100,64 @@ public class Stream_Utils {
}
return buffer;
}
private static class GuestInputStream extends InputStream {
private final InputStreamLike inputStreamLike;
private GuestInputStream(InputStreamLike inputStreamLike) {
this.inputStreamLike = inputStreamLike;
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
return inputStreamLike.read(b, off, len);
}
@Override
public int read() throws IOException {
byte[] arr = new byte[1];
int read = read(arr, 0, 1);
if (read == -1) {
return -1;
}
if (read != 1) {
throw new IOException();
}
return arr[0];
}
@Override
public int read(byte[] b) throws IOException {
return read(b, 0, b.length);
}
@Override
public int available() throws IOException {
try {
return inputStreamLike.available();
} catch (Error | Exception e) {
return 0;
}
}
}
private static final class GuestOutputStream extends OutputStream {
private final OutputStreamLike outputStreamLike;
private GuestOutputStream(OutputStreamLike os) {
this.outputStreamLike = os;
}
@Override
public void write(int b) throws IOException {
byte[] arr = new byte[] {(byte) b};
write(arr, 0, 1);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
outputStreamLike.write(b, off, len);
}
}
}

View File

@ -1,8 +1,14 @@
package org.enso.base;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.DOMConfiguration;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.bootstrap.DOMImplementationRegistry;
@ -10,6 +16,7 @@ import org.w3c.dom.ls.DOMImplementationLS;
import org.w3c.dom.ls.LSOutput;
import org.w3c.dom.ls.LSSerializer;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
@ -62,7 +69,25 @@ public class XML_Utils {
return out.toString();
}
public static void setCustomErrorHandler(DocumentBuilder documentBuilder) {
public static Document parseStream(InputStream is)
throws ParserConfigurationException, SAXException, IOException {
return doParse(new InputSource(is));
}
public static Document parseString(String text)
throws ParserConfigurationException, SAXException, IOException {
return doParse(new InputSource(new StringReader(text)));
}
private static Document doParse(InputSource is)
throws ParserConfigurationException, SAXException, IOException {
var factory = DocumentBuilderFactory.newInstance();
var builder = factory.newDocumentBuilder();
configureErrorHandler(builder);
return builder.parse(is);
}
private static void configureErrorHandler(DocumentBuilder documentBuilder) {
documentBuilder.setErrorHandler(
new ErrorHandler() {
@Override

View File

@ -0,0 +1,23 @@
package org.enso.base.file_system;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
public final class File_Utils {
private File_Utils() {}
public static Path toPath(String path) {
return Path.of(path);
}
public static PathMatcher matchPath(String filter) {
var fs = FileSystems.getDefault();
var matcher = fs.getPathMatcher(filter);
return matcher;
}
public static boolean matches(PathMatcher matcher, String pathStr) {
return matcher.matches(Path.of(pathStr));
}
}

View File

@ -5,8 +5,8 @@ import Standard.Base.Errors.No_Such_Key.No_Such_Key
from Standard.Test import all
polyglot java import java.nio.file.Path as JavaPath
polyglot java import java.util.Map as JavaMap
polyglot java import org.enso.base.file_system.File_Utils
## Type that violates reflexivity
type My_Nan
@ -105,7 +105,7 @@ add_specs suite_builder =
empty_map_fn = entry.get 1
pending = entry.get 2
add_common_specs suite_builder lang pending empty_map_fn
suite_builder.group "Enso maps" group_builder->
@ -278,11 +278,11 @@ add_specs suite_builder =
map.get (js_str "A") . should_equal 42
group_builder.specify "should support host objects as keys" <|
# JavaPath has proper implementation of hashCode
map = Map.singleton (JavaPath.of "/home/user/file.txt") 42
# java.nio.path.Path has proper implementation of hashCode
map = Map.singleton (File_Utils.toPath "/home/user/file.txt") 42
map.get "X" . should_equal Nothing
map.get "A" . should_equal Nothing
map.get (JavaPath.of "/home/user/file.txt") . should_equal 42
map.get (File_Utils.toPath "/home/user/file.txt") . should_equal 42
group_builder.specify "should support Python objects as keys" pending=pending_python_missing <|
py_obj = py_wrapper 42
@ -464,14 +464,14 @@ add_common_specs suite_builder prefix:Text (pending : (Text | Nothing)) (empty_m
suite_builder.group prefix+": Common polyglot Map operations" pending=pending group_builder->
group_builder.specify "should get the default comparator for polyglot maps" <|
Comparable.from empty_map . should_equal Default_Comparator
group_builder.specify "should compare two hash maps" <|
(empty_map.insert "a" 1).should_equal (empty_map.insert "a" 1)
(empty_map.insert "b" 2).should_not_equal (empty_map.insert "a" 1)
empty_map.should_equal empty_map
empty_map.should_not_equal (empty_map.insert "a" 1)
(empty_map.insert "a" 1 . insert "b" 2).should_equal (empty_map.insert "b" 2 . insert "a" 1)
group_builder.specify "should allow checking for non emptiness" <|
non_empty = empty_map . insert "foo" 1234
empty_map.not_empty . should_be_false
@ -481,7 +481,7 @@ add_common_specs suite_builder prefix:Text (pending : (Text | Nothing)) (empty_m
non_empty = empty_map.insert "a" "b" . insert "x" "y"
empty_map.size . should_equal 0
non_empty.size . should_equal 2
group_builder.specify "should allow checking for emptiness" <|
non_empty = empty_map . insert "foo" 1234
empty_map.is_empty . should_be_true
@ -558,7 +558,11 @@ add_common_specs suite_builder prefix:Text (pending : (Text | Nothing)) (empty_m
empty_map.insert Nothing 1 . insert Nothing 2 . get Nothing . should_equal 2
empty_map.insert Nothing 1 . should_equal (empty_map.insert Nothing 1)
empty_map.insert Nothing 1 . insert Nothing 2 . at Nothing . should_equal 2
group_builder.specify "should handle JavaScript null as keys" <|
empty_map.insert js_null 1 . at Nothing . should_equal 1
group_builder.specify "should handle Python None as keys" pending=pending_python_missing <|
empty_map.insert py_none 1 . at Nothing . should_equal 1
group_builder.specify "should define a well-defined text conversion" <|

View File

@ -85,6 +85,7 @@ import project.Runtime.Stack_Traces_Spec
import project.System.Environment_Spec
import project.System.File_Spec
import project.System.File_Read_Spec
import project.System.Input_Stream_Spec
import project.System.Process_Spec
import project.System.Reporting_Stream_Decoder_Spec
import project.System.Reporting_Stream_Encoder_Spec
@ -112,6 +113,7 @@ main filter=Nothing =
File_Spec.add_specs suite_builder
Temporary_File_Spec.add_specs suite_builder
File_Read_Spec.add_specs suite_builder
Input_Stream_Spec.add_specs suite_builder
Reporting_Stream_Decoder_Spec.add_specs suite_builder
Reporting_Stream_Encoder_Spec.add_specs suite_builder
Http_Header_Spec.add_specs suite_builder

View File

@ -5,8 +5,8 @@ from Standard.Test import all
polyglot java import java.math.BigInteger as Java_Big_Integer
polyglot java import java.nio.file.Path as Java_Path
polyglot java import java.util.Random as Java_Random
polyglot java import org.enso.base.file_system.File_Utils
polyglot java import org.enso.base_test_helpers.IntHolder
polyglot java import org.enso.base_test_helpers.IntHolderEquals
@ -180,8 +180,8 @@ add_specs suite_builder =
((CustomEqType.C1 0) == (CustomEqType.C2 7 3)).should_be_false
group_builder.specify "should dispatch to equals on host values" <|
path1 = Java_Path.of "home" "user" . resolve "file.txt"
path2 = Java_Path.of "home" "user" "file.txt"
path1 = File_Utils.toPath "home" . resolve "user" . resolve "file.txt"
path2 = File_Utils.toPath "home" . resolve "user" . resolve "file.txt"
(path1 == path2).should_be_true
path3 = path1.resolve "subfile.txt"
(path3 == path2).should_be_false

View File

@ -6,12 +6,33 @@ import Standard.Base.System.Input_Stream.Input_Stream
from Standard.Test import all
polyglot java import org.enso.base_test_helpers.RangeStream
polyglot java import org.enso.base.Stream_Utils
main filter=Nothing =
suite = Test.build suite_builder->
add_specs suite_builder
suite.run_with_filter filter
foreign js is_like data available = """
let at = 0
let is = {
read : function(arr, off, len) {
let cnt = 0;
while (len-- > 0) {
arr[off++] = data[at++];
cnt++;
}
return cnt;
}
}
if (available) {
is.available = function() {
return data.length - at;
};
}
return is;
add_specs suite_builder = suite_builder.group "Input Stream" group_builder->
group_builder.specify "should be peekable if backed by memory" <|
Managed_Resource.bracket (Input_Stream.from_bytes [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) (.close) stream->
@ -61,3 +82,33 @@ add_specs suite_builder = suite_builder.group "Input Stream" group_builder->
promoted_stream.peek_bytes 10 . should_equal [100, 101, 102, 103, 104]
# The read still succeeds - ensuring there isn't some early EOF
promoted_stream.read_n_bytes 10 . should_equal [100, 101, 102, 103, 104]
group_builder.specify "read without available" <|
stream_like = is_like [20, 5, 1, 10] False
is = Stream_Utils.asInputStream stream_like
is.available . should_equal 0
is.read . should_equal 20
is.read . should_equal 5
is.available . should_equal 0
is.read . should_equal 1
is.read . should_equal 10
is.available . should_equal 0
group_builder.specify "read with available" <|
stream_like = is_like [20, 6, 8, 23] True
is = Stream_Utils.asInputStream stream_like
is.available . should_equal 4
is.read . should_equal 20
is.read . should_equal 6
is.available . should_equal 2
is.read . should_equal 8
is.read . should_equal 23
is.available . should_equal 0