diff --git a/CHANGELOG.md b/CHANGELOG.md index db408465f99..7a3665440df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date.enso index 9b0a76aa5a8..b023a83fc7e 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date.enso @@ -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. diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index dd4d9008d80..28dd8bca765 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -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. diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeMethodNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeMethodNode.java index dfca6bd78e0..3276e42f366 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeMethodNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InvokeMethodNode.java @@ -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)", diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/resolver/HostMethodCallNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/resolver/HostMethodCallNode.java index 6cb255fa090..8420f51dd3c 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/resolver/HostMethodCallNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/resolver/HostMethodCallNode.java @@ -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; } /** diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/Context.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/Context.java index 8c00f5e76af..2dbcdb9ace0 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/Context.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/Context.java @@ -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 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 getDateConstructor() { + if (date == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + final String stdDateModuleName = "Standard.Base.Data.Time.Date"; + final String stdDateConstructorName = "Date"; + ensureModuleIsLoaded(stdDateModuleName); + Optional dateModule = findModule(stdDateModuleName); + if (dateModule.isPresent()) { + date = Optional.ofNullable(dateModule.get().getScope().getConstructors().get(stdDateConstructorName)); + } else { + date = Optional.empty(); + } + } + return date; + } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/Atom.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/Atom.java index c24916714ea..66224e4492a 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/Atom.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/Atom.java @@ -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; diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/ModuleScope.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/ModuleScope.java index 965624314f3..21f0fb09c81 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/ModuleScope.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/ModuleScope.java @@ -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.*; diff --git a/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/DateTest.scala b/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/DateTest.scala new file mode 100644 index 00000000000..8d72279b2e9 --- /dev/null +++ b/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/DateTest.scala @@ -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") + } + } +} diff --git a/test/Tests/src/Semantic/Java_Interop_Spec.enso b/test/Tests/src/Semantic/Java_Interop_Spec.enso index 19c0c98b052..b4e84e42fd4 100644 --- a/test/Tests/src/Semantic/Java_Interop_Spec.enso +++ b/test/Tests/src/Semantic/Java_Interop_Spec.enso @@ -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