mirror of
https://github.com/enso-org/enso.git
synced 2024-12-23 10:05:06 +03:00
Improved polyglot Date support (#3559)
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
](f16d07e640
). One just needs to accept `List<Value>` and then query `Value` for `isDate()` when needed. Last round of changes is related to **exception handling**.8b686b12bd
makes sure `makePolyglotError` accepts only polyglot values. Then it wraps plain Java exceptions into `WrapPlainException` with `has_type` method -60da5e70ed
- 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.
This commit is contained in:
parent
3b99e18f94
commit
4465d63dd8
@ -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]
|
||||
@ -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
|
||||
|
@ -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
|
||||
|
||||
@ -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
|
||||
|
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
|
@ -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.
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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.
|
||||
@ -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)
|
||||
(Time_Utils.date_adjust self -1 amount.internal_period) . internal_local_date
|
||||
|
||||
## Format this date using the default formatter.
|
||||
|
||||
> Example
|
||||
Convert the current date to text.
|
||||
|
||||
import Standard.Base.Data.Time.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.
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
|
@ -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.
|
||||
|
||||
@ -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
|
||||
|
@ -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
|
||||
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -15,6 +15,7 @@ import com.oracle.truffle.api.library.CachedLibrary;
|
||||
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;
|
||||
|
||||
@ -909,4 +910,29 @@ public class PolyglotProxy implements TruffleObject {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ import org.enso.interpreter.runtime.callable.UnresolvedSymbol;
|
||||
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;
|
||||
@ -246,15 +247,15 @@ public abstract class InvokeMethodNode extends BaseNode {
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -80,7 +80,11 @@ public abstract class HostMethodCallNode extends Node {
|
||||
*/
|
||||
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;
|
||||
@ -90,12 +94,6 @@ public abstract class HostMethodCallNode extends Node {
|
||||
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;
|
||||
}
|
||||
|
@ -0,0 +1,32 @@
|
||||
package org.enso.interpreter.node.expression.builtin.date;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.interop.InteropLibrary;
|
||||
import com.oracle.truffle.api.interop.UnsupportedMessageException;
|
||||
import com.oracle.truffle.api.library.CachedLibrary;
|
||||
import com.oracle.truffle.api.nodes.Node;
|
||||
import org.enso.interpreter.dsl.BuiltinMethod;
|
||||
import org.enso.interpreter.runtime.data.EnsoDate;
|
||||
|
||||
@BuiltinMethod(
|
||||
type = "Date",
|
||||
name = "internal_local_date",
|
||||
description = "Converts any format of a Date to Enso Date")
|
||||
public abstract class ToEnsoDateNode extends Node {
|
||||
public static ToEnsoDateNode build() {
|
||||
return ToEnsoDateNodeGen.create();
|
||||
}
|
||||
|
||||
abstract EnsoDate execute(Object self);
|
||||
|
||||
@Specialization
|
||||
EnsoDate executeConversion(Object self, @CachedLibrary(limit = "3") InteropLibrary iop) {
|
||||
try {
|
||||
return new EnsoDate(iop.asDate(self));
|
||||
} catch (UnsupportedMessageException ex) {
|
||||
CompilerDirectives.transferToInterpreter();
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
}
|
@ -3,10 +3,9 @@ package org.enso.interpreter.node.expression.builtin.interop.generic;
|
||||
import com.oracle.truffle.api.interop.InteropLibrary;
|
||||
import com.oracle.truffle.api.interop.UnsupportedMessageException;
|
||||
import com.oracle.truffle.api.nodes.Node;
|
||||
import com.oracle.truffle.api.profiles.BranchProfile;
|
||||
import org.enso.interpreter.Constants;
|
||||
import org.enso.interpreter.dsl.BuiltinMethod;
|
||||
import org.enso.interpreter.runtime.error.PanicException;
|
||||
import org.enso.interpreter.runtime.data.Array;
|
||||
|
||||
@BuiltinMethod(
|
||||
type = "Polyglot",
|
||||
@ -15,14 +14,12 @@ import org.enso.interpreter.runtime.error.PanicException;
|
||||
public class GetMembersNode extends Node {
|
||||
private @Child InteropLibrary library =
|
||||
InteropLibrary.getFactory().createDispatched(Constants.CacheSizes.BUILTIN_INTEROP_DISPATCH);
|
||||
private final BranchProfile err = BranchProfile.create();
|
||||
|
||||
Object execute(Object self, Object object) {
|
||||
try {
|
||||
return library.getMembers(object);
|
||||
} catch (UnsupportedMessageException e) {
|
||||
err.enter();
|
||||
throw new PanicException(e.getMessage(), this);
|
||||
return Array.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ import org.enso.interpreter.runtime.error.PanicException;
|
||||
description = "Gets the current execution stacktrace.")
|
||||
public class GetStackTraceNode extends Node {
|
||||
Array execute(Object self) {
|
||||
var exception = new PanicException(null, this);
|
||||
var exception = new PanicException("Stacktrace", this);
|
||||
TruffleStackTrace.fillIn(exception);
|
||||
return stackTraceToArray(exception);
|
||||
}
|
||||
|
@ -1,14 +1,15 @@
|
||||
package org.enso.interpreter.node.expression.constant;
|
||||
|
||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||
import com.oracle.truffle.api.interop.TruffleObject;
|
||||
import org.enso.interpreter.node.ExpressionNode;
|
||||
import org.enso.interpreter.runtime.error.PanicException;
|
||||
|
||||
/** Throws a runtime panic containing a statically-known payload. */
|
||||
public class ErrorNode extends ExpressionNode {
|
||||
private final Object payload;
|
||||
private final TruffleObject payload;
|
||||
|
||||
private ErrorNode(Object payload) {
|
||||
private ErrorNode(TruffleObject payload) {
|
||||
this.payload = payload;
|
||||
}
|
||||
|
||||
@ -29,7 +30,7 @@ public class ErrorNode extends ExpressionNode {
|
||||
* @param payload the payload carried by exceptions thrown in the course of this node's execution.
|
||||
* @return a new instance of this node.
|
||||
*/
|
||||
public static ErrorNode build(Object payload) {
|
||||
public static ErrorNode build(TruffleObject payload) {
|
||||
return new ErrorNode(payload);
|
||||
}
|
||||
}
|
||||
|
@ -468,27 +468,4 @@ 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;
|
||||
}
|
||||
}
|
||||
|
@ -75,6 +75,7 @@ public class Builtins {
|
||||
private final BuiltinAtomConstructor debug;
|
||||
private final BuiltinAtomConstructor projectDescription;
|
||||
private final BuiltinAtomConstructor file;
|
||||
private final BuiltinAtomConstructor date;
|
||||
private final BuiltinAtomConstructor warning;
|
||||
|
||||
/**
|
||||
@ -92,7 +93,7 @@ public class Builtins {
|
||||
builtinMethodNodes = readBuiltinMethodsMetadata(scope);
|
||||
registerBuiltinMethods(builtinTypes, scope, language);
|
||||
|
||||
error = new Error(this);
|
||||
error = new Error(this, context);
|
||||
ordering = new Ordering(this);
|
||||
system = new System(this);
|
||||
number = new Number(this);
|
||||
@ -113,6 +114,9 @@ public class Builtins {
|
||||
debug = new BuiltinAtomConstructor(this, Debug.class);
|
||||
projectDescription = new BuiltinAtomConstructor(this, ProjectDescription.class);
|
||||
file = new BuiltinAtomConstructor(this, File.class);
|
||||
date =
|
||||
new BuiltinAtomConstructor(
|
||||
this, org.enso.interpreter.node.expression.builtin.date.Date.class);
|
||||
special = new Special(language);
|
||||
warning = new BuiltinAtomConstructor(this, Warning.class);
|
||||
}
|
||||
@ -413,6 +417,15 @@ public class Builtins {
|
||||
return file.constructor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@code Date} atom constructor.
|
||||
*
|
||||
* @return the {@code Date} atom constructor
|
||||
*/
|
||||
public AtomConstructor date() {
|
||||
return date.constructor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@code Debug} atom constructor. TODO: this is redundant, figure out a way to avoid
|
||||
* createing spurious Debug builtin type
|
||||
|
@ -11,10 +11,21 @@ import org.enso.interpreter.runtime.data.Array;
|
||||
import org.enso.interpreter.runtime.data.text.Text;
|
||||
|
||||
import static com.oracle.truffle.api.CompilerDirectives.transferToInterpreterAndInvalidate;
|
||||
import com.oracle.truffle.api.exception.AbstractTruffleException;
|
||||
import com.oracle.truffle.api.interop.ArityException;
|
||||
import com.oracle.truffle.api.interop.InteropLibrary;
|
||||
import com.oracle.truffle.api.interop.TruffleObject;
|
||||
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;
|
||||
import org.enso.interpreter.runtime.Context;
|
||||
|
||||
/** Container for builtin Error types */
|
||||
public class Error {
|
||||
|
||||
private final Context context;
|
||||
private final BuiltinAtomConstructor syntaxError;
|
||||
private final BuiltinAtomConstructor typeError;
|
||||
private final BuiltinAtomConstructor compileError;
|
||||
@ -42,7 +53,8 @@ public class Error {
|
||||
private static final Text divideByZeroMessage = Text.create("Cannot divide by zero.");
|
||||
|
||||
/** Creates builders for error Atom Constructors. */
|
||||
public Error(Builtins builtins) {
|
||||
public Error(Builtins builtins, Context context) {
|
||||
this.context = context;
|
||||
syntaxError = new BuiltinAtomConstructor(builtins, SyntaxError.class);
|
||||
typeError = new BuiltinAtomConstructor(builtins, TypeError.class);
|
||||
compileError = new BuiltinAtomConstructor(builtins, CompileError.class);
|
||||
@ -132,8 +144,8 @@ public class Error {
|
||||
* @param cause the cause of the error.
|
||||
* @return a runtime representation of the polyglot error.
|
||||
*/
|
||||
public Atom makePolyglotError(Object cause) {
|
||||
return polyglotError.newInstance(cause);
|
||||
public Atom makePolyglotError(Throwable cause) {
|
||||
return polyglotError.newInstance(WrapPlainException.wrap(cause, context));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -207,4 +219,127 @@ public class Error {
|
||||
public Atom makeNotInvokableError(Object target) {
|
||||
return notInvokableError.newInstance(target);
|
||||
}
|
||||
|
||||
/** Represents plain Java exception as a {@link TruffleObject}.
|
||||
*/
|
||||
@ExportLibrary(InteropLibrary.class)
|
||||
static final class WrapPlainException extends AbstractTruffleException {
|
||||
private final AbstractTruffleException prototype;
|
||||
private final Throwable original;
|
||||
|
||||
private WrapPlainException(Throwable cause) {
|
||||
super(cause.getMessage(), cause, AbstractTruffleException.UNLIMITED_STACK_TRACE, null);
|
||||
this.prototype = null;
|
||||
this.original = cause;
|
||||
}
|
||||
|
||||
private WrapPlainException(AbstractTruffleException prototype, Throwable original) {
|
||||
super(prototype);
|
||||
this.prototype = prototype;
|
||||
this.original = original;
|
||||
}
|
||||
|
||||
static AbstractTruffleException wrap(Throwable cause, Context ctx) {
|
||||
var env = ctx.getEnvironment();
|
||||
if (env.isHostException(cause)) {
|
||||
var orig = env.asHostException(cause);
|
||||
return new WrapPlainException((AbstractTruffleException) cause, orig);
|
||||
} else if (cause instanceof AbstractTruffleException truffleEx) {
|
||||
return truffleEx;
|
||||
} else {
|
||||
return new WrapPlainException(cause);
|
||||
}
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
boolean hasExceptionMessage() {
|
||||
return getMessage() != null;
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
public Object getExceptionMessage() {
|
||||
return Text.create(getMessage());
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
String toDisplayString(boolean sideEffects) {
|
||||
return original.toString();
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
Object getMembers(boolean includeInternal) {
|
||||
return Array.empty();
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
boolean hasMembers() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
boolean isMemberInvocable(String member, @CachedLibrary(limit="1") InteropLibrary delegate) {
|
||||
boolean knownMembers = "is_a".equals(member) || "getMessage".equals(member);
|
||||
return knownMembers || (prototype != null && delegate.isMemberInvocable(prototype, member));
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
Object invokeMember(String name, Object[] args, @CachedLibrary(limit="2") InteropLibrary iop) throws ArityException, UnknownIdentifierException, UnsupportedTypeException, UnsupportedMessageException {
|
||||
if ("is_a".equals(name)) {
|
||||
if (args.length != 1) {
|
||||
throw ArityException.create(1,1, args.length);
|
||||
}
|
||||
Object meta;
|
||||
if (iop.isString(args[0])) {
|
||||
meta = args[0];
|
||||
} else {
|
||||
try {
|
||||
meta = iop.getMetaQualifiedName(args[0]);
|
||||
} catch (UnsupportedMessageException e) {
|
||||
meta = args[0];
|
||||
}
|
||||
}
|
||||
if (!iop.isString(meta)) {
|
||||
throw UnsupportedTypeException.create(args, "Provide class or fully qualified name of class to check");
|
||||
}
|
||||
|
||||
return hasType(iop.asString(meta), original.getClass());
|
||||
}
|
||||
if ("getMessage".equals(name)) {
|
||||
return getExceptionMessage();
|
||||
}
|
||||
return iop.invokeMember(this.prototype, name, args);
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
boolean isMemberReadable(String member, @CachedLibrary(limit="1") InteropLibrary delegate) {
|
||||
if (prototype == null) {
|
||||
return false;
|
||||
}
|
||||
return delegate.isMemberReadable(prototype, member);
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
Object readMember(String name, @CachedLibrary(limit="2") InteropLibrary iop) throws UnsupportedMessageException, UnknownIdentifierException {
|
||||
return iop.readMember(this.prototype, name);
|
||||
}
|
||||
|
||||
@CompilerDirectives.TruffleBoundary
|
||||
private static boolean hasType(String fqn, Class<?> type) {
|
||||
if (type == null) {
|
||||
return false;
|
||||
}
|
||||
if (type.getName().equals(fqn)) {
|
||||
return true;
|
||||
}
|
||||
if (hasType(fqn, type.getSuperclass())) {
|
||||
return true;
|
||||
}
|
||||
for (Class<?> interfaceType : type.getInterfaces()) {
|
||||
if (hasType(fqn, interfaceType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
package org.enso.interpreter.runtime.callable.atom;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives;
|
||||
import com.oracle.truffle.api.dsl.Cached;
|
||||
@ -203,22 +202,6 @@ public final 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;
|
||||
|
@ -144,6 +144,11 @@ public class Array implements TruffleObject {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
String toDisplayString(boolean b) {
|
||||
return toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Arrays.toString(items);
|
||||
|
@ -0,0 +1,125 @@
|
||||
package org.enso.interpreter.runtime.data;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives;
|
||||
import com.oracle.truffle.api.dsl.Cached;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.interop.InteropLibrary;
|
||||
import com.oracle.truffle.api.interop.TruffleObject;
|
||||
import com.oracle.truffle.api.library.CachedLibrary;
|
||||
import com.oracle.truffle.api.library.ExportLibrary;
|
||||
import com.oracle.truffle.api.library.ExportMessage;
|
||||
|
||||
import java.time.DateTimeException;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
|
||||
import org.enso.interpreter.dsl.Builtin;
|
||||
import org.enso.interpreter.node.expression.builtin.error.PolyglotError;
|
||||
import org.enso.interpreter.runtime.Context;
|
||||
import org.enso.interpreter.runtime.callable.UnresolvedSymbol;
|
||||
import org.enso.interpreter.runtime.callable.function.Function;
|
||||
import org.enso.interpreter.runtime.data.text.Text;
|
||||
import org.enso.interpreter.runtime.library.dispatch.MethodDispatchLibrary;
|
||||
|
||||
@ExportLibrary(InteropLibrary.class)
|
||||
@ExportLibrary(MethodDispatchLibrary.class)
|
||||
@Builtin(pkg = "date", name = "Date")
|
||||
public final class EnsoDate implements TruffleObject {
|
||||
private final LocalDate date;
|
||||
|
||||
public EnsoDate(LocalDate date) {
|
||||
this.date = date;
|
||||
}
|
||||
|
||||
@Builtin.Method(description = "Return current Date")
|
||||
public static EnsoDate now() {
|
||||
return new EnsoDate(LocalDate.now());
|
||||
}
|
||||
|
||||
@Builtin.Method(name = "internal_parse", description = "Constructs a new Date from text with optional pattern")
|
||||
@Builtin.Specialize
|
||||
@Builtin.WrapException(from = DateTimeParseException.class, to = PolyglotError.class, propagate = true)
|
||||
public static EnsoDate parse(Text text, Object noneOrPattern) {
|
||||
var str = text.getContents().toString();
|
||||
if (noneOrPattern instanceof Text pattern) {
|
||||
var formatter = DateTimeFormatter.ofPattern(pattern.getContents().toString());
|
||||
return new EnsoDate(LocalDate.parse(str, formatter));
|
||||
} else {
|
||||
return new EnsoDate(LocalDate.parse(str));
|
||||
}
|
||||
}
|
||||
|
||||
@Builtin.Method(name = "internal_new", description = "Constructs a new Date from a year, month, and day")
|
||||
@Builtin.WrapException(from = DateTimeException.class, to = PolyglotError.class, propagate = true)
|
||||
public static EnsoDate create(long year, long month, long day) {
|
||||
return new EnsoDate(LocalDate.of(Math.toIntExact(year), Math.toIntExact(month), Math.toIntExact(day)));
|
||||
}
|
||||
|
||||
@Builtin.Method(name = "year", description = "Gets a value of year")
|
||||
public long year() {
|
||||
return date.getYear();
|
||||
}
|
||||
|
||||
@Builtin.Method(name = "month", description = "Gets a value month")
|
||||
public long month() {
|
||||
return date.getMonthValue();
|
||||
}
|
||||
|
||||
@Builtin.Method(name = "day", description = "Gets a value day")
|
||||
public long day() {
|
||||
return date.getDayOfMonth();
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
boolean isDate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
LocalDate asDate() {
|
||||
return date;
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
boolean hasFunctionalDispatch() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
static class GetFunctionalDispatch {
|
||||
@CompilerDirectives.TruffleBoundary
|
||||
static Function doResolve(InteropLibrary my, UnresolvedSymbol symbol) {
|
||||
Context context = Context.get(my);
|
||||
return symbol.resolveFor(context.getBuiltins().date(), context.getBuiltins().any());
|
||||
}
|
||||
|
||||
@Specialization(
|
||||
guards = {"cachedSymbol == symbol", "function != null"},
|
||||
limit = "3")
|
||||
static Function resolveCached(
|
||||
EnsoDate self,
|
||||
UnresolvedSymbol symbol,
|
||||
@Cached("symbol") UnresolvedSymbol cachedSymbol,
|
||||
@CachedLibrary("self") InteropLibrary mySelf,
|
||||
@Cached("doResolve(mySelf, cachedSymbol)") Function function) {
|
||||
return function;
|
||||
}
|
||||
|
||||
@Specialization(replaces = "resolveCached")
|
||||
static Function resolve(
|
||||
EnsoDate self, UnresolvedSymbol symbol, @CachedLibrary("self") InteropLibrary mySelf)
|
||||
throws MethodDispatchLibrary.NoSuchMethodException {
|
||||
Function function = doResolve(mySelf, symbol);
|
||||
if (function == null) {
|
||||
throw new MethodDispatchLibrary.NoSuchMethodException();
|
||||
}
|
||||
return function;
|
||||
}
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
public final Object toDisplayString(boolean allowSideEffects) {
|
||||
return DateTimeFormatter.ISO_LOCAL_DATE.format(date);
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package org.enso.interpreter.runtime.error;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives;
|
||||
import com.oracle.truffle.api.dsl.Cached;
|
||||
import com.oracle.truffle.api.exception.AbstractTruffleException;
|
||||
import com.oracle.truffle.api.interop.*;
|
||||
@ -25,6 +26,10 @@ public class PanicException extends AbstractTruffleException {
|
||||
*/
|
||||
public PanicException(Object payload, Node location) {
|
||||
super(location);
|
||||
if (!InteropLibrary.isValidValue(payload)) {
|
||||
CompilerDirectives.transferToInterpreter();
|
||||
throw new IllegalArgumentException("Only interop values are supported: " + payload);
|
||||
}
|
||||
this.payload = payload;
|
||||
}
|
||||
|
||||
|
@ -9,10 +9,7 @@ import org.enso.interpreter.runtime.callable.UnresolvedSymbol;
|
||||
import org.enso.interpreter.runtime.callable.atom.Atom;
|
||||
import org.enso.interpreter.runtime.callable.atom.AtomConstructor;
|
||||
import org.enso.interpreter.runtime.callable.function.Function;
|
||||
import org.enso.interpreter.runtime.data.Array;
|
||||
import org.enso.interpreter.runtime.data.EnsoFile;
|
||||
import org.enso.interpreter.runtime.data.ManagedResource;
|
||||
import org.enso.interpreter.runtime.data.Ref;
|
||||
import org.enso.interpreter.runtime.data.*;
|
||||
import org.enso.interpreter.runtime.data.text.Text;
|
||||
import org.enso.interpreter.runtime.error.DataflowError;
|
||||
import org.enso.interpreter.runtime.error.PanicException;
|
||||
@ -51,7 +48,8 @@ import org.enso.polyglot.data.TypeGraph;
|
||||
PanicException.class,
|
||||
PanicSentinel.class,
|
||||
Warning.class,
|
||||
EnsoFile.class
|
||||
EnsoFile.class,
|
||||
EnsoDate.class
|
||||
})
|
||||
public class Types {
|
||||
|
||||
|
@ -56,6 +56,7 @@ import org.enso.interpreter.runtime.callable.argument.{
|
||||
ArgumentDefinition,
|
||||
CallArgument
|
||||
}
|
||||
import org.enso.interpreter.runtime.callable.atom.Atom
|
||||
import org.enso.interpreter.runtime.callable.atom.AtomConstructor
|
||||
import org.enso.interpreter.runtime.callable.function.{
|
||||
FunctionSchema,
|
||||
@ -1170,7 +1171,7 @@ class IrToTruffle(
|
||||
* @return a runtime node representing the error.
|
||||
*/
|
||||
def processError(error: IR.Error): RuntimeExpression = {
|
||||
val payload: AnyRef = error match {
|
||||
val payload: Atom = error match {
|
||||
case Error.InvalidIR(_, _, _) =>
|
||||
throw new CompilerError("Unexpected Invalid IR during codegen.")
|
||||
case err: Error.Syntax =>
|
||||
|
@ -13,7 +13,6 @@ import java.util.TreeSet;
|
||||
import org.enso.polyglot.RuntimeOptions;
|
||||
import org.graalvm.polyglot.Context;
|
||||
import org.graalvm.polyglot.Engine;
|
||||
import org.graalvm.polyglot.HostAccess;
|
||||
import org.graalvm.polyglot.Language;
|
||||
import org.graalvm.polyglot.Source;
|
||||
import org.junit.Assert;
|
||||
|
@ -86,9 +86,11 @@ class DateTest extends InterpreterTest {
|
||||
| IO.println ensomonth
|
||||
| IO.println ensoday
|
||||
| IO.println ensotext
|
||||
| ensodate
|
||||
|""".stripMargin
|
||||
eval(code)
|
||||
val ensoDate = eval(code)
|
||||
consumeOut shouldEqual List("2022", "4", "1", "2022-04-01")
|
||||
ensoDate.getMemberKeys().size() shouldEqual 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -57,6 +57,20 @@ class PolyglotTest extends InterpreterTest {
|
||||
)
|
||||
}
|
||||
|
||||
"empty members when message not supported" in {
|
||||
val code =
|
||||
"""from Standard.Base import all
|
||||
|
|
||||
|main =
|
||||
| instance = "Hi There"
|
||||
| members = Polyglot.get_members instance
|
||||
| IO.println members.length
|
||||
| IO.println members
|
||||
|""".stripMargin
|
||||
eval(code)
|
||||
consumeOut shouldEqual List("0", "[]")
|
||||
}
|
||||
|
||||
"match on Polyglot type when imported everything from stdlib" in {
|
||||
val code =
|
||||
"""from Standard.Base import all
|
||||
|
@ -35,7 +35,7 @@ public record SafeWrapException(Attribute.Class from, Attribute.Class to, Boolea
|
||||
return List.of(
|
||||
" } catch (" + from + " e) {",
|
||||
" Builtins builtins = Context.get(this).getBuiltins();",
|
||||
" throw new PanicException(e, this);"
|
||||
" throw new PanicException(e.getMessage(), this);"
|
||||
);
|
||||
} else {
|
||||
return List.of(
|
||||
|
@ -41,6 +41,7 @@ public record TypeWithKind(String baseType, TypeKind kind) {
|
||||
"org.enso.interpreter.runtime.callable.atom.Atom",
|
||||
"org.enso.interpreter.runtime.data.Array",
|
||||
"org.enso.interpreter.runtime.data.EnsoFile",
|
||||
"org.enso.interpreter.runtime.data.EnsoDate",
|
||||
"org.enso.interpreter.runtime.data.ManagedResource",
|
||||
"org.enso.interpreter.runtime.data.Ref",
|
||||
"org.enso.interpreter.runtime.data.text.Text",
|
||||
|
@ -1,14 +1,17 @@
|
||||
package org.enso.base;
|
||||
|
||||
import java.time.DateTimeException;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.Period;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeFormatterBuilder;
|
||||
import java.time.temporal.TemporalAccessor;
|
||||
import java.time.temporal.WeekFields;
|
||||
import java.util.Locale;
|
||||
import java.util.Random;
|
||||
|
||||
/** Utils for standard library operations on Time. */
|
||||
public class Time_Utils {
|
||||
@ -53,6 +56,26 @@ public class Time_Utils {
|
||||
return DateTimeFormatter.ISO_LOCAL_TIME;
|
||||
}
|
||||
|
||||
public static String local_date_format(LocalDate date, Object format) {
|
||||
return DateTimeFormatter.ofPattern(format.toString()).format(date);
|
||||
}
|
||||
|
||||
public static ZonedDateTime date_with_time(LocalDate date, LocalTime time, ZoneId zone) {
|
||||
return date.atTime(time).atZone(zone);
|
||||
}
|
||||
|
||||
public static LocalDate date_adjust(LocalDate date, long add, Period duration) {
|
||||
return add == 1 ? date.plus(duration) : date.minus(duration);
|
||||
}
|
||||
|
||||
public static long week_of_year(LocalDate date, Locale locale) {
|
||||
return WeekFields.of(locale).weekOfYear().getFrom(date);
|
||||
}
|
||||
|
||||
public static int compare_to(LocalDate self, LocalDate that) {
|
||||
return self.compareTo(that);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains an instance of ZonedDateTime from a text string.
|
||||
*
|
||||
|
@ -13,6 +13,7 @@ import org.enso.table.data.index.HashIndex;
|
||||
import org.enso.table.data.index.Index;
|
||||
import org.enso.table.data.mask.OrderMask;
|
||||
import org.enso.table.error.UnexpectedColumnTypeException;
|
||||
import org.graalvm.polyglot.Value;
|
||||
|
||||
/** A representation of a column. Consists of a column name and the underlying storage. */
|
||||
public class Column {
|
||||
@ -116,10 +117,10 @@ public class Column {
|
||||
* @param items the items contained in the column
|
||||
* @return a column with given name and items
|
||||
*/
|
||||
public static Column fromItems(String name, List<Object> items) {
|
||||
public static Column fromItems(String name, List<Value> items) {
|
||||
InferredBuilder builder = new InferredBuilder(items.size());
|
||||
for (Object item : items) {
|
||||
builder.appendNoGrow(item);
|
||||
for (var item : items) {
|
||||
builder.appendNoGrow(item.isDate() ? item.asDate() : item.as(Object.class));
|
||||
}
|
||||
return new Column(name, new DefaultIndex(items.size()), builder.seal());
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package org.enso.table.formatting;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Locale;
|
||||
import org.graalvm.polyglot.Value;
|
||||
|
||||
public class DateFormatter implements DataFormatter {
|
||||
private final DateTimeFormatter formatter;
|
||||
@ -17,6 +18,10 @@ public class DateFormatter implements DataFormatter {
|
||||
return NULL_REPRESENTATION;
|
||||
}
|
||||
|
||||
if (value instanceof Value v && v.isDate()) {
|
||||
value = v.asDate();
|
||||
}
|
||||
|
||||
if (value instanceof LocalDate date) {
|
||||
return date.format(formatter);
|
||||
}
|
||||
@ -26,6 +31,6 @@ public class DateFormatter implements DataFormatter {
|
||||
|
||||
@Override
|
||||
public boolean canFormat(Object value) {
|
||||
return value instanceof LocalDate;
|
||||
return value instanceof LocalDate || (value instanceof Value v && v.isDate());
|
||||
}
|
||||
}
|
||||
|
8
test/Table_Tests/data/prime_ministers.csv
Normal file
8
test/Table_Tests/data/prime_ministers.csv
Normal file
@ -0,0 +1,8 @@
|
||||
Number,Party,Title,From,To
|
||||
71,Conservative,Margaret Thatcher,1979-05-04,1990-11-28
|
||||
72,Conservative,John Major,1990-11-28,1997-05-02
|
||||
73,Labour,Tony Blair,1997-05-02,2007-06-27
|
||||
74,Labour,Gordon Brown,2007-06-27,2010-05-11
|
||||
75,Conservative,David Cameron,2010-05-11,2016-07-13
|
||||
76,Conservative,Theresa May,2016-07-13,2019-07-24
|
||||
77,Conservative,Boris Johnson,2019-07-24,2022-07-07
|
|
@ -143,13 +143,13 @@ spec =
|
||||
|
||||
Test.specify "should format dates" <|
|
||||
formatter = Data_Formatter
|
||||
formatter.format (Date.new 2022 . internal_local_date) . should_equal "2022-01-01"
|
||||
formatter.format (Date.new 2022) . should_equal "2022-01-01"
|
||||
formatter.format (Time.new 1999 . internal_zoned_date_time) . should_equal "1999-01-01 00:00:00"
|
||||
formatter.format (Time_Of_Day.new . internal_local_time) . should_equal "00:00:00"
|
||||
|
||||
Test.specify "should allow custom date formats" <|
|
||||
formatter = Data_Formatter date_formats=["E, d MMM y", "d MMM y[ G]"] datetime_formats=["dd/MM/yyyy HH:mm"] time_formats=["h:mma"] datetime_locale=Locale.uk
|
||||
formatter.format (Date.new 2022 06 21 . internal_local_date) . should_equal "Tue, 21 Jun 2022"
|
||||
formatter.format (Date.new 2022 06 21) . should_equal "Tue, 21 Jun 2022"
|
||||
formatter.format (Time.new 1999 02 03 04 56 11 . internal_zoned_date_time) . should_equal "03/02/1999 04:56"
|
||||
formatter.format (Time_Of_Day.new 13 55 . internal_local_time) . should_equal "1:55pm"
|
||||
|
||||
|
@ -149,7 +149,7 @@ spec =
|
||||
|
||||
Test.specify 'should allow to always quote text and custom values, but for non-text primitves only if absolutely necessary' <|
|
||||
format = File_Format.Delimited "," value_formatter=(Data_Formatter thousand_separator='"' date_formats=["E, d MMM y"]) . with_quotes always_quote=True quote_escape='\\'
|
||||
table = Table.new [['The Column "Name"', ["foo","'bar'",'"baz"', 'one, two, three']], ["B", [1.0, 1000000.5, 2.2, -1.5]], ["C", ["foo", My_Type 44, (Date.new 2022 06 21 . internal_local_date), 42]], ["D", [1,2,3,4000]], ["E", [Nothing, (Time_Of_Day.new 13 55 . internal_local_time), Nothing, Nothing]]]
|
||||
table = Table.new [['The Column "Name"', ["foo","'bar'",'"baz"', 'one, two, three']], ["B", [1.0, 1000000.5, 2.2, -1.5]], ["C", ["foo", My_Type 44, (Date.new 2022 06 21), 42]], ["D", [1,2,3,4000]], ["E", [Nothing, (Time_Of_Day.new 13 55 . internal_local_time), Nothing, Nothing]]]
|
||||
file = (enso_project.data / "transient" / "quote_always.csv")
|
||||
file.delete_if_exists
|
||||
table.write file format on_problems=Report_Error . should_succeed
|
||||
|
54
test/Table_Tests/src/Table_Date_Spec.enso
Normal file
54
test/Table_Tests/src/Table_Date_Spec.enso
Normal file
@ -0,0 +1,54 @@
|
||||
from Standard.Base import all
|
||||
import Standard.Base.Data.Time.Date
|
||||
|
||||
import Standard.Table
|
||||
import Standard.Table.Data.Column
|
||||
import Standard.Table.Io.File_Format
|
||||
from Standard.Table.Data.Data_Formatter as Data_Formatter_Module import Data_Formatter
|
||||
from Standard.Table.Io.Line_Ending_Style import Unix_Line_Endings
|
||||
|
||||
import Standard.Test
|
||||
import project.Util
|
||||
|
||||
spec =
|
||||
c_number = ["Number", [71, 72, 73, 74, 75, 76, 77]]
|
||||
c_party = ["Party", ["Conservative", "Conservative", "Labour", "Labour", "Conservative", "Conservative", "Conservative"]]
|
||||
c_name = ["Title", ["Margaret Thatcher", "John Major", "Tony Blair", "Gordon Brown", "David Cameron", "Theresa May", "Boris Johnson"]]
|
||||
c_from = ["From", [Date.new 1979 05 04, Date.new 1990 11 28, Date.new 1997 05 02, Date.new 2007 06 27, Date.new 2010 05 11, Date.new 2016 07 13, Date.new 2019 07 24]]
|
||||
c_to = ["To", [Date.new 1990 11 28, Date.new 1997 05 02, Date.new 2007 06 27, Date.new 2010 05 11, Date.new 2016 07 13, Date.new 2019 07 24, Date.new 2022 07 07]]
|
||||
expected = Table.new [c_number, c_party, c_name, c_from, c_to]
|
||||
|
||||
Test.group "File.read (Delimited) should work with Dates" <|
|
||||
table = (enso_project.data / "prime_ministers.csv").read
|
||||
Test.specify "should be able to read in a table with dates" <|
|
||||
table.columns.length.should_equal 5
|
||||
table.columns.map (.name) . should_equal ['Number','Party', 'Title', 'From', 'To']
|
||||
table.row_count.should_equal 7
|
||||
|
||||
Test.specify "should be able to treat a single value as a Date" <|
|
||||
from_column = table.at 'From'
|
||||
from_column.at 6 . year . should_equal 2019
|
||||
from_column.at 6 . should_equal (Date.new 2019 7 24)
|
||||
|
||||
Test.specify "should be able to compare columns and table" <|
|
||||
table.at 'Number' . should_equal (Column.from_vector c_number.first c_number.second)
|
||||
table.at 'Party' . should_equal (Column.from_vector c_party.first c_party.second)
|
||||
table.at 'Title' . should_equal (Column.from_vector c_name.first c_name.second)
|
||||
table.at 'From' . should_equal (Column.from_vector c_from.first c_from.second)
|
||||
table.at 'To' . should_equal (Column.from_vector c_to.first c_to.second)
|
||||
table.should_equal expected
|
||||
|
||||
Test.group "Should be able to serialise a table with Dates to Text" <|
|
||||
Test.specify "should serialise back to input" <|
|
||||
expected_text = (enso_project.data / "prime_ministers.csv").read_text
|
||||
delimited = Text.from expected format=(File_Format.Delimited "," line_endings=Unix_Line_Endings)
|
||||
delimited.should_equal expected_text
|
||||
|
||||
Test.specify "should serialise dates with format" <|
|
||||
test_table = Table.new [c_from]
|
||||
expected_text = 'From\n04.05.1979\n28.11.1990\n02.05.1997\n27.06.2007\n11.05.2010\n13.07.2016\n24.07.2019\n'
|
||||
data_formatter = Data_Formatter . with_datetime_formats date_formats=["dd.MM.yyyy"]
|
||||
delimited = Text.from test_table format=(File_Format.Delimited "," value_formatter=data_formatter line_endings=Unix_Line_Endings)
|
||||
delimited.should_equal expected_text
|
||||
|
||||
main = Test.Suite.run_main spec
|
@ -2,6 +2,7 @@ from Standard.Base import all
|
||||
|
||||
import Standard.Base.System
|
||||
import Standard.Table
|
||||
import Standard.Table.Data.Column
|
||||
import Standard.Test
|
||||
|
||||
Table.Table.should_equal expected =
|
||||
@ -10,6 +11,11 @@ Table.Table.should_equal expected =
|
||||
self_cols.map .name . should_equal (that_cols.map .name) frames_to_skip=1
|
||||
self_cols.map .to_vector . should_equal (that_cols.map .to_vector) frames_to_skip=1
|
||||
|
||||
Column.Column.should_equal expected =
|
||||
self.name.should_equal expected.name
|
||||
self.length.should_equal expected.length
|
||||
self.to_vector.should_equal expected.to_vector
|
||||
|
||||
normalize_lines string line_separator=System.default_line_separator newline_at_end=True =
|
||||
case newline_at_end of
|
||||
True -> string.lines.join line_separator suffix=line_separator
|
||||
|
@ -7,58 +7,66 @@ import Standard.Base.Data.Time.Time_Of_Day
|
||||
import Standard.Base.Data.Time.Zone
|
||||
import Standard.Test
|
||||
|
||||
polyglot java import java.time.LocalDate
|
||||
|
||||
spec =
|
||||
Test.group "Date" <|
|
||||
specWith "Date" Date.new Date.parse
|
||||
specWith "JavaScriptDate" js_date js_parse
|
||||
specWith "JavaDate" java_date Date.parse
|
||||
specWith "JavaScriptArrayWithADate" js_array_date Date.parse
|
||||
|
||||
specWith name create_new_date parse_date =
|
||||
Test.group name <|
|
||||
|
||||
Test.specify "should create local date" <|
|
||||
date = Date.new 2020 1 1
|
||||
date = create_new_date 2020 1 1
|
||||
date . year . should_equal 2020
|
||||
date . month . should_equal 1
|
||||
date . day . should_equal 1
|
||||
|
||||
Test.specify "should handle errors when creating local date" <|
|
||||
case Date.new 2020 30 30 . catch of
|
||||
case create_new_date 2020 30 30 . catch of
|
||||
Time.Time_Error msg ->
|
||||
msg . should_equal "Invalid value for MonthOfYear (valid values 1 - 12): 30"
|
||||
result ->
|
||||
Test.fail ("Unexpected result: " + result.to_text)
|
||||
|
||||
Test.specify "should format local date using provided pattern" <|
|
||||
text = Date.new 2020 12 21 . format "yyyyMMdd"
|
||||
text = create_new_date 2020 12 21 . format "yyyyMMdd"
|
||||
text . should_equal "20201221"
|
||||
|
||||
Test.specify "should format local date using default pattern" <|
|
||||
text = Date.new 2020 12 21 . to_text
|
||||
text = create_new_date 2020 12 21 . to_text
|
||||
text . should_equal "2020-12-21"
|
||||
|
||||
Test.specify "should parse default time format" <|
|
||||
text = Date.new 2020 12 21 . to_text
|
||||
date = Date.parse text
|
||||
text = create_new_date 2020 12 21 . to_text
|
||||
date = parse_date text
|
||||
date . year . should_equal 2020
|
||||
date . month . should_equal 12
|
||||
date . day . should_equal 21
|
||||
|
||||
Test.specify "should throw error when parsing invalid date" <|
|
||||
case Date.parse "birthday" . catch of
|
||||
case parse_date "birthday" . catch of
|
||||
Time.Time_Error msg ->
|
||||
msg . should_equal "Text 'birthday' could not be parsed at index 0"
|
||||
result ->
|
||||
Test.fail ("Unexpected result: " + result.to_text)
|
||||
|
||||
Test.specify "should parse local date" <|
|
||||
date = Date.parse "1999-01-01"
|
||||
date = parse_date "1999-01-01"
|
||||
date . year . should_equal 1999
|
||||
date . month . should_equal 1
|
||||
date . day . should_equal 1
|
||||
|
||||
Test.specify "should parse custom format" <|
|
||||
date = Date.parse "1999 1 1" "yyyy M d"
|
||||
date = parse_date "1999 1 1" "yyyy M d"
|
||||
date . year . should_equal 1999
|
||||
date . month . should_equal 1
|
||||
date . day . should_equal 1
|
||||
|
||||
Test.specify "should throw error when parsing custom format" <|
|
||||
date = Date.parse "1999-01-01" "yyyy M d"
|
||||
date = parse_date "1999-01-01" "yyyy M d"
|
||||
case date.catch of
|
||||
Time.Time_Error msg ->
|
||||
msg . should_equal "Text '1999-01-01' could not be parsed at index 4"
|
||||
@ -66,7 +74,7 @@ spec =
|
||||
Test.fail ("Unexpected result: " + result.to_text)
|
||||
|
||||
Test.specify "should convert to time" <|
|
||||
time = Date.new 2000 12 21 . to_time (Time_Of_Day.new 12 30 45) Zone.utc
|
||||
time = create_new_date 2000 12 21 . to_time (Time_Of_Day.new 12 30 45) Zone.utc
|
||||
time . year . should_equal 2000
|
||||
time . month . should_equal 12
|
||||
time . day . should_equal 21
|
||||
@ -77,45 +85,45 @@ spec =
|
||||
time . zone . zone_id . should_equal Zone.utc.zone_id
|
||||
|
||||
Test.specify "should convert to Json" <|
|
||||
date = Date.new 2001 12 21
|
||||
date = create_new_date 2001 12 21
|
||||
date.to_json.should_equal <|
|
||||
Json.from_pairs [["type", "Date"], ["day", date.day], ["month", date.month], ["year", date.year]]
|
||||
|
||||
Test.specify "should add date-based interval" <|
|
||||
date = Date.new 1970 + 1.day
|
||||
date = create_new_date 1970 + 1.day
|
||||
date . year . should_equal 1970
|
||||
date . month . should_equal 1
|
||||
date . day . should_equal 2
|
||||
|
||||
Test.specify "should subtract date-based interval" <|
|
||||
date = Date.new 1970 - 1.year
|
||||
date = create_new_date 1970 - 1.year
|
||||
date . year . should_equal 1969
|
||||
date . month . should_equal 1
|
||||
date . day . should_equal 1
|
||||
|
||||
Test.specify "should support mixed interval operators" <|
|
||||
date = Date.new 1970 + 1.month - 1.year
|
||||
date = create_new_date 1970 + 1.month - 1.year
|
||||
date . year . should_equal 1969
|
||||
date . month . should_equal 2
|
||||
date . day . should_equal 1
|
||||
|
||||
Test.specify "should throw error when adding time-based interval" <|
|
||||
case (Date.new 1970 + 1.hour) . catch of
|
||||
case (create_new_date 1970 + 1.hour) . catch of
|
||||
Time.Time_Error message ->
|
||||
message . should_equal "Date does not support time intervals"
|
||||
result ->
|
||||
Test.fail ("Unexpected result: " + result.to_text)
|
||||
|
||||
Test.specify "should throw error when subtracting time-based interval" <|
|
||||
case (Date.new 1970 - (1.day - 1.minute)) . catch of
|
||||
case (create_new_date 1970 - (1.day - 1.minute)) . catch of
|
||||
Time.Time_Error message ->
|
||||
message . should_equal "Date does not support time intervals"
|
||||
result ->
|
||||
Test.fail ("Unexpected result: " + result.to_text)
|
||||
|
||||
Test.specify "should be comparable" <|
|
||||
date_1 = Date.parse "2021-01-02"
|
||||
date_2 = Date.parse "2021-01-01"
|
||||
date_1 = parse_date "2021-01-02"
|
||||
date_2 = parse_date "2021-01-01"
|
||||
(date_1 == date_2) . should_be_false
|
||||
date_1==date_1 . should_be_true
|
||||
date_1!=date_2 . should_be_true
|
||||
@ -123,11 +131,40 @@ spec =
|
||||
date_1<date_2 . should_be_false
|
||||
|
||||
Test.specify "should return the correct week of year" <|
|
||||
date_1 = Date.parse "2021-08-01"
|
||||
date_1 = parse_date "2021-08-01"
|
||||
date_1.week_of_year Locale.mexico . should_equal 32
|
||||
|
||||
date_2 = Date.parse "2021-01-01"
|
||||
date_2 = parse_date "2021-01-01"
|
||||
date_2.week_of_year Locale.us . should_equal 1
|
||||
date_2.week_of_year Locale.uk . should_equal 0
|
||||
|
||||
main = Test.Suite.run_main spec
|
||||
|
||||
parseNormally x y = (Date.parse x y) . to_text
|
||||
|
||||
js_parse text format=Nothing =
|
||||
d = Date.parse text format
|
||||
js_date d.year d.month d.day
|
||||
|
||||
js_date year month=1 day=1 =
|
||||
Panic.catch Any (js_date_impl year month day) (err -> Error.throw (Time.Time_Error err.payload.cause))
|
||||
|
||||
|
||||
js_array_date year month=1 day=1 =
|
||||
arr = Panic.catch Any (js_array_dateCreate year month day) (err -> Error.throw (Time.Time_Error err.payload.cause))
|
||||
arr.at(0)
|
||||
|
||||
java_date year month=1 day=1 =
|
||||
Panic.catch Any (LocalDate.of year month day) (err -> Error.throw (Time.Time_Error <| err.payload.to_display_text.take (Range 16 Nothing)))
|
||||
|
||||
foreign js js_date_impl year month=1 day=1 = """
|
||||
if (month > 12) {
|
||||
throw `Invalid value for MonthOfYear (valid values 1 - 12): ${month}`;
|
||||
}
|
||||
return new Date(year, month - 1, day);
|
||||
|
||||
foreign js js_array_dateCreate year month day = """
|
||||
if (month > 12) {
|
||||
throw `Invalid value for MonthOfYear (valid values 1 - 12): ${month}`;
|
||||
}
|
||||
return [ new Date(year, month - 1, day) ];
|
||||
|
@ -1,11 +1,11 @@
|
||||
polyglot java import java.time.LocalDate
|
||||
polyglot java import java.time.format.DateTimeFormatter
|
||||
import project.Polyglot
|
||||
|
||||
new year (month = 1) (day = 1) = LocalDate.of year month day
|
||||
new year (month = 1) (day = 1) = (LocalDate.of year month day) . internal_local_date
|
||||
|
||||
type Date
|
||||
type Date internal_local_date
|
||||
year = self . internal_local_date . getYear
|
||||
month = self . internal_local_date . getMonthValue
|
||||
day = self . internal_local_date . getDayOfMonth
|
||||
to_text = DateTimeFormatter.ISO_LOCAL_DATE.format self.internal_local_date
|
||||
@Builtin_Type
|
||||
type Date
|
||||
year = @Builtin_Method "Date.year"
|
||||
month = @Builtin_Method "Date.month"
|
||||
day = @Builtin_Method "Date.day"
|
||||
|
Loading…
Reference in New Issue
Block a user