summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--src/nre.nim124
-rw-r--r--test/find.nim4
-rw-r--r--test/init.nim35
-rw-r--r--test/misc.nim4
-rw-r--r--test/split.nim4
5 files changed, 83 insertions, 88 deletions
diff --git a/src/nre.nim b/src/nre.nim
index b960bdc92..b119ea739 100644
--- a/src/nre.nim
+++ b/src/nre.nim
@@ -307,71 +307,61 @@ proc `==`*(a, b: RegexMatch): bool =
 
 # Creation & Destruction {{{
 # PCRE Options {{{
-let Options: Table[string, int] = {
-  "8" : pcre.UTF8,
-  "utf8" : pcre.UTF8,
-  "9" : pcre.NEVER_UTF,
-  "no_utf8" : pcre.NEVER_UTF,
-  "A" : pcre.ANCHORED,
-  "anchored" : pcre.ANCHORED,
-  # "C" : pcre.AUTO_CALLOUT, unsuported XXX
-  "E" : pcre.DOLLAR_ENDONLY,
-  "dollar_endonly" : pcre.DOLLAR_ENDONLY,
-  "f" : pcre.FIRSTLINE,
-  "firstline" : pcre.FIRSTLINE,
-  "i" : pcre.CASELESS,
-  "case_insensitive" : pcre.CASELESS,
-  "m" : pcre.MULTILINE,
-  "multiline" : pcre.MULTILINE,
-  "N" : pcre.NO_AUTO_CAPTURE,
-  "no_auto_capture" : pcre.NO_AUTO_CAPTURE,
-  "s" : pcre.DOTALL,
-  "dotall" : pcre.DOTALL,
-  "U" : pcre.UNGREEDY,
-  "ungreedy" : pcre.UNGREEDY,
-  "u" : pcre.UTF8,
-  "W" : pcre.UCP,
-  "ucp" : pcre.UCP,
-  "X" : pcre.EXTRA,
-  "extra" : pcre.EXTRA,
-  "x" : pcre.EXTENDED,
-  "extended" : pcre.EXTENDED,
-  "Y" : pcre.NO_START_OPTIMIZE,
-  "no_start_optimize" : pcre.NO_START_OPTIMIZE,
-
-  "any"         : pcre.NEWLINE_ANY,
-  "anycrlf"     : pcre.NEWLINE_ANYCRLF,
-  "cr"          : pcre.NEWLINE_CR,
-  "crlf"        : pcre.NEWLINE_CRLF,
-  "lf"          : pcre.NEWLINE_LF,
-  "bsr_anycrlf" : pcre.BSR_ANYCRLF,
-  "bsr_unicode" : pcre.BSR_UNICODE,
-  "js"          : pcre.JAVASCRIPT_COMPAT,
+const PcreOptions = {
+  "NEVER_UTF": pcre.NEVER_UTF,
+  "ANCHORED": pcre.ANCHORED,
+  "DOLLAR_ENDONLY": pcre.DOLLAR_ENDONLY,
+  "FIRSTLINE": pcre.FIRSTLINE,
+  "NO_AUTO_CAPTURE": pcre.NO_AUTO_CAPTURE,
+  "JAVASCRIPT_COMPAT": pcre.JAVASCRIPT_COMPAT,
+  "U": pcre.UTF8 or pcre.UCP
 }.toTable
 
-proc tokenizeOptions(opts: string): tuple[flags: int, study: bool] =
-  result = (0, true)
-
-  var longOpt: string = nil
-  for i, c in opts:
-    # Handle long options {{{
-    if c == '<':
-      longOpt = ""
-      continue
-
-    if longOpt != nil:
-      if c == '>':
-        if longOpt == "no_study":
-          result.study = false
-        else:
-          result.flags = result.flags or Options.fget(longOpt)
-        longOpt = nil
+# Options that are supported inside regular expressions themselves
+const SkipOptions = [
+  "LIMIT_MATCH=", "LIMIT_RECURSION=", "NO_AUTO_POSSESS", "NO_START_OPT",
+  "UTF8", "UTF16", "UTF32", "UTF", "UCP",
+  "CR", "LF", "CRLF", "ANYCRLF", "ANY", "BSR_ANYCRLF", "BSR_UNICODE"
+]
+
+proc extractOptions(pattern: string): tuple[pattern: string, flags: int, study: bool] =
+  result = ("", 0, true)
+
+  var optionStart = 0
+  var equals = false
+  for i, c in pattern:
+    if optionStart == i:
+      if c != '(':
+        break
+      optionStart = i
+
+    elif optionStart == i-1:
+      if c != '*':
+        break
+
+    elif c == ')':
+      let name = pattern[optionStart+2 .. i-1]
+      if equals or name in SkipOptions:
+        result.pattern.add pattern[optionStart .. i]
+      elif PcreOptions.hasKey name:
+        result.flags = result.flags or PcreOptions[name]
+      elif name == "NO_STUDY":
+        result.study = false
       else:
-        longOpt.add(c.toLower)
-      continue
-    # }}}
+        break
+      optionStart = i+1
+      equals = false
+
+    elif not equals:
+      if c == '=':
+        equals = true
+        if pattern[optionStart+2 .. i] notin SkipOptions:
+          break
+      elif c notin {'A'..'Z', '0'..'9', '_'}:
+        break
+
+  result.pattern.add pattern[optionStart .. pattern.high]
 
-    result.flags = result.flags or Options.fget($c)
 # }}}
 
 type UncheckedArray {.unchecked.}[T] = array[0 .. 0, T]
@@ -402,24 +392,22 @@ proc getNameToNumberTable(pattern: Regex): Table[string, int] =
 
     result[name] = num
 
-proc initRegex(pattern: string, options: string): Regex =
+proc initRegex(pattern: string, flags: int, study = true): Regex =
   new(result, destroyRegex)
   result.pattern = pattern
 
   var errorMsg: cstring
   var errOffset: cint
 
-  let opts = tokenizeOptions(options)
-
   result.pcreObj = pcre.compile(cstring(pattern),
                                 # better hope int is at least 4 bytes..
-                                cint(opts.flags), addr errorMsg,
+                                cint(flags), addr errorMsg,
                                 addr errOffset, nil)
   if result.pcreObj == nil:
     # failed to compile
     raise SyntaxError(msg: $errorMsg, pos: errOffset, pattern: pattern)
 
-  if opts.study:
+  if study:
     # XXX investigate JIT
     result.pcreExtra = pcre.study(result.pcreObj, 0x0, addr errorMsg)
     if errorMsg != nil:
@@ -427,7 +415,9 @@ proc initRegex(pattern: string, options: string): Regex =
 
   result.captureNameToId = result.getNameToNumberTable()
 
-proc re*(pattern: string, options = ""): Regex = initRegex(pattern, options)
+proc re*(pattern: string): Regex =
+  let (pattern, flags, study) = extractOptions(pattern)
+  initRegex(pattern, flags, study)
 # }}}
 
 # Operations {{{
diff --git a/test/find.nim b/test/find.nim
index 549fd6ecd..90acaf4e1 100644
--- a/test/find.nim
+++ b/test/find.nim
@@ -21,5 +21,5 @@ suite "find":
     check("".findAll(re"") == @[""])
     check("abc".findAll(re"") == @["", "", "", ""])
     check("word word".findAll(re"\b") == @["", "", "", ""])
-    check("word\r\lword".findAll(re(r"$", "m<anycrlf>")) == @["", ""])
-    check("слово слово".findAll(re(r"\b", "uW")) == @["", "", "", ""])
+    check("word\r\lword".findAll(re"(*ANYCRLF)(?m)$") == @["", ""])
+    check("слово слово".findAll(re"(*U)\b") == @["", "", "", ""])
diff --git a/test/init.nim b/test/init.nim
index 2207afad8..76ffaba19 100644
--- a/test/init.nim
+++ b/test/init.nim
@@ -1,24 +1,30 @@
-import unittest
-import nre
+import unittest, private/pcre
+include nre
 
 suite "Test NRE initialization":
   test "correct intialization":
     check(re("[0-9]+") != nil)
-    check(re("[0-9]+", "i") != nil)
+    check(re("(?i)[0-9]+") != nil)
 
-  test "correct options":
-    expect(SyntaxError):  # ValueError would be bad
-      discard re("[0-9]+",
-        "89AEfimNsUWXxY<any><anycrlf><cr><crlf><lf><bsr_anycrlf><bsr_unicode><js><no_study>")
-    expect(SyntaxError):
-      discard re("[0-9]+",
-        "<utf8><no_utf8><anchored><dollar_endonly><firstline>" &
-        "<case_insensitive><multiline><no_auto_capture><dotall><ungreedy>" &
-        "<ucp><extra><extended><no_start_optimize>")
+  test "options":
+    check(extractOptions("(*NEVER_UTF)") ==
+          ("", pcre.NEVER_UTF, true))
+    check(extractOptions("(*UTF8)(*ANCHORED)(*UCP)z") ==
+          ("(*UTF8)(*UCP)z", pcre.ANCHORED, true))
+    check(extractOptions("(*ANCHORED)(*UTF8)(*JAVASCRIPT_COMPAT)z") ==
+          ("(*UTF8)z", pcre.ANCHORED or pcre.JAVASCRIPT_COMPAT, true))
+
+    check(extractOptions("(*NO_STUDY)(") == ("(", 0, false))
+
+    check(extractOptions("(*LIMIT_MATCH=6)(*ANCHORED)z") ==
+          ("(*LIMIT_MATCH=6)z", pcre.ANCHORED, true))
 
   test "incorrect options":
-    expect(KeyError): discard re("[0-9]+", "a")
-    expect(KeyError): discard re("[0-9]+", "<does_not_exist>")
+    for s in ["CR", "(CR", "(*CR", "(*abc)", "(*abc)CR",
+              "(?i)",
+              "(*LIMIT_MATCH=5", "(*NO_AUTO_POSSESS=5)"]:
+      let ss = s & "(*NEVER_UTF)"
+      check(extractOptions(ss) == (ss, 0, true))
 
   test "invalid regex":
     expect(SyntaxError): discard re("[0-9")
@@ -28,4 +34,3 @@ suite "Test NRE initialization":
       let ex = SyntaxError(getCurrentException())
       check(ex.pos == 4)
       check(ex.pattern == "[0-9")
-
diff --git a/test/misc.nim b/test/misc.nim
index 1340cdc43..f110c3f1e 100644
--- a/test/misc.nim
+++ b/test/misc.nim
@@ -2,8 +2,8 @@ import unittest, nre, strutils, optional_t.nonstrict
 
 suite "Misc tests":
   test "unicode":
-    check("".find(re("", "8")).match == "")
-    check("перевірка".replace(re(r"\w", "uW"), "") == "")
+    check("".find(re"(*UTF8)").match == "")
+    check("перевірка".replace(re"(*U)\w", "") == "")
 
   test "empty or non-empty match":
     check("abc".findall(re"|.").join(":") == ":a::b::c:")
diff --git a/test/split.nim b/test/split.nim
index fc2bbe1b4..8064e40b7 100644
--- a/test/split.nim
+++ b/test/split.nim
@@ -21,8 +21,8 @@ suite "string splitting":
     check("12345".split(re("")) == @["1", "2", "3", "4", "5"])
     check("".split(re"") == newSeq[string]())
     check("word word".split(re"\b") == @["word", " ", "word"])
-    check("word\r\lword".split(re(r"$", "m<anycrlf>")) == @["word", "\r\lword"])
-    check("слово слово".split(re(r"(\b)", "uW")) == @["", "слово", "", " ", "", "слово", ""])
+    check("word\r\lword".split(re"(*ANYCRLF)(?m)$") == @["word", "\r\lword"])
+    check("слово слово".split(re"(*U)(\b)") == @["", "слово", "", " ", "", "слово", ""])
 
   test "perl split tests":
     check("forty-two"                    .split(re"")      .join(",") == "f,o,r,t,y,-,t,w,o")