Enso Date shall be converted to java.time.LocalDate when passed to Java (#3374)

This commit is contained in:
Jaroslav Tulach 2022-04-15 06:02:05 +02:00 committed by GitHub
parent d5d5d3aac5
commit ab692b3b74
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 250 additions and 58 deletions

View File

@ -176,6 +176,7 @@
- [Fixed issues related to constructors' default arguments][3330]
- [Fixed compiler issue related to module cache.][3367]
- [Fixed execution of defaulted arguments of Atom Constructors][3358]
- [Converting Enso Date to java.time.LocalDate and back][3374]
[3227]: https://github.com/enso-org/enso/pull/3227
[3248]: https://github.com/enso-org/enso/pull/3248
@ -184,6 +185,7 @@
[3358]: https://github.com/enso-org/enso/pull/3358
[3360]: https://github.com/enso-org/enso/pull/3360
[3367]: https://github.com/enso-org/enso/pull/3367
[3374]: https://github.com/enso-org/enso/pull/3374
# Enso 2.0.0-alpha.18 (2021-10-12)

View File

@ -20,7 +20,7 @@ polyglot java import org.enso.base.Time_Utils
example_now = Date.now
now : Date
now = Date LocalDate.now
now = LocalDate.now
## ALIAS Current Date
@ -60,7 +60,7 @@ today = here.now
new : Integer -> Integer -> Integer -> Date ! Time.Time_Error
new year (month = 1) (day = 1) =
Panic.recover Any (Date (LocalDate.of year month day)) . catch e-> case e of
Panic.recover Any (LocalDate.of year month day) . catch e-> case e of
Polyglot_Error err -> Error.throw (Time.Time_Error err.getMessage)
x -> x
@ -253,7 +253,7 @@ type Date
example_subtract = Date.new 2020 - 7.days
- : Duration -> Date
- amount = if amount.is_time then Error.throw (Time.Time_Error "Date does not support time intervals") else
Date (this . internal_local_date . minus amount.internal_period)
(this . internal_local_date . minus amount.internal_period)
## Format this date using the default formatter.

View File

@ -547,47 +547,36 @@ For more details about the CI setup, you can check the
### Running Enso
The only component in this repository with a proper executable is the Enso
interpreter. It can be run using the sbt `run` command in the project `runner`
and provides a rudimentary command-line interface to the basic capabilities of
the interpreter.
Enso should be launched using the `distribution/bin` scripts.
#### Interpreter
Interpreter is started with the `distribution/bin/enso` script and requires
`runner.jar` and `runtime.jar` (see
The language interpreter can be started by the `bin/enso` launcher script
located inside of the Enso runtime distribution. Use the following `sbt` command
to compile necessary bits (see
[Building the Interperter CLI Fat Jar](#building-the-interpreter-cli-fat-jar))
to be built and copied (or linked) to the `distribution/component` directory.
and generate the Enso distribution:
##### Bash
```bash
# build runtime.jar and runner.jar
sbt engine-runner/assembly
# link or copy jars to the distributiong
mkdir -p distribution/component
cd distribution/component
ln -s ../../runtime.jar .
ln -s ../../runner.jar .
$ sbt buildEngineDistribution
...
Engine package created at built-distribution/enso-engine-0.0.0-dev-linux-amd64/enso-0.0.0-dev
```
##### PowerShell
```powershell
# build runtime.jar and runner.jar
sbt.bat engine-runner/assembly
# copy jars to the distributiong
mkdir -p .\distribution\component
cp .\runtime.jar .\distribution\component\
cp .\runner.jar .\distribution\component\
sbt.bat buildEngineDistribution
```
Detailed information on the flags it supports is shown by the `--help` flag, but
the primary functionality is as follows:
Then one can execute the launcher:
- `--new PATH`: Creates a new Enso project at the location spcified by `PATH`.
```bash
$ built-distribution/enso-engine-0.0.0-dev-linux-amd64/enso-0.0.0-dev/bin/enso
```
Detailed information on the flags it supports can be shown with the `--help`
flag, but the primary functionality is as follows:
- `--new PATH`: Creates a new Enso project at the location specified by `PATH`.
- `--run PATH`: Executes the interpreter on the Enso source specified by `PATH`.
In this case, `PATH` must point to either a standalone Enso file or an Enso
project.

View File

@ -1,7 +1,6 @@
package org.enso.interpreter.node.callable;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.dsl.*;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.interop.InteropLibrary;
@ -15,7 +14,6 @@ import com.oracle.truffle.api.source.SourceSection;
import java.util.UUID;
import java.util.concurrent.locks.Lock;
import org.enso.interpreter.Language;
import org.enso.interpreter.node.BaseNode;
import org.enso.interpreter.node.callable.dispatch.InvokeFunctionNode;
import org.enso.interpreter.node.callable.resolver.*;
@ -34,7 +32,6 @@ import org.enso.interpreter.runtime.state.Stateful;
public abstract class InvokeMethodNode extends BaseNode {
private @Child InvokeFunctionNode invokeFunctionNode;
private final ConditionProfile errorReceiverProfile = ConditionProfile.createCountingProfile();
private final BranchProfile polyglotArgumentErrorProfile = BranchProfile.create();
private @Child InvokeMethodNode childDispatch;
private final int argumentCount;
private final int thisArgumentPosition;
@ -162,8 +159,7 @@ public abstract class InvokeMethodNode extends BaseNode {
guards = {
"!methods.hasFunctionalDispatch(_this)",
"!methods.hasSpecialDispatch(_this)",
"polyglotCallType != NOT_SUPPORTED",
"polyglotCallType != CONVERT_TO_TEXT"
"polyglotCallType.isInteropLibrary()",
})
Stateful doPolyglot(
VirtualFrame frame,
@ -235,6 +231,34 @@ public abstract class InvokeMethodNode extends BaseNode {
}
}
@Specialization(
guards = {
"!methods.hasFunctionalDispatch(_this)",
"!methods.hasSpecialDispatch(_this)",
"getPolyglotCallType(_this, symbol.getName(), interop) == CONVERT_TO_DATE"
})
Stateful doConvertDate(
VirtualFrame frame,
Object state,
UnresolvedSymbol symbol,
Object _this,
Object[] arguments,
@CachedLibrary(limit = "10") MethodDispatchLibrary methods,
@CachedLibrary(limit = "1") MethodDispatchLibrary dateDispatch,
@CachedLibrary(limit = "10") InteropLibrary interop
) {
try {
var dateConstructor = Context.get(this).getDateConstructor();
Object date = dateConstructor.isPresent() ? dateConstructor.get().newInstance(_this) : _this;
Function function = dateDispatch.getFunctionalDispatch(date, symbol);
arguments[0] = date;
return invokeFunctionNode.execute(function, frame, state, arguments);
} catch (MethodDispatchLibrary.NoSuchMethodException e) {
throw new PanicException(
Context.get(this).getBuiltins().error().makeNoSuchMethodError(_this, symbol), this);
}
}
@Specialization(
guards = {
"!methods.hasFunctionalDispatch(_this)",

View File

@ -43,8 +43,25 @@ public abstract class HostMethodCallNode extends Node {
* org.enso.interpreter.runtime.data.text.Text} and dispatching natively.
*/
CONVERT_TO_TEXT,
/**
* The method call should be handled by converting {@code _this} to a {@code
* Standard.Base.Data.Time.Date} and dispatching natively.
*/
CONVERT_TO_DATE,
/** The method call should be handled by dispatching through the {@code Any} type. */
NOT_SUPPORTED
NOT_SUPPORTED;
/**
* Directly use {@link InteropLibrary}, or not. Types that return false are
* either {@link #NOT_SUPPORTED unsupported} or require
* additional conversions like {@link #CONVERT_TO_TEXT} and {@link #CONVERT_TO_DATE}.
*
* @return true if one can directly pass this object to
* {@link InteropLibrary}
*/
public boolean isInteropLibrary() {
return this != NOT_SUPPORTED && this != CONVERT_TO_TEXT && this != CONVERT_TO_DATE;
}
}
private static final String ARRAY_LENGTH_NAME = "length";
@ -76,9 +93,12 @@ public abstract class HostMethodCallNode extends Node {
return PolyglotCallType.READ_ARRAY_ELEMENT;
} else if (library.isString(_this)) {
return PolyglotCallType.CONVERT_TO_TEXT;
} else {
return PolyglotCallType.NOT_SUPPORTED;
} else if (library.isDate(_this)) {
if (!library.isTime(_this)) {
return PolyglotCallType.CONVERT_TO_DATE;
}
}
return PolyglotCallType.NOT_SUPPORTED;
}
/**

View File

@ -1,5 +1,6 @@
package org.enso.interpreter.runtime;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
import com.oracle.truffle.api.TruffleFile;
import com.oracle.truffle.api.TruffleLanguage;
@ -64,6 +65,8 @@ public class Context {
private final DistributionManager distributionManager;
private final LockManager lockManager;
private final AtomicLong clock = new AtomicLong();
@CompilerDirectives.CompilationFinal
private Optional<AtomConstructor> date;
/**
* Creates a new Enso context.
@ -463,4 +466,24 @@ public class Context {
public long clockTick() {
return clock.getAndIncrement();
}
/** Return the {@code Standard.Base.Data.Time.Date} constructor.
*
* @return optional with {@link AtomConstructor} for the date, if it can be found
*/
public Optional<AtomConstructor> getDateConstructor() {
if (date == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
final String stdDateModuleName = "Standard.Base.Data.Time.Date";
final String stdDateConstructorName = "Date";
ensureModuleIsLoaded(stdDateModuleName);
Optional<Module> dateModule = findModule(stdDateModuleName);
if (dateModule.isPresent()) {
date = Optional.ofNullable(dateModule.get().getScope().getConstructors().get(stdDateConstructorName));
} else {
date = Optional.empty();
}
}
return date;
}
}

View File

@ -1,5 +1,7 @@
package org.enso.interpreter.runtime.callable.atom;
import java.time.LocalDate;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.Specialization;
@ -203,6 +205,21 @@ public class Atom implements TruffleObject {
return true;
}
@ExportMessage
boolean isDate(@CachedLibrary("this") InteropLibrary iop) {
var dateConstructor = Context.get(iop).getDateConstructor();
if (dateConstructor.isPresent()) {
return dateConstructor.get() == this.constructor;
} else {
return false;
}
}
@ExportMessage
LocalDate asDate(@CachedLibrary(limit = "3") InteropLibrary iop) throws UnsupportedMessageException {
return iop.asDate(fields[0]);
}
@ExportMessage
static class GetFunctionalDispatch {
static final int CACHE_SIZE = 10;

View File

@ -1,6 +1,5 @@
package org.enso.interpreter.runtime.scope;
import com.google.common.base.Joiner;
import com.oracle.truffle.api.CompilerDirectives;
import java.util.*;

View File

@ -0,0 +1,94 @@
package org.enso.interpreter.test.semantic
import org.enso.interpreter.test.{InterpreterContext, InterpreterTest}
class DateTest extends InterpreterTest {
override def subject: String = "LocalDate"
override def specify(implicit
interpreterContext: InterpreterContext
): Unit = {
"evaluate a date expression" in {
val code =
s"""from Standard.Builtins import all
|
|import Standard.Base.Data.Time.Date
|
|main =
| IO.println (Date.new 2022 04 01)
|""".stripMargin
eval(code)
consumeOut shouldEqual List("2022-04-01")
}
"print out java date" in {
val code =
s"""from Standard.Builtins import all
|polyglot java import java.time.LocalDate
|
|main =
| IO.println (LocalDate.of 2022 04 01)
|""".stripMargin
eval(code)
consumeOut shouldEqual List("2022-04-01")
}
"send enso date into java" in {
val code =
s"""from Standard.Builtins import all
|polyglot java import java.time.LocalTime
|import Standard.Base.Data.Time.Date
|
|main =
| ensodate = Date.new 2022 04 01
| javatime = LocalTime.of 10 26
| javatimedate = javatime.atDate ensodate
| javadate = javatimedate . toLocalDate
| IO.println javadate
|""".stripMargin
eval(code)
consumeOut shouldEqual List("2022-04-01")
}
"check java date has enso methods" in {
val code =
s"""from Standard.Builtins import all
|polyglot java import java.time.LocalDate
|import Standard.Base.Data.Time.Date
|
|main =
| javadate = LocalDate.of 2022 4 1
| ensoyear = javadate.year
| ensomonth = javadate.month
| ensoday = javadate.day
| ensotext = javadate.to_text
| IO.println ensoyear
| IO.println ensomonth
| IO.println ensoday
| IO.println ensotext
|""".stripMargin
eval(code)
consumeOut shouldEqual List("2022", "4", "1", "2022-04-01")
}
"check enso date has enso methods" in {
val code =
s"""from Standard.Builtins import all
|import Standard.Base.Data.Time.Date
|
|main =
| ensodate = Date.new 2022 4 1
| ensoyear = ensodate.year
| ensomonth = ensodate.month
| ensoday = ensodate.day
| ensotext = ensodate.to_text
| IO.println ensoyear
| IO.println ensomonth
| IO.println ensoday
| IO.println ensotext
|""".stripMargin
eval(code)
consumeOut shouldEqual List("2022", "4", "1", "2022-04-01")
}
}
}

View File

@ -9,22 +9,46 @@ polyglot java import java.lang.String
polyglot java import java.lang.StringBuilder as Java_String_Builder
polyglot java import java.util.ArrayList
spec = Test.group "Java FFI" <|
Test.specify "should call methods imported from Java" <|
Long.sum 1 2 . should_equal 3
import Standard.Base.Data.Time.Date
polyglot java import java.time.LocalDate
polyglot java import java.time.LocalTime
Test.specify "should call constructors imported from Java" <|
list = ArrayList.new
list.add 432
list.get 0 . should_equal 432
Test.specify "should auto-convert numeric types across the polyglot boundary" <|
(Float.valueOf "123.3" + 5).should_equal 128.3 epsilon=0.0001
(Integer.sum 1 2 + 3) . should_equal 6
Test.specify "should auto-convert strings across the polyglot boundary" <|
(String.format "%s bar %s" "baz" "quux" + " foo").should_equal "baz bar quux foo"
Test.specify "should support Java import renaming" <|
builder = Java_String_Builder.new
builder.append "foo"
builder.append "bar"
str = builder.toString
str.should_equal "foobar"
spec =
Test.group "Java FFI" <|
Test.specify "should call methods imported from Java" <|
Long.sum 1 2 . should_equal 3
Test.specify "should call constructors imported from Java" <|
list = ArrayList.new
list.add 432
list.get 0 . should_equal 432
Test.specify "should auto-convert numeric types across the polyglot boundary" <|
(Float.valueOf "123.3" + 5).should_equal 128.3 epsilon=0.0001
(Integer.sum 1 2 + 3) . should_equal 6
Test.specify "should auto-convert strings across the polyglot boundary" <|
(String.format "%s bar %s" "baz" "quux" + " foo").should_equal "baz bar quux foo"
Test.specify "should support Java import renaming" <|
builder = Java_String_Builder.new
builder.append "foo"
builder.append "bar"
str = builder.toString
str.should_equal "foobar"
Test.group "Java/Enso Date" <|
Test.specify "Java date has Enso properties" <|
april1st = LocalDate.of 2022 04 01
april1st.year.should_equal 2022
april1st.month.should_equal 4
april1st.day.should_equal 1
Test.specify "send Enso date into Java" <|
ensodate = Date.new 2022 04 01
javatime = LocalTime.of 10 26
javatimedate = javatime.atDate ensodate
april1st = javatimedate . toLocalDate
april1st.year.should_equal 2022
april1st.month.should_equal 4
april1st.day.should_equal 1
main = Test.Suite.run_main here.spec