Skip to content

Commit

Permalink
Improved polyglot Date support (#3559)
Browse files Browse the repository at this point in the history
Significantly improves the polyglot Date support (as introduced by #3374). It enhances the `Date_Spec` to run it in four flavors:
- with Enso Date (as of now)
- with JavaScript Date
- with JavaScript Date wrapped in (JavaScript) array
- with Java LocalDate allocated directly

The code is then improved by necessary modifications to make the `Date_Spec` pass.

# Important Notes
James has requested in [#181755990](https://www.pivotaltracker.com/n/projects/2539304/stories/181755990) - e.g. _Review and improve InMemory Table support for Dates, Times, DateTimes, BigIntegers_ the following program to work:
```
foreign js dateArr = """
return [1, new Date(), 7]

main =
IO.println <| (dateArr.at 1).week_of_year
```
the program works with here in provided changes and prints `27` as of today.

@jdunkerley has provided tests for proper behavior of date in `Table` and `Column`. Those tests are working as of [f16d07e](f16d07e). One just needs to accept `List<Value>` and then query `Value` for `isDate()` when needed.

Last round of changes is related to **exception handling**. 8b686b1 makes sure `makePolyglotError` accepts only polyglot values. Then it wraps plain Java exceptions into `WrapPlainException` with `has_type` method - 60da5e7 - the remaining changes in the PR are only trying to get all tests working in the new setup.

The support for `Time` isn't part of this PR yet.
  • Loading branch information
JaroslavTulach authored Jul 21, 2022
1 parent 3b99e18 commit 4465d63
Show file tree
Hide file tree
Showing 39 changed files with 608 additions and 168 deletions.
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,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]
- [Converting Enso Date to java.time.LocalDate and back][3559]
- [Incremental Reparsing of a Simple Edits][3508]
- [Functions with all-defaulted arguments now execute automatically][3414]
- [Provide `tagValues` for function arguments in the language server][3422]
Expand Down Expand Up @@ -297,7 +297,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
[3559]: https://github.com/enso-org/enso/pull/3559
[3508]: https://github.com/enso-org/enso/pull/3508
[3412]: https://github.com/enso-org/enso/pull/3412
[3414]: https://github.com/enso-org/enso/pull/3414
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ type Engine

internal_pattern = maybe_java_pattern.map_error case _ of
Polyglot_Error err ->
if Java.is_instance err PatternSyntaxException . not then err else
if err.is_a PatternSyntaxException . not then err else
Regex.Syntax_Error err.getMessage
other -> other

Expand Down Expand Up @@ -823,8 +823,8 @@ type Match
handle_error : Any -> (Text | Integer) -> Any
handle_error error id = case error of
Polyglot_Error err ->
is_ioob = Java.is_instance err IndexOutOfBoundsException
is_iae = Java.is_instance err IllegalArgumentException
is_ioob = err.is_a IndexOutOfBoundsException
is_iae = err.is_a IllegalArgumentException
maps_to_no_such_group = is_ioob || is_iae

if maps_to_no_such_group.not then err else
Expand Down
12 changes: 5 additions & 7 deletions distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time.enso
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ type Time

example_date = Time.now.date
date : Date
date = Date.Date self.internal_zoned_date_time.toLocalDate
date = self.internal_zoned_date_time.toLocalDate

## ALIAS Change Time Zone

Expand Down Expand Up @@ -466,13 +466,11 @@ type Time
sign = self.internal_zoned_date_time.compareTo that.internal_zoned_date_time
Ordering.from_sign sign

## Checks if `self` equals `that`.

Arguments:
- that: The other `Time` to compare against.
## Compares two Time for equality.
== : Time -> Boolean
== that =
self.internal_zoned_date_time.equals that.internal_zoned_date_time
== that = case that of
Time _ -> self.internal_zoned_date_time.equals that.internal_zoned_date_time
_ -> False

type Time_Error

Expand Down
61 changes: 25 additions & 36 deletions distribution/lib/Standard/Base/0.0.0-dev/src/Data/Time/Date.enso
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,8 @@ import Standard.Base.Data.Time
import Standard.Base.Data.Time.Duration
import Standard.Base.Data.Time.Time_Of_Day
import Standard.Base.Data.Time.Zone
import Standard.Base.Polyglot

polyglot java import java.time.format.DateTimeFormatter
polyglot java import java.time.Instant
polyglot java import java.time.LocalDate
polyglot java import java.time.temporal.WeekFields
polyglot java import org.enso.base.Time_Utils

## Obtains the current date from the system clock in the system timezone.
Expand All @@ -20,7 +17,7 @@ polyglot java import org.enso.base.Time_Utils

example_now = Date.now
now : Date
now = LocalDate.now
now = @Builtin_Method "Date.now"

## ALIAS Current Date

Expand Down Expand Up @@ -67,9 +64,9 @@ new year (month = 1) (day = 1) =
instead of Enso format. Hopefully this will be fixed with
https://github.com/enso-org/enso/pull/3559
Then this should be switched to use `Panic.catch_java`.
Panic.recover Any (LocalDate.of year month day) . catch Any e-> case e of
Panic.recover Any (Date.internal_new year month day) . catch Any e-> case e of
Polyglot_Error err -> Error.throw (Time.Time_Error err.getMessage)
x -> x
ex -> ex

## ALIAS Date from Text

Expand Down Expand Up @@ -136,27 +133,25 @@ new year (month = 1) (day = 1) =
parse : Text -> (Text | Nothing) -> Date ! Time.Time_Error
parse text pattern=Nothing =
result = Panic.recover Any <| case pattern of
Nothing -> LocalDate.parse text
Text -> LocalDate.parse text (DateTimeFormatter.ofPattern pattern)
Nothing -> Date.internal_parse text 0
Text -> Date.internal_parse text pattern
_ -> Panic.throw (Time.Time_Error "An invalid pattern was provided.")
Date result . map_error <| case _ of
result . map_error <| case _ of
Polyglot_Error err -> Time.Time_Error err.getMessage
x -> x
ex -> ex

type Date

## This type represents a date, often viewed as year-month-day.

Arguments:
- internal_local_date: The internal date representation.

For example, the value "2nd October 2007" can be stored in a `Date`.

This class does not store or represent a time or timezone. Instead, it
is a description of the date, as used for birthdays. It cannot represent
an instant on the time-line without additional information such as an
offset or timezone.
type Date internal_local_date
@Builtin_Type
type Date

## Get the year field.

Expand All @@ -167,7 +162,7 @@ type Date

example_year = Date.now.year
year : Integer
year = self . internal_local_date . getYear
year = @Builtin_Method "Date.year"

## Get the month of year field, as a number from 1 to 12.

Expand All @@ -178,7 +173,7 @@ type Date

example_month = Date.now.month
month : Integer
month = self . internal_local_date . getMonthValue
month = @Builtin_Method "Date.month"

## Get the day of month field.

Expand All @@ -189,7 +184,7 @@ type Date

example_day = Date.now.day
day : Integer
day = self . internal_local_date . getDayOfMonth
day = @Builtin_Method "Date.day"

## Returns the number of week of year this date falls into.

Expand All @@ -205,9 +200,7 @@ type Date
containing the first Thursday of the year. Therefore it is important to
properly specify the `locale` argument.
week_of_year : Locale.Locale -> Integer
week_of_year locale=Locale.default =
field = WeekFields.of locale.java_locale . weekOfYear
self.internal_local_date.get field
week_of_year locale=Locale.default = Time_Utils.week_of_year self locale.java_locale

## ALIAS Date to Time

Expand All @@ -226,7 +219,7 @@ type Date

example_to_time = Date.new 2020 2 3 . to_time Time_Of_Day.new Zone.utc
to_time : Time_Of_Day -> Zone -> Time
to_time time_of_day (zone = Zone.system) = Time.Time (self . internal_local_date . atTime time_of_day.internal_local_time . atZone zone.internal_zone_id)
to_time time_of_day (zone = Zone.system) = Time.Time (Time_Utils.date_with_time self time_of_day.internal_local_time zone.internal_zone_id)

## Add the specified amount of time to this instant to get another date.

Expand All @@ -242,7 +235,7 @@ type Date
example_add = Date.new 2020 + 6.months
+ : Duration -> Date
+ amount = if amount.is_time then Error.throw (Time.Time_Error "Date does not support time intervals") else
Date (self . internal_local_date . plus amount.internal_period)
(Time_Utils.date_adjust self 1 amount.internal_period) . internal_local_date

## Subtract the specified amount of time from this instant to get another
date.
Expand All @@ -259,18 +252,8 @@ 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
(self . internal_local_date . minus amount.internal_period)

## Format this date using the default formatter.

> Example
Convert the current date to text.

import Standard.Base.Data.Time.Date
(Time_Utils.date_adjust self -1 amount.internal_period) . internal_local_date

example_to_text = Date.now.to_text
to_text : Text
to_text = Time_Utils.default_date_formatter . format self.internal_local_date

## A Date to Json conversion.

Expand Down Expand Up @@ -327,7 +310,7 @@ type Date

example_format = Date.new 2020 6 2 . format "yyyyGG"
format : Text -> Text
format pattern = DateTimeFormatter.ofPattern pattern . format self.internal_local_date
format pattern = Time_Utils.local_date_format self pattern

## Compares `self` to `that` to produce an ordering.

Expand All @@ -340,5 +323,11 @@ type Date
(Date.new 2000).compare_to (Date.new 2001)
compare_to : Date -> Ordering
compare_to that =
sign = self.internal_local_date.compareTo that.internal_local_date
sign = Time_Utils.compare_to self that
Ordering.from_sign sign

## Compares two Dates for equality.
== : Date -> Boolean
== that =
sign = Time_Utils.compare_to self that
0 == sign
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ type Time_Of_Day
example_to_time = Time_Of_Day.new 12 30 . to_time (Date.new 2020)
to_time : Date -> Zone -> Time
to_time date (zone = Zone.system) =
Time.Time (self . internal_local_time . atDate date.internal_local_date . atZone zone.internal_zone_id)
Time.Time (self . internal_local_time . atDate date . atZone zone.internal_zone_id)

## Add the specified amount of time to this instant to get a new instant.

Expand Down Expand Up @@ -332,3 +332,9 @@ type Time_Of_Day
compare_to that =
sign = self.internal_local_time.compareTo that.internal_local_time
Ordering.from_sign sign

## Compares two Time_Of_Day for equality.
== : Date -> Boolean
== that = case that of
Time_Of_Day _ -> self.internal_local_time.equals that.internal_local_time
_ -> False
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,7 @@ type Panic
False -> Panic.throw caught_panic
True -> case caught_panic.payload of
Polyglot_Error java_exception ->
case Java.is_instance java_exception panic_type of
case java_exception.is_a panic_type of
True -> handler caught_panic
False -> Panic.throw caught_panic
_ -> Panic.throw caught_panic
Expand Down Expand Up @@ -392,7 +392,7 @@ type Panic
catch_java panic_type ~action handler =
Panic.catch_primitive action caught_panic-> case caught_panic.payload of
Polyglot_Error java_exception ->
case (panic_type == Any) || (Java.is_instance java_exception panic_type) of
case (panic_type == Any) || (java_exception.is_a panic_type) of
True -> handler java_exception
False -> Panic.throw caught_panic
_ -> Panic.throw caught_panic
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -597,7 +597,7 @@ type Http
request : Request -> Response ! Request_Error
request req =
handle_request_error =
Panic.catch_java Any handler=(err-> Error.throw (Request_Error err.getClass.getSimpleName err.getMessage))
Panic.catch_java Any handler=(err-> Error.throw (Request_Error 'IllegalArgumentException' err.getMessage))
Panic.recover Any <| handle_request_error <|
body_publishers = HttpRequest.BodyPublishers
builder = HttpRequest.newBuilder
Expand Down
8 changes: 4 additions & 4 deletions distribution/lib/Standard/Base/0.0.0-dev/src/System/File.enso
Original file line number Diff line number Diff line change
Expand Up @@ -955,10 +955,10 @@ handle_java_exceptions file ~action =

Converts a Java `IOException` into its Enso counterpart.
wrap_io_exception file io_exception =
if Java.is_instance io_exception NoSuchFileException then Error.throw (File_Not_Found file) else
if Java.is_instance io_exception FileAlreadyExistsException then Error.throw (File_Already_Exists_Error file) else
if Java.is_instance io_exception AccessDeniedException then Error.throw (IO_Error file "You do not have permission to access the file") else
Error.throw (IO_Error file "An IO error has occurred: "+io_exception.getMessage)
if io_exception.is_a NoSuchFileException then Error.throw (File_Not_Found file) else
if io_exception.is_a FileAlreadyExistsException then Error.throw (File_Already_Exists_Error file) else
if io_exception.is_a AccessDeniedException then Error.throw (IO_Error file "You do not have permission to access the file") else
Error.throw (IO_Error file "An IO error has occurred: "+io_exception.to_text)

## PRIVATE

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import com.oracle.truffle.api.library.ExportLibrary;
import com.oracle.truffle.api.library.ExportMessage;
import com.oracle.truffle.api.profiles.BranchProfile;
import java.time.LocalDate;
import org.enso.interpreter.epb.node.ContextRewrapExceptionNode;
import org.enso.interpreter.epb.node.ContextRewrapNode;

Expand Down Expand Up @@ -909,4 +910,29 @@ Object getExceptionMessage(
leaveOrigin(node, p);
}
}

@ExportMessage
boolean isDate(
@CachedLibrary("this.delegate") InteropLibrary datum,
@CachedLibrary("this") InteropLibrary node) {
Object p = enterOrigin(node);
try {
return datum.isDate(delegate);
} finally {
leaveOrigin(node, p);
}
}

@ExportMessage
LocalDate asDate(
@CachedLibrary("this.delegate") InteropLibrary datume,
@CachedLibrary("this") InteropLibrary node)
throws UnsupportedMessageException {
Object p = enterOrigin(node);
try {
return datume.asDate(delegate);
} finally {
leaveOrigin(node, p);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo;
import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.data.ArrayRope;
import org.enso.interpreter.runtime.data.EnsoDate;
import org.enso.interpreter.runtime.data.text.Text;
import org.enso.interpreter.runtime.error.*;
import org.enso.interpreter.runtime.library.dispatch.MethodDispatchLibrary;
Expand Down Expand Up @@ -246,15 +247,15 @@ Stateful doConvertDate(
@CachedLibrary(limit = "10") MethodDispatchLibrary methods,
@CachedLibrary(limit = "1") MethodDispatchLibrary dateDispatch,
@CachedLibrary(limit = "10") InteropLibrary interop) {
var ctx = Context.get(this);
try {
var dateConstructor = Context.get(this).getDateConstructor();
Object date = dateConstructor.isPresent() ? dateConstructor.get().newInstance(self) : self;
var hostLocalDate = interop.asDate(self);
var date = new EnsoDate(hostLocalDate);
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(self, symbol), this);
} catch (MethodDispatchLibrary.NoSuchMethodException | UnsupportedMessageException e) {
throw new PanicException(ctx.getBuiltins().error().makeNoSuchMethodError(self, symbol), this);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,11 @@ public boolean isInteropLibrary() {
*/
public static PolyglotCallType getPolyglotCallType(
Object self, String methodName, InteropLibrary library) {
if (library.isMemberInvocable(self, methodName)) {
if (library.isDate(self) && !library.isTime(self)) {
return PolyglotCallType.CONVERT_TO_DATE;
} else if (library.isString(self)) {
return PolyglotCallType.CONVERT_TO_TEXT;
} else if (library.isMemberInvocable(self, methodName)) {
return PolyglotCallType.CALL_METHOD;
} else if (library.isMemberReadable(self, methodName)) {
return PolyglotCallType.GET_MEMBER;
Expand All @@ -90,12 +94,6 @@ public static PolyglotCallType getPolyglotCallType(
return PolyglotCallType.GET_ARRAY_LENGTH;
} else if (library.hasArrayElements(self) && methodName.equals(ARRAY_READ_NAME)) {
return PolyglotCallType.READ_ARRAY_ELEMENT;
} else if (library.isString(self)) {
return PolyglotCallType.CONVERT_TO_TEXT;
} else if (library.isDate(self)) {
if (!library.isTime(self)) {
return PolyglotCallType.CONVERT_TO_DATE;
}
}
return PolyglotCallType.NOT_SUPPORTED;
}
Expand Down
Loading

0 comments on commit 4465d63

Please sign in to comment.