regex: bug fixes, docs

pull/3489/head
penguindark 2020-01-18 07:38:00 +01:00 committed by Alexander Medvednikov
parent ad7bc37672
commit 36660ce749
3 changed files with 95 additions and 68 deletions

View File

@ -4,14 +4,14 @@
## introduction ## introduction
Write here the introduction Write here the introduction... not today!! -_-
## Basic assumption ## Basic assumption
In this release, during the writing of the code some assumption are made and are valid for all the features. In this release, during the writing of the code some assumptions are made and are valid for all the features.
1. The matching stops at the end of the string not at the newline chars. 1. The matching stops at the end of the string not at the newline chars.
2. The basic element of this regex engine are the tokens, in query string a simple char is a token. The token is the atomic unit of this regex engine. 2. The basic elements of this regex engine are the tokens, in a query string a simple char is a token. The token is the atomic unit of this regex engine.
## Match positional limiter ## Match positional limiter
@ -21,11 +21,11 @@ The module supports the following features:
`^` (Caret.) Matches at the start of the string `^` (Caret.) Matches at the start of the string
`?` Matches at the end of the string `$` Matches at the end of the string
## Tokens ## Tokens
The tokens are the atomic unit used by this regex engine and can be ones of the following: The tokens are the atomic units used by this regex engine and can be ones of the following:
### Simple char ### Simple char
@ -33,11 +33,11 @@ this token is a simple single character like `a`.
### Char class (cc) ### Char class (cc)
The cc match all the chars specified in its inside, it is delimited by square brackets `[ ]` The cc match all the chars specified inside, it is delimited by square brackets `[ ]`
the sequence of chars in the class is evaluated with an OR operation. the sequence of chars in the class is evaluated with an OR operation.
For example the following cc `[abc]` match any char that is `a` or `b` or `c` but doesn't match `C` or `z`. For example, the following cc `[abc]` match any char that is `a` or `b` or `c` but doesn't match `C` or `z`.
Inside a cc is possible to specify a "range" of chars, for example `[ad-f]` is equivalent to write `[adef]`. Inside a cc is possible to specify a "range" of chars, for example `[ad-f]` is equivalent to write `[adef]`.
@ -68,17 +68,17 @@ A meta-char can match different type of chars.
Each token can have a quantifier that specify how many times the char can or must be matched. Each token can have a quantifier that specify how many times the char can or must be matched.
**Short quantifier** #### **Short quantifier**
- `?` match 0 or 1 time, `a?b` match both `ab` or `b` - `?` match 0 or 1 time, `a?b` match both `ab` or `b`
- `+` match at minimum 1 time, `a+` match both `aaa` or `a` - `+` match at minimum 1 time, `a+` match both `aaa` or `a`
- `*` match 0 or more time, `a*b` match both `aaab` or `ab` or `b` - `*` match 0 or more time, `a*b` match both `aaab` or `ab` or `b`
**Long quantifier** #### **Long quantifier**
- `{x}` match exactly x time, `a{2}` match `aa` but doesn't match `aaa` or `a` - `{x}` match exactly x time, `a{2}` match `aa` but doesn't match `aaa` or `a`
- `{min,}` match at minimum min time, `a{2,}` match `aaa` or `aa` but doesn't match `a` - `{min,}` match at minimum min time, `a{2,}` match `aaa` or `aa` but doesn't match `a`
- `{,max}` match at least 1 time and maximum max time, `a{,2}` match `a` and `aa` but doesn't match `aaa` - `{,max}` match at least 0 time and maximum max time, `a{,2}` match `a` and `aa` but doesn't match `aaa`
- `{min,max}` match from min times to max times, `a{2,3}` match `aa` and `aaa` but doesn't match `a` or `aaaa` - `{min,max}` match from min times to max times, `a{2,3}` match `aa` and `aaa` but doesn't match `a` or `aaaa`
a long quantifier may have a `greedy off` flag that is the `?` char after the brackets, `{2,4}?` means to match the minimum number possible tokens in this case 2. a long quantifier may have a `greedy off` flag that is the `?` char after the brackets, `{2,4}?` means to match the minimum number possible tokens in this case 2.
@ -102,7 +102,7 @@ the dot char match any char until the next token match is satisfied.
the token `|` is a logic OR operation between two consecutive tokens, `a|b` match a char that is `a` or `b`. the token `|` is a logic OR operation between two consecutive tokens, `a|b` match a char that is `a` or `b`.
The or token can work in a "chained way": `a|(b)|cd ` test first `a` if the char is not `a` the test the group `(b)` and if the group doesn't match test the token `c`. The OR token can work in a "chained way": `a|(b)|cd ` test first `a` if the char is not `a` then test the group `(b)` and if the group doesn't match test the token `c`.
**note: The OR work at token level! It doesn't work at concatenation level!** **note: The OR work at token level! It doesn't work at concatenation level!**
@ -181,16 +181,16 @@ re.flag = regex.F_BIN
### Initializer ### Initializer
These function are helper that create the `RE` struct, a `RE` struct can be created manually if you needed. These functions are helper that create the `RE` struct, a `RE` struct can be created manually if you needed.
**Simplified initializer** #### **Simplified initializer**
```v ```v
// regex create a regex object from the query string and compile it // regex create a regex object from the query string and compile it
pub fn regex(in_query string) (RE,int,int) pub fn regex(in_query string) (RE,int,int)
``` ```
**Base initializer** #### **Base initializer**
```v ```v
// new_regex create a REgex of small size, usually sufficient for ordinary use // new_regex create a REgex of small size, usually sufficient for ordinary use
@ -199,13 +199,13 @@ pub fn new_regex() RE
// new_regex_by_size create a REgex of large size, mult specify the scale factor of the memory that will be allocated // new_regex_by_size create a REgex of large size, mult specify the scale factor of the memory that will be allocated
pub fn new_regex_by_size(mult int) RE pub fn new_regex_by_size(mult int) RE
``` ```
After the base initializer use, the regex expression must be compiled with: After a base initializer is used, the regex expression must be compiled with:
```v ```v
// compile return (return code, index) where index is the index of the error in the query string if return code is an error code // compile return (return code, index) where index is the index of the error in the query string if return code is an error code
pub fn (re mut RE) compile(in_txt string) (int,int) pub fn (re mut RE) compile(in_txt string) (int,int)
``` ```
### Functions ### Operative Functions
These are the operative functions These are the operative functions
@ -227,7 +227,7 @@ pub fn (re mut RE) replace(in_txt string, repl string) string
This module has few small utilities to help the writing of regex expressions. This module has few small utilities to help the writing of regex expressions.
**Syntax errors highlight** ### **Syntax errors highlight**
the following example code show how to visualize the syntax errors in the compilation phase: the following example code show how to visualize the syntax errors in the compilation phase:
@ -256,7 +256,7 @@ if re_err != COMPILE_OK {
``` ```
**Compiled code** ### **Compiled code**
It is possible view the compiled code calling the function `get_query()` the result will be something like this: It is possible view the compiled code calling the function `get_query()` the result will be something like this:
@ -279,7 +279,7 @@ PC: 2 ist: 88000000 PROG_END { 0, 0}
`{m,n}` is the quantifier, the greedy off flag `?` will be showed if present in the token `{m,n}` is the quantifier, the greedy off flag `?` will be showed if present in the token
**Log debug** ### **Log debug**
The log debugger allow to print the status of the regex parser when the parser is running. The log debugger allow to print the status of the regex parser when the parser is running.
@ -338,6 +338,21 @@ the columns have the following meaning:
`{2,3}:1?` quantifier `{min,max}`, `:1` is the actual counter of repetition, `?` is the greedy off flag if present `{2,3}:1?` quantifier `{min,max}`, `:1` is the actual counter of repetition, `?` is the greedy off flag if present
### **Custom Logger output**
The debug functions output uses the `stdout` as default, it is possible to provide an alternative output setting a custom output function:
```v
// custom print function, the input will be the regex debug string
fn custom_print(txt string) {
println("my log: $txt")
}
mut re := new_regex()
re.log_func = custom_print // every debug output from now will call this function
```
## Example code ## Example code
Here there is a simple code to perform some basically match of strings Here there is a simple code to perform some basically match of strings

View File

@ -200,7 +200,6 @@ pub fn (re RE) get_parse_error_string(err int) string {
} }
} }
// utf8_str convert and utf8 sequence to a printable string // utf8_str convert and utf8 sequence to a printable string
[inline] [inline]
fn utf8_str(ch u32) string { fn utf8_str(ch u32) string {
@ -231,7 +230,7 @@ mut:
ist u32 = u32(0) ist u32 = u32(0)
// char // char
ch u32 = u32(0)// char of the token if any ch u32 = u32(0) // char of the token if any
ch_len byte = byte(0) // char len ch_len byte = byte(0) // char len
// Quantifiers / branch // Quantifiers / branch
@ -245,7 +244,7 @@ mut:
// counters for quantifier check (repetitions) // counters for quantifier check (repetitions)
rep int = 0 rep int = 0
// validator function pointer and control char // validator function pointer
validator fn (byte) bool validator fn (byte) bool
// groups variables // groups variables
@ -280,9 +279,9 @@ pub const (
struct StateDotObj{ struct StateDotObj{
mut: mut:
i int = 0 // char index in the input buffer i int = -1 // char index in the input buffer
pc int = 0 // program counter saved pc int = -1 // program counter saved
mi int = 0 // match_index saved mi int = -1 // match_index saved
group_stack_index int = -1 // group index stack pointer saved group_stack_index int = -1 // group index stack pointer saved
} }
@ -648,7 +647,7 @@ fn (re RE) parse_quantifier(in_txt string, in_i int) (int, int, int, bool) {
// min parsing skip if comma present // min parsing skip if comma present
if status == .start && ch == `,` { if status == .start && ch == `,` {
q_min = 1 // default min in a {} quantifier is 1 q_min = 0 // default min in a {} quantifier is 0
status = .comma_checked status = .comma_checked
i++ i++
continue continue
@ -998,6 +997,7 @@ pub fn (re mut RE) compile(in_txt string) (int,int) {
// Post processing // Post processing
//****************************************** //******************************************
// count IST_DOT_CHAR to set the size of the state stack // count IST_DOT_CHAR to set the size of the state stack
mut pc1 := 0 mut pc1 := 0
mut tmp_count := 0 mut tmp_count := 0
@ -1007,9 +1007,9 @@ pub fn (re mut RE) compile(in_txt string) (int,int) {
} }
pc1++ pc1++
} }
// init the state stack // init the state stack
re.state_stack = [StateDotObj{}].repeat(tmp_count+1) re.state_stack = [StateDotObj{}].repeat(tmp_count+1)
// OR branch // OR branch
// a|b|cd // a|b|cd
@ -1279,7 +1279,8 @@ pub fn (re mut RE) match_base(in_txt byteptr, in_txt_len int ) (int,int) {
mut pc := -1 // program counter mut pc := -1 // program counter
mut state := StateObj{} // actual state mut state := StateObj{} // actual state
mut ist := u32(0) // Program Counter mut ist := u32(0) // actual instruction
mut l_ist := u32(0) // last matched instruction
mut group_stack := [-1].repeat(re.group_max) mut group_stack := [-1].repeat(re.group_max)
mut group_data := [-1].repeat(re.group_max) mut group_data := [-1].repeat(re.group_max)
@ -1359,7 +1360,7 @@ pub fn (re mut RE) match_base(in_txt byteptr, in_txt_len int ) (int,int) {
tmp_gr := re.prog[re.prog[pc].goto_pc].group_rep tmp_gr := re.prog[re.prog[pc].goto_pc].group_rep
buf2.write("GROUP_START #:${tmp_gi} rep:${tmp_gr} ") buf2.write("GROUP_START #:${tmp_gi} rep:${tmp_gr} ")
} else if ist == IST_GROUP_END { } else if ist == IST_GROUP_END {
buf2.write("GROUP_END #:${re.prog[pc].group_id} deep:${group_index} ") buf2.write("GROUP_END #:${re.prog[pc].group_id} deep:${group_index}")
} }
} }
if re.prog[pc].rep_max == MAX_QUANTIFIER { if re.prog[pc].rep_max == MAX_QUANTIFIER {
@ -1417,17 +1418,10 @@ pub fn (re mut RE) match_base(in_txt byteptr, in_txt_len int ) (int,int) {
} }
// manage IST_DOT_CHAR // manage IST_DOT_CHAR
if re.state_stack_index >= 0 {
//C.printf("DOT CHAR text end management!\n")
// if DOT CHAR is not the last instruction and we are still going, then no match!!
if pc < re.prog.len && re.prog[pc+1].ist != IST_PROG_END {
return NO_MATCH_FOUND,0
}
}
m_state == .end m_state == .end
break break
return NO_MATCH_FOUND,0 //return NO_MATCH_FOUND,0
} }
// starting and init // starting and init
@ -1475,12 +1469,13 @@ pub fn (re mut RE) match_base(in_txt byteptr, in_txt_len int ) (int,int) {
// check if stop // check if stop
if m_state == .stop { if m_state == .stop {
// if we are in restore state ,do it and restart // if we are in restore state ,do it and restart
if re.state_stack_index >= 0 { //C.printf("re.state_stack_index %d\n",re.state_stack_index )
if re.state_stack_index >=0 && re.state_stack[re.state_stack_index].pc >= 0 {
i = re.state_stack[re.state_stack_index].i i = re.state_stack[re.state_stack_index].i
pc = re.state_stack[re.state_stack_index].pc pc = re.state_stack[re.state_stack_index].pc
state.match_index = re.state_stack[re.state_stack_index].mi state.match_index = re.state_stack[re.state_stack_index].mi
group_index = re.state_stack[re.state_stack_index].group_stack_index group_index = re.state_stack[re.state_stack_index].group_stack_index
m_state = .ist_load m_state = .ist_load
continue continue
} }
@ -1499,12 +1494,22 @@ pub fn (re mut RE) match_base(in_txt byteptr, in_txt_len int ) (int,int) {
// program end // program end
if ist == IST_PROG_END { if ist == IST_PROG_END {
// if we are in match exit well // if we are in match exit well
if group_index >= 0 && state.match_index >= 0 { if group_index >= 0 && state.match_index >= 0 {
group_index = -1 group_index = -1
} }
// we have a DOT MATCH on going
//C.printf("IST_PROG_END l_ist: %08x\n", l_ist)
if re.state_stack_index>=0 && l_ist == IST_DOT_CHAR {
m_state = .stop
continue
}
re.state_stack_index = -1
m_state = .stop m_state = .stop
continue continue
} }
// check GROUP start, no quantifier is checkd for this token!! // check GROUP start, no quantifier is checkd for this token!!
@ -1527,7 +1532,7 @@ pub fn (re mut RE) match_base(in_txt byteptr, in_txt_len int ) (int,int) {
//C.printf("g.id: %d group_index: %d\n", re.prog[pc].group_id, group_index) //C.printf("g.id: %d group_index: %d\n", re.prog[pc].group_id, group_index)
if group_index >= 0 { if group_index >= 0 {
start_i := group_stack[group_index] start_i := group_stack[group_index]
group_stack[group_index]=-1 //group_stack[group_index]=-1
// save group results // save group results
g_index := re.prog[pc].group_id*2 g_index := re.prog[pc].group_id*2
@ -1537,6 +1542,7 @@ pub fn (re mut RE) match_base(in_txt byteptr, in_txt_len int ) (int,int) {
re.groups[g_index] = 0 re.groups[g_index] = 0
} }
re.groups[g_index+1] = i re.groups[g_index+1] = i
//C.printf("GROUP %d END [%d, %d]\n", re.prog[pc].group_id, re.groups[g_index], re.groups[g_index+1])
} }
re.prog[pc].group_rep++ // increase repetitions re.prog[pc].group_rep++ // increase repetitions
@ -1568,6 +1574,7 @@ pub fn (re mut RE) match_base(in_txt byteptr, in_txt_len int ) (int,int) {
else if ist == IST_DOT_CHAR { else if ist == IST_DOT_CHAR {
//C.printf("IST_DOT_CHAR rep: %d\n", re.prog[pc].rep) //C.printf("IST_DOT_CHAR rep: %d\n", re.prog[pc].rep)
state.match_flag = true state.match_flag = true
l_ist = u32(IST_DOT_CHAR)
if first_match < 0 { if first_match < 0 {
first_match = i first_match = i
@ -1575,12 +1582,23 @@ pub fn (re mut RE) match_base(in_txt byteptr, in_txt_len int ) (int,int) {
state.match_index = i state.match_index = i
re.prog[pc].rep++ re.prog[pc].rep++
if re.prog[pc].rep == 1 { //if re.prog[pc].rep >= re.prog[pc].rep_min && re.prog[pc].rep <= re.prog[pc].rep_max {
if re.prog[pc].rep >= 0 && re.prog[pc].rep <= re.prog[pc].rep_max {
//C.printf("DOT CHAR save state : %d\n", re.state_stack_index)
// save the state // save the state
re.state_stack_index++
// manage first dot char
if re.state_stack_index < 0 {
re.state_stack_index++
}
re.state_stack[re.state_stack_index].pc = pc re.state_stack[re.state_stack_index].pc = pc
re.state_stack[re.state_stack_index].mi = state.match_index re.state_stack[re.state_stack_index].mi = state.match_index
re.state_stack[re.state_stack_index].group_stack_index = group_index re.state_stack[re.state_stack_index].group_stack_index = group_index
} else {
re.state_stack[re.state_stack_index].pc = -1
re.state_stack[re.state_stack_index].mi = -1
re.state_stack[re.state_stack_index].group_stack_index = -1
} }
if re.prog[pc].rep >= 1 && re.state_stack_index >= 0 { if re.prog[pc].rep >= 1 && re.state_stack_index >= 0 {
@ -1590,19 +1608,11 @@ pub fn (re mut RE) match_base(in_txt byteptr, in_txt_len int ) (int,int) {
// manage * and {0,} quantifier // manage * and {0,} quantifier
if re.prog[pc].rep_min > 0 { if re.prog[pc].rep_min > 0 {
i += char_len // next char i += char_len // next char
l_ist = u32(IST_DOT_CHAR)
} }
if re.prog[pc+1].ist != IST_GROUP_END { m_state = .ist_next
m_state = .ist_next continue
continue
}
// IST_DOT_CHAR is the last instruction, get all
else {
//C.printf("We are the last one!\n")
pc--
m_state = .ist_next_ks
continue
}
} }
@ -1622,6 +1632,7 @@ pub fn (re mut RE) match_base(in_txt byteptr, in_txt_len int ) (int,int) {
if cc_res { if cc_res {
state.match_flag = true state.match_flag = true
l_ist = u32(IST_CHAR_CLASS_POS)
if first_match < 0 { if first_match < 0 {
first_match = i first_match = i
@ -1645,6 +1656,7 @@ pub fn (re mut RE) match_base(in_txt byteptr, in_txt_len int ) (int,int) {
//C.printf("BSLS in_ch: %c res: %d\n", ch, tmp_res) //C.printf("BSLS in_ch: %c res: %d\n", ch, tmp_res)
if tmp_res { if tmp_res {
state.match_flag = true state.match_flag = true
l_ist = u32(IST_BSLS_CHAR)
if first_match < 0 { if first_match < 0 {
first_match = i first_match = i
@ -1669,6 +1681,7 @@ pub fn (re mut RE) match_base(in_txt byteptr, in_txt_len int ) (int,int) {
if re.prog[pc].ch == ch if re.prog[pc].ch == ch
{ {
state.match_flag = true state.match_flag = true
l_ist = u32(IST_SIMPLE_CHAR)
if first_match < 0 { if first_match < 0 {
first_match = i first_match = i
@ -1857,7 +1870,7 @@ pub fn (re mut RE) match_base(in_txt byteptr, in_txt_len int ) (int,int) {
} }
// no other options // no other options
//C.printf("NO_MATCH_FOUND\n") //C.printf("ist_quant_n NO_MATCH_FOUND\n")
result = NO_MATCH_FOUND result = NO_MATCH_FOUND
m_state = .stop m_state = .stop
continue continue
@ -1873,12 +1886,6 @@ pub fn (re mut RE) match_base(in_txt byteptr, in_txt_len int ) (int,int) {
rep := re.prog[pc].rep rep := re.prog[pc].rep
// clear the actual dot char capture state
if re.state_stack_index >= 0 {
//C.printf("Drop the DOT_CHAR state!\n")
re.state_stack_index--
}
// under range // under range
if rep > 0 && rep < re.prog[pc].rep_min { if rep > 0 && rep < re.prog[pc].rep_min {
//C.printf("ist_quant_p UNDER RANGE\n") //C.printf("ist_quant_p UNDER RANGE\n")

View File

@ -33,15 +33,13 @@ match_test_suite = [
TestItem{"this is a good sample.",r"( ?\w+){,4}",0,14}, TestItem{"this is a good sample.",r"( ?\w+){,4}",0,14},
TestItem{"this is a good sample.",r"( ?\w+){,5}",0,21}, TestItem{"this is a good sample.",r"( ?\w+){,5}",0,21},
TestItem{"this is a good sample.",r"( ?\w+){2,3}",0,9}, TestItem{"this is a good sample.",r"( ?\w+){2,3}",0,9},
TestItem{"this is a good sample.",r"(\s?\w+){2,3}",0,9}, TestItem{"this is a good sample.",r"(\s?\w+){2,3}",0,9},
TestItem{"this is a good sample.",r".*i(\w)+",0,4},
TestItem{"this these those.",r"(th[ei]se?\s|\.)+",0,11}, TestItem{"this these those.",r"(th[ei]se?\s|\.)+",0,11},
TestItem{"this these those ",r"(th[eio]se? ?)+",0,17}, TestItem{"this these those ",r"(th[eio]se? ?)+",0,17},
TestItem{"this these those ",r"(th[eio]se? )+",0,17}, TestItem{"this these those ",r"(th[eio]se? )+",0,17},
TestItem{"this,these,those. over",r"(th[eio]se?[,. ])+",0,17}, TestItem{"this,these,those. over",r"(th[eio]se?[,. ])+",0,17},
TestItem{"soday,this,these,those. over",r"(th[eio]se?[,. ])+",6,23}, TestItem{"soday,this,these,those. over",r"(th[eio]se?[,. ])+",6,23},
TestItem{"soday,this,these,those. over",r".*,(th[eio]se?[,. ])+",0,23},
TestItem{"soday,this,these,thesa.thesi over",r".*,(th[ei]se?[,. ])+(thes[ai][,. ])+",0,29},
TestItem{"cpapaz",r"(c(pa)+z)",0,6}, TestItem{"cpapaz",r"(c(pa)+z)",0,6},
TestItem{"this is a cpapaz over",r"(c(pa)+z)",10,16}, TestItem{"this is a cpapaz over",r"(c(pa)+z)",10,16},
TestItem{"this is a cpapapez over",r"(c(p[ae])+z)",10,18}, TestItem{"this is a cpapapez over",r"(c(p[ae])+z)",10,18},
@ -56,16 +54,23 @@ match_test_suite = [
TestItem{"this cpapaz adce aabe",r"(c(pa)+z)(\s[\a]+){2}",5,21}, TestItem{"this cpapaz adce aabe",r"(c(pa)+z)(\s[\a]+){2}",5,21},
TestItem{"1234this cpapaz adce aabe",r"(c(pa)+z)(\s[\a]+){2}$",9,25}, TestItem{"1234this cpapaz adce aabe",r"(c(pa)+z)(\s[\a]+){2}$",9,25},
TestItem{"this cpapaz adce aabe third",r"(c(pa)+z)(\s[\a]+){2}",5,21}, TestItem{"this cpapaz adce aabe third",r"(c(pa)+z)(\s[\a]+){2}",5,21},
TestItem{"123cpapaz ole. pippo",r"(c(pa)+z)(\s+\a+[\.,]?)+",3,20},
TestItem{"this is a good sample.",r".*i(\w)+",0,4},
TestItem{"soday,this,these,those. over",r".*,(th[eio]se?[,. ])+",0,23},
TestItem{"soday,this,these,thesa.thesi over",r".*,(th[ei]se?[,. ])+(thes[ai][,. ])+",0,29},
TestItem{"cpapaz ole. pippo,",r".*(c(pa)+z)(\s+\a+[\.,]?)+",0,18}, TestItem{"cpapaz ole. pippo,",r".*(c(pa)+z)(\s+\a+[\.,]?)+",0,18},
TestItem{"cpapaz ole. pippo",r".*(c(pa)+z)(\s+\a+[\.,]?)+",0,17}, TestItem{"cpapaz ole. pippo",r".*(c(pa)+z)(\s+\a+[\.,]?)+",0,17},
TestItem{"cpapaz ole. pippo, 852",r".*(c(pa)+z)(\s+\a+[\.,]?)+",0,18}, TestItem{"cpapaz ole. pippo, 852",r".*(c(pa)+z)(\s+\a+[\.,]?)+",0,18},
TestItem{"123cpapaz ole. pippo",r".*(c(pa)+z)(\s+\a+[\.,]?)+",0,20}, TestItem{"123cpapaz ole. pippo",r".*(c(pa)+z)(\s+\a+[\.,]?)+",0,20},
TestItem{"...cpapaz ole. pippo",r".*(c(pa)+z)(\s+\a+[\.,]?)+",0,20}, TestItem{"...cpapaz ole. pippo",r".*(c(pa)+z)(\s+\a+[\.,]?)+",0,20},
TestItem{"123cpapaz ole. pippo",r"(c(pa)+z)(\s+\a+[\.,]?)+",3,20},
TestItem{"cpapaz ole. pippo,",r".*c.+ole.*pi",0,14}, TestItem{"cpapaz ole. pippo,",r".*c.+ole.*pi",0,14},
TestItem{"cpapaz ole. pipipo,",r".*c.+ole.*p([ip])+o",0,18}, TestItem{"cpapaz ole. pipipo,",r".*c.+ole.*p([ip])+o",0,18},
TestItem{"cpapaz ole. pipipo",r"^.*c.+ol?e.*p([ip])+o$",0,18}, TestItem{"cpapaz ole. pipipo",r"^.*c.+ol?e.*p([ip])+o$",0,18},
TestItem{"abbb",r"ab{2,3}?",0,3}, TestItem{"abbb",r"ab{2,3}?",0,3},
TestItem{" pippo pera",r"\s(.*)pe(.*)",0,11},
TestItem{" abb",r"\s(.*)",0,4},
// negative // negative
TestItem{"zthis ciao",r"((t[hieo]+se?)\s*)+",-1,0}, TestItem{"zthis ciao",r"((t[hieo]+se?)\s*)+",-1,0},