Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion src/Fable.Transforms/Beam/Replacements.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3516,13 +3516,25 @@ let mailbox (com: ICompiler) (_ctx: Context) r t (i: CallInfo) (thisArg: Expr op

let private regex
(com: ICompiler)
(_ctx: Context)
(ctx: Context)
r
(t: Type)
(info: CallInfo)
(thisArg: Expr option)
(args: Expr list)
=
checkRegexOptions
com
ctx
r
(RegexOption.IgnoreCase
||| RegexOption.Multiline
||| RegexOption.Compiled
||| RegexOption.Singleline
||| RegexOption.ECMAScript
||| RegexOption.NonBacktracking)
args

match info.CompiledName, thisArg, args with
// Constructor
| ".ctor", _, [ pattern ] -> Helper.LibCall(com, "fable_regex", "create", t, [ pattern ], ?loc = r) |> Some
Expand Down
12 changes: 12 additions & 0 deletions src/Fable.Transforms/Dart/Replacements.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3309,6 +3309,18 @@ let regexMatchToSeq com t e =
Helper.LibCall(com, "RegExp", "GroupIterable", t, [ e ])

let regex com (ctx: Context) r t (i: CallInfo) (thisArg: Expr option) (args: Expr list) =
checkRegexOptions
com
ctx
r
(RegexOption.IgnoreCase
||| RegexOption.Multiline
||| RegexOption.Compiled
||| RegexOption.Singleline
||| RegexOption.ECMAScript
||| RegexOption.NonBacktracking)
args

let isGroup =
function
| ExprType(DeclaredTypeFullName Types.regexGroup) -> true
Expand Down
12 changes: 12 additions & 0 deletions src/Fable.Transforms/Python/Replacements.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3513,6 +3513,18 @@ let regex com (ctx: Context) r t (i: CallInfo) (thisArg: Expr option) (args: Exp
let propInt p callee = getExpr r t callee (makeIntConst p)
let propStr p callee = getExpr r t callee (makeStrConst p)

checkRegexOptions
com
ctx
r
(RegexOption.IgnoreCase
||| RegexOption.Multiline
||| RegexOption.Compiled
||| RegexOption.Singleline
||| RegexOption.ECMAScript
||| RegexOption.NonBacktracking)
args

let isGroup =
match thisArg with
| Some(ExprType(DeclaredTypeFullName Types.regexGroup)) -> true
Expand Down
99 changes: 93 additions & 6 deletions src/Fable.Transforms/Replacements.Util.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1282,18 +1282,63 @@ let (|CustomOp|_|) (com: ICompiler) (ctx: Context) r t opName (argExprs: Expr li
| Some x -> ValueSome x
| None -> ValueNone

module RegexOption =
[<Literal>]
let IgnoreCase = 1

[<Literal>]
let Multiline = 2

[<Literal>]
let ExplicitCapture = 4

[<Literal>]
let Compiled = 8

[<Literal>]
let Singleline = 16

[<Literal>]
let IgnorePatternWhitespace = 32

[<Literal>]
let RightToLeft = 64

[<Literal>]
let ECMAScript = 256

[<Literal>]
let CultureInvariant = 512

[<Literal>]
let NonBacktracking = 1024

let names =
[
IgnoreCase, "IgnoreCase"
Multiline, "Multiline"
ExplicitCapture, "ExplicitCapture"
Compiled, "Compiled"
Singleline, "Singleline"
IgnorePatternWhitespace, "IgnorePatternWhitespace"
RightToLeft, "RightToLeft"
ECMAScript, "ECMAScript"
CultureInvariant, "CultureInvariant"
NonBacktracking, "NonBacktracking"
]

[<return: Struct>]
let (|RegexFlags|_|) e =
let rec getFlags =
function
| NumberConst(NumberValue.Int32 value, _) ->
match value with
| 1 -> Some [ RegexIgnoreCase ]
| 2 -> Some [ RegexMultiline ]
| 8 -> Some [] // Compiled flag (ignored)
| 16 -> Some [ RegexSingleline ]
| 256 -> Some [] // ECMAScript flag (ignored)
| 1024 -> Some [] // NonBacktracking flag (ignored)
| RegexOption.IgnoreCase -> Some [ RegexIgnoreCase ]
| RegexOption.Multiline -> Some [ RegexMultiline ]
| RegexOption.Compiled -> Some []
| RegexOption.Singleline -> Some [ RegexSingleline ]
| RegexOption.ECMAScript -> Some []
| RegexOption.NonBacktracking -> Some []
| _ -> None
| Operation(Binary(BinaryOrBitwise, flags1, flags2), _, _, _) ->
match getFlags flags1, getFlags flags2 with
Expand All @@ -1305,6 +1350,48 @@ let (|RegexFlags|_|) e =
| Some x -> ValueSome x
| None -> ValueNone

[<return: Struct>]
let (|RegexOptionsConst|_|) (e: Expr) =
let rec fold =
function
| NumberConst(NumberValue.Int32 value, _) -> Some value
| Operation(Binary(BinaryOrBitwise, left, right), _, _, _) ->
match fold left, fold right with
| Some left, Some right -> Some(left ||| right)
| _ -> None
| _ -> None

match e.Type, fold e with
| Number(Int32, NumberInfo.IsEnum ent), Some value when ent.FullName = Types.regexOptions -> ValueSome value
| _ -> ValueNone

let checkRegexOptions (com: ICompiler) (ctx: Context) r (supported: int) (args: Expr list) =
for arg in args do
match arg with
| RegexOptionsConst options when options &&& ~~~supported <> 0 ->
let unsupported = options &&& ~~~supported

let names =
RegexOption.names
|> List.choose (fun (bit, name) ->
if unsupported &&& bit <> 0 then
Some $"RegexOptions.%s{name}"
else
None
)

let names =
if List.isEmpty names then
[ $"RegexOptions value 0x%X{unsupported}" ]
else
names

let names = String.concat ", " names

$"%s{names} is not supported by Fable on this target"
|> addError com ctx.InlinePath r
| _ -> ()

[<return: Struct>]
let (|UniversalFableCoreHelpers|_|) (com: ICompiler) (ctx: Context) r t (i: CallInfo) args error =
function
Expand Down
12 changes: 12 additions & 0 deletions src/Fable.Transforms/Replacements.fs
Original file line number Diff line number Diff line change
Expand Up @@ -4055,6 +4055,18 @@ let regex com (ctx: Context) r t (i: CallInfo) (thisArg: Expr option) (args: Exp
let propInt p callee = getExpr r t callee (makeIntConst p)
let propStr p callee = getExpr r t callee (makeStrConst p)

checkRegexOptions
com
ctx
r
(RegexOption.IgnoreCase
||| RegexOption.Multiline
||| RegexOption.Compiled
||| RegexOption.Singleline
||| RegexOption.ECMAScript
||| RegexOption.NonBacktracking)
args

let isGroup =
match thisArg with
| Some(ExprType(DeclaredTypeFullName Types.regexGroup)) -> true
Expand Down
13 changes: 13 additions & 0 deletions src/Fable.Transforms/Rust/Replacements.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2968,6 +2968,19 @@ let bclNativeImpl com ctx r t i moduleName memberName (thisArg: Expr option) (ar
| None -> makeStaticMemberCall com r t i moduleName memberName args

let regex com (ctx: Context) r t (i: CallInfo) (thisArg: Expr option) (args: Expr list) =
checkRegexOptions
com
ctx
r
(RegexOption.IgnoreCase
||| RegexOption.Multiline
||| RegexOption.Compiled
||| RegexOption.Singleline
||| RegexOption.IgnorePatternWhitespace
||| RegexOption.ECMAScript
||| RegexOption.NonBacktracking)
args

match i.CompiledName with
// | "GetEnumerator" -> getEnumerator com r t i thisArg.Value |> Some
| meth ->
Expand Down
2 changes: 2 additions & 0 deletions src/Fable.Transforms/Transforms.Util.fs
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,8 @@ module Types =
[<Literal>]
let regexCaptureCollection = "System.Text.RegularExpressions.CaptureCollection"

let regexOptions = "System.Text.RegularExpressions.RegexOptions"

[<Literal>]
let random = "System.Random"

Expand Down
4 changes: 4 additions & 0 deletions src/fable-library-beam/fable_regex.erl
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,10 @@ get_options(#{options := O}) -> O.

%% --- Internal helpers ---

options_to_erl(Options) when is_integer(Options), Options band bnot (1 bor 2 bor 8 bor 16 bor 256 bor 1024) =/= 0 ->
erlang:error(
<<"RegexOptions only supports: IgnoreCase, Multiline, Compiled, Singleline, ECMAScript and NonBacktracking">>
);
options_to_erl(Options) when is_integer(Options) ->
Flags = [],
%% IgnoreCase
Expand Down
4 changes: 4 additions & 0 deletions src/fable-library-py/fable_library/reg_exp.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ def GetEnumerator(self, __unit: Unit = UNIT) -> IEnumerator[Match[str]]:

def _options_to_flags(options: int) -> int:
"""Convert .NET options to Python flags."""
if options & ~(1 | 2 | 8 | 16 | 256 | 1024):
raise Exception(
"RegexOptions only supports: IgnoreCase, Multiline, Compiled, Singleline, ECMAScript and NonBacktracking"
)
flags = 256
if options & 1:
flags |= re.IGNORECASE
Expand Down
18 changes: 18 additions & 0 deletions src/fable-library-py/tests/test_reg_exp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import pytest
from fable_library.reg_exp import create


def test_accepted_options_compile() -> None:
ignore_case = 1
compiled = 8
non_backtracking = 1024
regex = create("ab", ignore_case | compiled | non_backtracking)

assert regex.search("xABx") is not None


def test_unsupported_option_raises() -> None:
right_to_left = 64

with pytest.raises(Exception, match="RegexOptions only supports"):
create("ab", right_to_left)
1 change: 1 addition & 0 deletions tests/Integration/Compiler/Fable.Tests.Compiler.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
<Compile Include="CompilerHelpersTests.fs" />
<Compile Include="InflateTests.fs" />
<Compile Include="WatchDependencyTests.fs" />
<Compile Include="RegexOptionsTests.fs" />
<Compile Include="Main.fs" />
</ItemGroup>
</Project>
1 change: 1 addition & 0 deletions tests/Integration/Compiler/Main.fs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ let allTests =
CompilerHelpers.tests
Inflate.tests
WatchDependency.tests
RegexOptions.tests
]


Expand Down
56 changes: 56 additions & 0 deletions tests/Integration/Compiler/RegexOptionsTests.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
module Fable.Tests.Compiler.RegexOptions

open Util.Testing
open Fable.Tests.Compiler.Util
open Fable.Tests.Compiler.Util.Compiler

let private compile source = Compiler.Cached.compile Compiler.Settings.standard source

let tests =
testList "Regex Options" [
testCase "An unsupported option on the constructor is a compile error" <| fun _ ->
let source =
"""
open System.Text.RegularExpressions

let r = Regex("a", RegexOptions.RightToLeft)
"""
compile source
|> Assert.Is.error
|> Assert.Exists.errorWith "RegexOptions.RightToLeft is not supported"
|> ignore

testCase "Only the unsupported options of a combination are reported" <| fun _ ->
let source =
"""
open System.Text.RegularExpressions

let found = Regex.IsMatch("abc", "b", RegexOptions.IgnoreCase ||| RegexOptions.ExplicitCapture ||| RegexOptions.CultureInvariant)
"""
compile source
|> Assert.Exists.errorWith "RegexOptions.ExplicitCapture, RegexOptions.CultureInvariant is not supported"
|> ignore

testCase "Options that only affect performance compile without a diagnostic" <| fun _ ->
let source =
"""
open System.Text.RegularExpressions

let r = Regex("a", RegexOptions.Compiled ||| RegexOptions.NonBacktracking ||| RegexOptions.IgnoreCase)
let parts = Regex.Split("a1b", "\\d", RegexOptions.ECMAScript)
"""
compile source
|> Assert.Is.strictSuccess
|> ignore

testCase "Options that are only known at run time are left to the runtime check" <| fun _ ->
let source =
"""
open System.Text.RegularExpressions

let make (options: RegexOptions) = Regex("a", options)
"""
compile source
|> Assert.Is.strictSuccess
|> ignore
]
Loading