lib/re: fix offset of submatches and clarify variables name
[nit.git] / lib / core / re.nit
index 7d588ca..33ab76c 100644 (file)
@@ -22,6 +22,7 @@
 module re
 
 import text
+intrude import text::flat
 import gc
 import error
 
@@ -35,7 +36,7 @@ in "C Header" `{
 # It is recommanded to use the higher level API offered by the class `Regex`,
 # but it can still be used for advanced purpose or in optimized code.
 #
-# To use this class and other `private` entities of this module, use `intrude import standard::re`
+# To use this class and other `private` entities of this module, use `intrude import core::re`
 private extern class NativeRegex `{ regex_t* `}
        # Allocate a new `NativeRegex`, it must then be compiled using `regcomp` before calling `regexec`
        new malloc `{ return malloc(sizeof(regex_t)); `}
@@ -152,11 +153,11 @@ class Regex
        # Ignore case when matching letters
        var ignore_case = false is writable
 
-       # Optimize `self` for `is_in` and `String::has`, but do not support searches
+       # Optimize `self` for `String::has` and `is_in`, but do not support searches
        #
        # If `true`, `self` cannont be used with `String::search_all`, `String::replace`
        # or `String::split`.
-       var optimize_is_in = false is writable
+       var optimize_has = false is writable
 
        # Treat a newline in string as dividing string into multiple lines
        #
@@ -207,7 +208,7 @@ class Regex
                var cflags = 0
                if extended then cflags |= flag_extended
                if ignore_case then cflags |= flag_icase
-               if optimize_is_in then cflags |= flag_nosub
+               if optimize_has then cflags |= flag_nosub
                if newline then cflags |= flag_newline
 
                var native = self.native
@@ -271,6 +272,9 @@ class Regex
 
        private fun get_error(errcode: Int): String
        do
+               var native = native
+               assert native != null
+
                # Error, should be out of memory but we cover any possible error anyway
                var error_cstr = native.regerror(errcode)
 
@@ -290,6 +294,9 @@ class Regex
                var comp_res = compile
                assert comp_res == null else "Regex compilation failed with: {comp_res.message}\n".output
 
+               var native = native
+               assert native != null
+
                # Actually execute
                var eflags = gather_eflags
                var res = native.regexec_match_only(text.to_cstring, eflags)
@@ -306,7 +313,7 @@ class Regex
                abort
        end
 
-       # require: not optimize_is_in
+       # require: not optimize_has
        #
        #     assert "l".to_re.search_index_in("hello world", 0) == 2
        #     assert "el+o".to_re.search_index_in("hello world", 0) == 1
@@ -314,11 +321,14 @@ class Regex
        #     assert "z".to_re.search_index_in("hello world", 0) == -1
        redef fun search_index_in(text, from)
        do
-               assert not optimize_is_in
+               assert not optimize_has
 
                var comp_res = compile
                assert comp_res == null else "Regex compilation failed with: {comp_res.message}\n".output
 
+               var native = native
+               assert native != null
+
                # Actually execute
                text = text.to_s
                var cstr = text.substring_from(from).to_cstring
@@ -339,22 +349,28 @@ class Regex
                abort
        end
 
-       # require: not optimize_is_in
+       # require: not optimize_has
        #
        #     assert "l".to_re.search_in("hello world", 0).from == 2
        #     assert "el+o".to_re.search_in("hello world", 0).from == 1
        #     assert "l+".to_re.search_in("hello world", 3).from == 3
        #     assert "z".to_re.search_in("hello world", 0) == null
+       #     assert "cd(e)".to_re.search_in("abcdef", 2)[1].to_s == "e"
        redef fun search_in(text, from)
        do
-               assert not optimize_is_in
+               assert not optimize_has
 
                var comp_res = compile
                assert comp_res == null else "Regex compilation failed with: {comp_res.message}\n".output
 
+               var native = native
+               assert native != null
+
                # Actually execute
                text = text.to_s
-               var cstr = text.substring_from(from).to_cstring
+               var sub = text.substring_from(from)
+               var cstr = sub.to_cstring
+               var bstr = new FlatString.full(cstr, sub.bytelen, 0, sub.bytelen - 1, text.length - from)
                var eflags = gather_eflags
                var native_match = self.native_match
 
@@ -363,15 +379,19 @@ class Regex
 
                # Found one?
                if res == 0 then
+                       var first_char = bstr.byte_to_char_index(native_match.rm_so)
+                       var last_char = bstr.byte_to_char_index(native_match.rm_eo - native_match.rm_so - 1)
                        var match = new Match(text,
-                               from + native_match.rm_so,
-                               native_match.rm_eo - native_match.rm_so)
+                               from + first_char,
+                               last_char + 1)
 
                        # Add sub expressions
-                       for i in [1..nsub] do
+                       for i in [1 .. nsub] do
+                               first_char = bstr.byte_to_char_index(native_match[i].rm_so)
+                               last_char = bstr.byte_to_char_index(native_match[i].rm_eo - native_match[i].rm_so - 1)
                                match.subs.add new Match( text,
-                                       native_match[i].rm_so,
-                                       native_match[i].rm_eo - native_match[i].rm_so)
+                                       from + first_char,
+                                       last_char + 1)
                        end
 
                        return match
@@ -386,17 +406,20 @@ class Regex
                abort
        end
 
-       # require: not optimize_is_in
+       # require: not optimize_has
        #
        #     assert "ab".to_re.search_all_in("abbab").join(", ") == "ab, ab"
        #     assert "b+".to_re.search_all_in("abbabaabbbbbcab").join(", ") == "bb, b, bbbbb, b"
        redef fun search_all_in(text)
        do
-               assert not optimize_is_in
+               assert not optimize_has
 
                var comp_res = compile
                assert comp_res == null else "Regex compilation failed with: {comp_res.message}\n".output
 
+               var native = native
+               assert native != null
+
                # Actually execute
                text = text.to_s
                var cstr = text.to_cstring