Skip to content

Commit

Permalink
Merge pull request #3512 from fable-compiler/various_improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
MangelMaxime authored Aug 25, 2023
2 parents 2e2fc98 + 8a0af3a commit dd4d6c9
Show file tree
Hide file tree
Showing 22 changed files with 375 additions and 153 deletions.
2 changes: 2 additions & 0 deletions src/Fable.Cli/RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
* Always delete the `fable_modules` folder when the cache is invalidated
* Remove `--typescript` options support, use `--lang <target>` instead
* Fix #3441: Don't ignore error when loading plugin
* Fix #3482: Remove `Py.python` and `Py.expr_python` use `emitPyStatement` and `emitPyExpr` instead
* Restrict replacements to accept only functions from their target language module

### 4.1.4

Expand Down
7 changes: 0 additions & 7 deletions src/Fable.Core/Fable.Core.Py.fs
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,6 @@ module Py =
[<Emit("lambda *args: $0(args)")>]
let argsFunc (fn: obj[] -> obj): Callable = nativeOnly

/// Embeds literal Python code into F#. Code will be printed as statements,
/// if you want to return a value use Python `return` keyword within a function.
let python (template: string): 'T = nativeOnly

/// Embeds a literal Python expression into F#
let expr_python (template: string): 'T = nativeOnly

/// Defines a Jupyter-like code cell. Translates to `# %%`
/// https://code.visualstudio.com/docs/python/jupyter-support-py
[<Emit("# %%", isStatement=true)>]
Expand Down
10 changes: 5 additions & 5 deletions src/Fable.Core/Fable.Core.PyInterop.fs
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@ let ($) (callee: obj) (args: obj): 'a = nativeOnly
let (==>) (key: string) (v: obj): string*obj = nativeOnly

/// Destructure a tuple of arguments and applies to literal Python code as with EmitAttribute.
/// E.g. `emitExpr (arg1, arg2) "$0 + $1"` in Python becomes `arg1 + arg2`
let emitExpr<'T> (args: obj) (pyCode: string): 'T = nativeOnly
/// E.g. `emitPyExpr (arg1, arg2) "$0 + $1"` in Python becomes `arg1 + arg2`
let emitPyExpr<'T> (args: obj) (pyCode: string): 'T = nativeOnly

/// Same as emitExpr but intended for Python code that must appear in a statement position
/// E.g. `emitStatement aValue "while($0 < 5) doSomething()"`
let emitStatement<'T> (args: obj) (pyCode: string): 'T = nativeOnly
/// Same as emitPyExpr but intended for Python code that must appear in a statement position
/// E.g. `emitPyStatement aValue "while($0 < 5) doSomething()"`
let emitPyStatement<'T> (args: obj) (pyCode: string): 'T = nativeOnly

/// Create a literal Python object from a collection of key-value tuples.
/// E.g. `createObj [ "a" ==> 5 ]` in Python becomes `{ a: 5 }`
Expand Down
4 changes: 0 additions & 4 deletions src/Fable.Core/Fable.Core.Rust.fs
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,6 @@ type ReferenceTypeAttribute(pointerType: PointerType) =
type UnsafeAttribute() =
inherit Attribute()

/// Destructure a tuple of arguments and apply them to literal code as with EmitAttribute.
/// E.g. `emitExpr (arg1, arg2) "$0 + $1"` becomes `arg1 + arg2`
let emitExpr<'T> (args: obj) (code: string): 'T = nativeOnly

/// Works like `ImportAttribute` (same semantics as Dart imports).
/// You can use "*" selector.
let import<'T> (selector: string) (path: string): 'T = nativeOnly
Expand Down
10 changes: 10 additions & 0 deletions src/Fable.Core/Fable.Core.RustInterop.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module Fable.Core.RustInterop

open System

/// Implicit cast for erased unions (U2, U3...)
let inline (!^) (x:^t1) : ^t2 = ((^t1 or ^t2) : (static member op_ErasedCast : ^t1 -> ^t2) x)

/// Destructure a tuple of arguments and apply them to literal code as with EmitAttribute.
/// E.g. `emitRustExpr (arg1, arg2) "$0 + $1"` becomes `arg1 + arg2`
let emitRustExpr<'T> (args: obj) (code: string): 'T = nativeOnly
1 change: 1 addition & 0 deletions src/Fable.Core/Fable.Core.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
<Compile Include="Fable.Core.JsInterop.fs" />
<Compile Include="Fable.Core.PhpInterop.fs" />
<Compile Include="Fable.Core.PyInterop.fs" />
<Compile Include="Fable.Core.RustInterop.fs" />
<Compile Include="Fable.Core.Extensions.fs" />
<Content Include="RELEASE_NOTES.md" />
</ItemGroup>
Expand Down
8 changes: 8 additions & 0 deletions src/Fable.Core/RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
### Unreleased

* Fix #3482: Remove `Py.python` and `Py.expr_python`
* Add `!^` to `Fable.Core.RustInterop` module
* Fix #3484: Rename `emitStatement` to `emitPyStatement` in `PyInterop`
* Fix #3484: Rename `emitExpr` to `emitPyExpr` in `PyInterop`
* Fix #3484: Replace `Rust.emitExpr` with `RustInterop.emitRustExpr`

### 4.0.0

* Fable 4 stable
Expand Down
11 changes: 2 additions & 9 deletions src/Fable.Transforms/Python/Replacements.fs
Original file line number Diff line number Diff line change
Expand Up @@ -867,14 +867,7 @@ let fableCoreLib (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Exp
| "typedArrays" -> makeBoolConst com.Options.TypedArrays |> Some
| "extension" -> makeStrConst com.Options.FileExtension |> Some
| _ -> None
| "Fable.Core.Py", ("python" | "expr_python" as meth) ->
let isStatement = meth <> "expr_python"
match args with
| RequireStringConstOrTemplate com ctx r template::_ ->
emitTemplate r t [] isStatement template |> Some
| _ -> None
| "Fable.Core.PyInterop", _
| "Fable.Core.JsInterop", _ ->
| "Fable.Core.PyInterop", _ ->
match i.CompiledName, args with
| Naming.StartsWith "import" suffix, _ ->
match suffix, args with
Expand Down Expand Up @@ -918,7 +911,7 @@ let fableCoreLib (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Exp
"$0($1...)"
|> emitExpr r t (callee :: args)
|> Some
| Naming.StartsWith "emit" rest, [ args; macro ] ->
| Naming.StartsWith "emitPy" rest, [ args; macro ] ->
match macro with
| RequireStringConstOrTemplate com ctx r template ->
let args = destructureTupleArgs [ args ]
Expand Down
11 changes: 7 additions & 4 deletions src/Fable.Transforms/Rust/Replacements.fs
Original file line number Diff line number Diff line change
Expand Up @@ -704,16 +704,19 @@ let fableCoreLib (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Exp
| "typedArrays" -> makeBoolConst com.Options.TypedArrays |> Some
| "extension" -> makeStrConst com.Options.FileExtension |> Some
| _ -> None
| "Fable.Core.JsInterop", "op_BangHat"-> List.tryHead args
| "Fable.Core.RustInterop", "op_BangHat"-> List.tryHead args
| "Fable.Core.RustInterop", _ ->
match i.CompiledName, args with
| "emitRustExpr", [args; RequireStringConstOrTemplate com ctx r template] ->
let args = destructureTupleArgs [args]
emitTemplate r t args false template |> Some
| _ -> None
| "Fable.Core.Rust", _ ->
match i.CompiledName, args with
| "import", [RequireStringConst com ctx r selector; RequireStringConst com ctx r path] ->
makeImportUserGenerated r t selector path |> Some
| "importAll", [RequireStringConst com ctx r path] ->
makeImportUserGenerated r t "*" path |> Some
| "emitExpr", [args; RequireStringConstOrTemplate com ctx r template] ->
let args = destructureTupleArgs [args]
emitTemplate r t args false template |> Some
| _ -> None
| _ -> None

Expand Down
117 changes: 80 additions & 37 deletions src/fable-library-dart/Date.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ void _assertNonUnspecified(int kind) {
}
}

final _parseFormat = RegExp(r'^\+?(?<date>(?<date1>\d+)[\/\-.](?<date2>\d+)(?:[\/\-.](?<date3>\d+))?(?:T|\s+))?(?<time>\d+:\d+(?::\d+(?:\.\d+)?)?)?\s*(?<ampm>[AaPp][Mm])?\s*(?<offset>Z|[+-](?<offsethours>[01]?\d):?(?<offsetminutes>[0-5]?\d)?)?$');
final _parseFormat = RegExp(
r'^\+?(?<date>(?<date1>\d+)[\/\-.](?<date2>\d+)(?:[\/\-.](?<date3>\d+))?(?:T|\s+))?(?<time>\d+:\d+(?::\d+(?:\.\d+)?)?)?\s*(?<ampm>[AaPp][Mm])?\s*(?<offset>Z|[+-](?<offsethours>[01]?\d):?(?<offsetminutes>[0-5]?\d)?)?$');

DateTime parse(String input) {
input = input.trim();
Expand Down Expand Up @@ -106,17 +107,18 @@ DateTime parse(String input) {
utc = true;
if (offset.toUpperCase() != "Z") {
final offsetMinutes = m.namedGroup("offsetminutes");
offsetTotalMinutes =
int.parse(m.namedGroup("offsethours")!) * 60
+ (offsetMinutes != null ? int.parse(offsetMinutes) : 0);
offsetTotalMinutes = int.parse(m.namedGroup("offsethours")!) * 60 +
(offsetMinutes != null ? int.parse(offsetMinutes) : 0);
if (offset.startsWith("-")) {
offsetTotalMinutes = -offsetTotalMinutes;
}
}
}

final parsedDate = utc ? DateTime.utc(year, month, day, hours, minutes, secondsInt, 0, microSeconds) :
DateTime(year, month, day, hours, minutes, secondsInt, 0, microSeconds);
final parsedDate = utc
? DateTime.utc(
year, month, day, hours, minutes, secondsInt, 0, microSeconds)
: DateTime(year, month, day, hours, minutes, secondsInt, 0, microSeconds);

// DateTime will be UTC in this case so it's safe to add time, see:
// https://medium.com/pinch-nl/datetime-and-daylight-saving-time-in-dart-9c9468633b5d
Expand All @@ -137,7 +139,11 @@ bool tryParse(String input, types.FSharpRef<DateTime> defaultValue) {
}

DateTime create(int year, int month, int day,
[int hour = 0, int min = 0, int sec = 0, int millisec = 0, int kind = DateTimeKind.Local]) {
[int hour = 0,
int min = 0,
int sec = 0,
int millisec = 0,
int kind = DateTimeKind.Local]) {
_assertNonUnspecified(kind);
return kind == DateTimeKind.Utc
? DateTime.utc(year, month, day, hour, min, sec, millisec)
Expand All @@ -150,11 +156,18 @@ DateTime specifyKind(DateTime d, int kind) {
}

Duration timeOfDay(DateTime d) {
return Duration(hours: d.hour, minutes: d.minute, seconds: d.second, milliseconds: d.millisecond, microseconds: d.microsecond);
return Duration(
hours: d.hour,
minutes: d.minute,
seconds: d.second,
milliseconds: d.millisecond,
microseconds: d.microsecond);
}

DateTime date(DateTime d) {
return d.isUtc ? DateTime.utc(d.year, d.month, d.day) : DateTime(d.year, d.month, d.day);
return d.isUtc
? DateTime.utc(d.year, d.month, d.day)
: DateTime(d.year, d.month, d.day);
}

int year(DateTime d) => d.year;
Expand Down Expand Up @@ -184,8 +197,10 @@ DateTime addYears(DateTime d, int v) {
final _daysInMonth = daysInMonth(newYear, newMonth);
final newDay = math.min(_daysInMonth, d.day);
return d.isUtc
? DateTime.utc(newYear, newMonth, newDay, d.hour, d.minute, d.second, d.millisecond, d.microsecond)
: DateTime(newYear, newMonth, newDay, d.hour, d.minute, d.second, d.millisecond, d.microsecond);
? DateTime.utc(newYear, newMonth, newDay, d.hour, d.minute, d.second,
d.millisecond, d.microsecond)
: DateTime(newYear, newMonth, newDay, d.hour, d.minute, d.second,
d.millisecond, d.microsecond);
}

DateTime addMonths(DateTime d, int v) {
Expand All @@ -207,8 +222,10 @@ DateTime addMonths(DateTime d, int v) {
final _daysInMonth = daysInMonth(newYear, newMonth);
final newDay = math.min(_daysInMonth, d.day);
return d.isUtc
? DateTime.utc(newYear, newMonth, newDay, d.hour, d.minute, d.second, d.millisecond, d.microsecond)
: DateTime(newYear, newMonth, newDay, d.hour, d.minute, d.second, d.millisecond, d.microsecond);
? DateTime.utc(newYear, newMonth, newDay, d.hour, d.minute, d.second,
d.millisecond, d.microsecond)
: DateTime(newYear, newMonth, newDay, d.hour, d.minute, d.second,
d.millisecond, d.microsecond);
}

DateTime add(DateTime d, Duration ts) {
Expand All @@ -217,8 +234,13 @@ DateTime add(DateTime d, Duration ts) {
final oldTzOffset = d.timeZoneOffset;
final newTzOffset = newDate.timeZoneOffset;
return oldTzOffset != newTzOffset
? newDate.add(newTzOffset - oldTzOffset)
: newDate;
// Maxime Mangel: I am not sure why but the next line needs to have
// different reversed compared to other languages implementations
// Without that, I have an error on the test "Adding days to a local date works even if daylight saving time changes"
// when executing from Europe/Paris timezone
// If this breaks another timezone, we need to investigate for a more robust detection
? newDate.add(oldTzOffset - newTzOffset)
: newDate;
} else {
return newDate;
}
Expand All @@ -229,7 +251,8 @@ DateTime subtract(DateTime d, Duration ts) {
}

Duration subtractDate(DateTime d1, DateTime d2) {
return Duration(microseconds: d1.microsecondsSinceEpoch - d2.microsecondsSinceEpoch);
return Duration(
microseconds: d1.microsecondsSinceEpoch - d2.microsecondsSinceEpoch);
}

int compare(DateTime dt1, DateTime dt2) {
Expand All @@ -240,7 +263,8 @@ DateTime addDays(DateTime d, double v) => add(d, timespan.fromDays(v));
DateTime addHours(DateTime d, double v) => add(d, timespan.fromHours(v));
DateTime addMinutes(DateTime d, double v) => add(d, timespan.fromMinutes(v));
DateTime addSeconds(DateTime d, double v) => add(d, timespan.fromSeconds(v));
DateTime addMilliseconds(DateTime d, double v) => add(d, timespan.fromMilliseconds(v));
DateTime addMilliseconds(DateTime d, double v) =>
add(d, timespan.fromMilliseconds(v));
DateTime addTicks(DateTime d, int v) => add(d, Duration(microseconds: v ~/ 10));

DateTime now() {
Expand All @@ -256,10 +280,9 @@ DateTime today() {
}

bool isLeapYear(int year) =>
year % 4 == 0 && year % 100 != 0 || year % 400 == 0;
year % 4 == 0 && year % 100 != 0 || year % 400 == 0;

int daysInMonth(int year, int month) =>
month == 2
int daysInMonth(int year, int month) => month == 2
? (isLeapYear(year) ? 29 : 28)
: (month >= 8 ? (month % 2 == 0 ? 31 : 30) : (month % 2 == 0 ? 30 : 31));

Expand All @@ -283,8 +306,9 @@ String dateOffsetToString(Duration offset) {
final hours = offsetMinutes ~/ 3600000;
final minutes = (offsetMinutes % 3600000) ~/ 60000;
return (isMinus ? "-" : "+") +
util.padWithZeros(hours, 2) + ":" +
util.padWithZeros(minutes, 2);
util.padWithZeros(hours, 2) +
":" +
util.padWithZeros(minutes, 2);
}

String toDateOnlyString(DateTime date) {
Expand All @@ -294,7 +318,7 @@ String toDateOnlyString(DateTime date) {

String toTimeOnlyString(DateTime date) {
final str = date.toIso8601String();
return str.substring(str.indexOf("T") + 1, str.length - (date.isUtc ? 1 : 0));
return str.substring(str.indexOf("T") + 1, str.length - (date.isUtc ? 1 : 0));
}

String dateToISOString(DateTime d, [bool? utc]) {
Expand All @@ -304,8 +328,8 @@ String dateToISOString(DateTime d, [bool? utc]) {
d = utc ? d.toUtc() : d.toLocal();
}
return utc
? d.toIso8601String()
: d.toIso8601String() + dateOffsetToString(d.timeZoneOffset * -60000);
? d.toIso8601String()
: d.toIso8601String() + dateOffsetToString(d.timeZoneOffset * -60000);
}

String dateToStringWithCustomFormat(DateTime date, String format, [bool? utc]) {
Expand All @@ -318,21 +342,37 @@ String dateToStringWithCustomFormat(DateTime date, String format, [bool? utc]) {
switch (matchValue.substring(0, 1)) {
case "y":
final y = date.year;
rep = matchValue.length < 4 ? y % 100 : y; break;
case "M": rep = date.month; break;
case "d": rep = date.day; break;
case "H": rep = date.hour; break;
rep = matchValue.length < 4 ? y % 100 : y;
break;
case "M":
rep = date.month;
break;
case "d":
rep = date.day;
break;
case "H":
rep = date.hour;
break;
case "h":
final h = date.hour;
rep = h > 12 ? h % 12 : h; break;
case "m": rep = date.minute; break;
case "s": rep = date.second; break;
case "f": rep = date.millisecond; break;
rep = h > 12 ? h % 12 : h;
break;
case "m":
rep = date.minute;
break;
case "s":
rep = date.second;
break;
case "f":
rep = date.millisecond;
break;
}
if (rep == null) {
return matchValue;
} else {
return (rep < 10 && matchValue.length > 1) ? "0" + rep.toString() : "" + rep.toString();
return (rep < 10 && matchValue.length > 1)
? "0" + rep.toString()
: "" + rep.toString();
}
});
}
Expand All @@ -342,11 +382,14 @@ String toString(DateTime date, [String? format]) {
return date.toString();
} else if (format.length == 1) {
switch (format) {
case "D": case "d":
case "D":
case "d":
return toDateOnlyString(date);
case "T": case "t":
case "T":
case "t":
return toTimeOnlyString(date);
case "O": case "o":
case "O":
case "o":
return dateToISOString(date);
default:
throw Exception("Unrecognized Date print format");
Expand Down
Loading

0 comments on commit dd4d6c9

Please sign in to comment.