/*
 Copyright (C) 1991-2012 the Free Software Foundation, Inc.
 Copyright (C) 2021 Momi-g

 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

/*SH_doc
title=ureg section=3 repnl=\040
@name	ureg
@_brief	portable BRE/ERE regex api
@synopsis
 #include "ureg.h"
 
 //main func 
 ureg_t* ureg_new(const char* reg [,int reglen, int syn] );
 ureg_t* ureg_new_raw(const char* reg [,int reglen, int syn] );
 int ureg_search(ureg_t* robj, const char* s [,int slen] );
 int ureg_search_head(ureg_t* robj, const char* s [,int slen] );
 void ureg_free(ureg_t* robj);

 //main object info 
 typedef struct ureg_tag {
	void* data;		//opaque regobj
	char* fastmap;	//fastmap char[255]. holds 1st byte hit or not
	char* lc;		//save regex locale
	int rawflg;		//locale flag
	const char* p;		//result buff
	int sz;
	const char* pbk[10];
	int szbk[10];		//buff
 } ureg_t;
 
 //support func
 ureg_t ureg_easy(const char* reg,const char* s
		[,int reglen,int slen,int syn]);
 int ureg_ere_syn(void);
 int ureg_bre_syn(void);
 void* ureg_iconv(const char* fromenc,
		const char* toenc, char** s [,int slen]);

@tl;dr
	@(code)@
	#include "ureg.h"
	
	int main() {
		  ureg_t res = ureg_easy("a[bc]", "123abc");	//dfl:ERE
		  printf("%.*s \n", res.sz, res.p);	// == "ab"
		  return 0;
	}
	//~$ cc src.c libureg.a
	@()@

@_eg
	@(code)
 #include "ureg.h"
	
 int main() {
	//simple
	ureg_t res = ureg_easy("a[bc]", "123abc");
		//res.p="123abc"+3, res.sz=2
	res = ureg_easy("a(b|c)", "123abc");
		//res.pbk[1]="123abc"+3,res.szbk[1]=1
	printf("%.*s", res.szbk[0], res.pbk[0]);	//>> "ab"
	
	//complex
	ureg_t* obj = ureg_new("a(b|c)");	//rtn NULL: err
	const char* s = "123abc";
	int rc = ureg_search(obj, s);	//rc== hit:>=0 nohit/err:<0
	  //rc=3, obj->p=s+3(rc), obj->sz=2 
	  //obj->pbk[0]= s+3, obj->szbk[0]=2 //p=pbk[0]: all the match str
	  //obj->pbk[1]= s+4, obj->szbk[1]=1 //bkref: (b|c) hits 123a(b)c
	  //obj->pbk[2]= NULL,obj->szbk[2]=-1 //set NULL/-1 if fail
	
	rc = ureg_search_head(obj, s);	//regex hits only string top
		// rc= -1, obj->p= NULL, obj->sz= -1	//nohit
	rc = ureg_search_head(obj, s+3);  // rc=0, obj->p= s+3, obj->sz= 2
	ureg_free(obj);
	
	setlocale(LC_CTYPE, "UTF32");	// ~$ iconv -l
	obj = ureg_new_raw("a(b|c)");
	setlocale(LC_CTYPE, "C");	// reset
	rc = ureg_search(obj, s);
	ureg_free(obj);
	
	//ureg_iconv(): not regex. conv encode UTF-8, EBCDIC etc
	const char* s0 = "hwd";  //"hwd"=="\150\167\144" in C charset
	const char* s1 = "\210\246\204";	//"hwd" in EBCDIC-BR
	char* s = s0;
	int* p = ureg_iconv("C","EBCDIC-BR",&s,2); //(from,to,s0_adrs,len)
	int binsz = p[0];	//==2, conv result(EBCDIC-BR) bytesz
	char* pp = (char*)(p+1);
		// pp[0]=='\210', pp[1]=='\246', pp[2]=='\0'
		// srcptr &s is changed to "hwd"+2, "(hw)d" pos, s[0]=='d'
		// | binsz |  c  |  c  | 0 |
		//    int    char  char  \0
	free(p);	// ureg_iconv() rtns sz+bin malloc()ed ptr.
	return 0;
  }
  //~$ cc src.c libureg.a
	@()@

@description
	ureg uses posix-ERE with the following expantion in default.
	some items are compromised for reasons --
	@(pre)@
	- [] and .(dot) includes \n and \0. posix doesnt allow byte \0.
	- [] and .(dot) ignores out of charactor byte. (should be err. gave up)
	    eg) [\100\377] >> [\100] (ascii locale)
	    eg) [Σ\377]==[\u03a3] @: 1char, multibyte locale
	    eg) [Σ\377]==[\316\243\377] @: 3 char, C locale
	- allow bytedata except [] and .(dot)   eg) "\377z[0-9]"  >> valid
	- normal char esc \@ treats as @ (should be err. gave up)
    @()@
    --

	@(raw).SS easy mode@()--
	'	'ureg_t ureg_easy(r, s [, rlen, slen, syn]); --
	--
	ureg_easy() uses for oneshot search. use '-1' if rlen/slen/syn isnt set.
	search result is set to rtn. if nohit, set res.p==NULL, res.sz<0. --
	'  'res = ureg_easy("a[bc]", "123abc");  //hit, res.p="..."+3, res.sz=2 --
	'  'res = ureg_easy("a[bc]", "123abc", -1,-1,-1);  //same --
	--
	'_param'
	@(pre)
	r     : regex str
	s     : search target string/binary
	[rlen]: r size. use strlen(r) if set -1/noset
	[slen]: s size
	[syn] : change regex syntax. see below 'complex mode'
	@()--

	'_return'
	@(pre)
    res.p : matched ptr. res.p[0]==@'a@' in above sample. "123(a)bc"
    res.sz: match string byte size
    others: see below (complex mode)
    @()--
	ureg_easy() has the following restrictions
	@(pre)
	- locale rawmode isnt support. always use the sys locale(UTF-8 etc)
	- execute malloc()/free() every time
	@()--
	
	sample:
		@(pre)
	#include "ureg.h"
	int main(int argc, char** argv){
		ureg_t res = ureg_easy("a[bc]", "123abc");
		printf("%.*s \n" res.sz, res.p);	//>> disp "ab"
		return 0;	//no memleak
	}
	// ~$ gcc src.c libureg.a
		@()--
	@(raw).SS complex mode@()--
	divided to 3 parts, compile >> search >> free	--
	--
	1. compile regex
	@(pre)
	ureg_t* robj = ureg_new(r [,rlen, syn] );
	ureg_t* robj = ureg_new_raw(r [,rlen, syn] );
	int syn = ureg_ere_syn();
	int syn = ureg_bre_syn();
	@()--
	C-lang uses "C" locale in default setting and all 8bit char is vaild. --
	(www.gnu.org/software/grep/manual/html_node/Character-Encoding.html) --
	'  'eg) int main(){ puts(setlocale(LC_CTYPE, NULL)); } //>> "C" --
	--
	and regex semantics are differences from locale "C" to "UTF-8". --
	'  'C : "[Σ]" == "[\316\243]" == "(\316|\243)" //binary \316 or \243 --
	'  'u8: "[Σ]" == "\316\243" //binary sequence is 1 charactor --

	@(list)
	ureg_new(): use OS locale, setlocale(LC_CTYPE, "") (see ~$ echo $LANG) and
	use its locale when search.
	ureg_new_raw(): uses current locale and never changes locale setting, so
	adjusting locale is user duty. if new/search locale isnt the same, you
	may get the strange results. robj->lc saves setlocale(LC_TYPE,NULL) str.
	@()--
	sample:
	@(code)
	#include "ureg.h"
	int main() {
		ureg_t* obj = ureg_new("[Σ]");
		puts(obj->lc);	// "XX.UTF-8" etc
		ureg_search(obj, "xΣyz");	// match len: obj->sz=2
		ureg_free(obj);

		obj = ureg_new_raw("[Σ]");  //== "(\316|\243)" at locale "C"
		puts(obj->lc);	// "C" etc
		ureg_search(obj, "xΣyz");  // obj->sz=1
		ureg_free(obj);

			setlocale(LC_CTYPE, "C.UTF-8");
		obj = ureg_new_raw("[Σ]");	//== "\316\243" at UTF-8
			setlocale(LC_CTYPE, "C");
		ureg_search(obj, "xΣyz"); //obj->sz=1, use current locale
			puts(obj->lc);	//>> "C.UTF-8"
		ureg_free(obj);
		return 0;
	}
	//~$ cc src.c  libureg.a
	@()--
	malloc()/free() isnt executed every time. you can avoid the overhead.
	but locale setting is very confusing so I reccomend you to use
	ureg_new() except rare/special usage.	--
	--
	'_param'
	@(list)
	r   : regex str. mb class/range works only under the mb locale env.--
		[Σ-Τ] == "(\u03a3|\u03a4)"  //"XXX.UTF-8"	--
		[Σ-Τ] == [\316\243-\316\244]== [\243-\316]  //"C"
	[rlen]: r size. use strlen(r) if set -1/noset
	[syn] : use ERE if -1/noset. ureg_ere_syn()/ureg_bre_syn() returns
		ERE/BRE flag.	--
		'  'int syn = ureg_bre_syn(); // or ureg_ere_syn(); --
		'  'robj = ureg_new("a\\(b\\)c", -1, syn); --
		'  'rc = ureg_search(robj, "00abc");	//hit rc=2	--
	@()--
	
	'_return'
	@(list)
	robj->data : ptr to compiled regex data
	robj->fastmap : ''char arr[255]. if r="[ab]c", arr['a'], ['b'] is 1. others is 0.''
	robj->lc : copy of setlocale(LC_CTYPE, NULL) str when regex compiled.
	robj->rawflg : 0/1 == ureg_new()/ureg_new_raw()
	others : result buffer
    @()--

	2. search
	@(pre)
	int rc = ureg_search(robj, s [, slen] );
	int rc = ureg_search_head(robj, s [, slen] );
	@()--
	@(list)
	ureg_search(): finds match data from s. use strlen(s) if slen= -1/noset.
		if you used ureg_new(), use robj->lc locale setting automatically.
	ureg_search_head(): check only BOS like lex scanner
	@()--
	
	'_param'
	@(list)
	robj : search result is set to this member. set NULL / -1 if nohit.
	char* robj->p : full matching ptrpos
	int obj->sz : byte size of matching str.
	char* obj->pbk[10]: back reference ptrpos. obj->p == obj->pbk[0]
	int obj->szbk[10]: byte size of back references. obj->sz==obj->szbk[0]. 
		posix-ERE syntax doesnt allow backref \1-9, but to hold backref data
		is lawful. posix regcomp(), REG_NOSUB also holds its data.
	s		: search target string ptr
	[slen]	: use strlen(s) if -1/noset
	@()

	'_return'
	@(list)
	int rc: funcs rtns >=0 or <0 == hit/nohit. srcptr+rc will be hittop ptr.
	@()
		
	sample:
	@(code)
	r = "a[bc]";
	obj = ureg_new(r);
	s = "123abc";
	rc = ureg_search(obj, s);	//rc=3, hit: "...(ab)c", s[3]='a'
	rc = ureg_search_head(obj, s); //rc<0, nohit
	rc = ureg_search_head(obj, s+3); //rc=0, hit "(ab)c", (s+3)[rc]='a'
	@()--
	search funcs doesnt support pirtial match. you need fulltext.
	@(code)
	r = "a(bc)";
	s = "123abc";
	rc = ureg_search(obj, s, 4); //"123a", reg hits 'a' but fail. rc<0
	// if supports rc= -5(pirtial/morestr) etc, you may use fgetc()
	@()--
	back reference result \1-\9 is set to pbk/szbk[1-9]. [0] has fullmatch data.
	index 1-9 applies to open parlen "(" order. the same as posix. --
	'  'reg: ( a ( b | c ) ) --
	'  'bk : \1  \2 --
	--
	back reference sample:
	@(code)
	r = "a[bc]";
	s = "123abc";
	robj = ureg_new(r);
	rc = ureg_search(robj, s);
	  //robj->p=s+4, robj->sz=2
	  //pbk[0]=p,szbk[0]=sz, pbk[1]=NULL, szbk[1]=-1
 	r = "\\(a\\)\\(bc\\)";
 	s = "123abc";
	robj = ureg_new(r, -1, ureg_bre_syn() );
	rc = ureg_search(robj, s);	//robj->p, pbk is as below
		//	str: "123abc",	reg: "(a)(bc)"
		//	p  :  ...oo..,  robj->sz=2, rc=3, robj->p==s+rc
		//	p0 :  (p0==p) 
		//	p1 :  ...o...,  robj->szbk[1]== 1
		//	p2 :  ....o..,  robj->szbk[2]== 1
		//	p3 :  NULL,     robj->szbk[3]== -1
	@()--

	3. free
	@(pre)
	void ureg_free(robj);
	@()--
	robj is malloc()ed. free memory when you finish regex search. --

	@(raw).SS other func@()--
	@(pre)
	void* p = ureg_iconv(fenc, tenc, &s [, slen]);
	@()--
	ureg_iconv() doesnt treat regex. convert different encoding data.
	posix iconv() api/manual is very awkward so make a wrapper. --
	--
	'_param'
		@(pre)
	fenc  : from enc type str. "EBCDIC-BR" etc. see ~$ iconv -l
	tenc  : to enc
	&s    : srcptr-ref. this ag is changed by func, so pass the copy.
	[slen]: src byte size. use strlen(*&s) if set -1/noset.
		@()--
	'_return'
	@(list)
	p: suc/fail == notNULL/NULL. converted binary stream with bytesize.
	use type punning (int), (char*) to get sz and binptr.
	byte sequence is as follows. --
	@()--
		@(code)
	| binsz |  c  |  c  | 0 |
	   int    char  char  \0	(if binsz=2)
	...
	char* s = (char*)"hw";
	int* p = ureg_iconv("ASCII", "UTF32", &s); //p is malloc()ed
	int binsz = p[0];	//==12, conv bytesz. BOM(4b) + h(4b) + w(4b)
	char* bin = (char*)(p+1);
	printf("%.*s \n", binsz, bin);
	free(p);
		@()--
	if allsrc is converted to other enc, s is set as s == src+slen.
	if detect invalid byte sequences, s is set to its address.
		@(code)
	all suc: "oooooooo"
	          ........s(maybe \0 or other)
	bad seq: "ooooox.."
	          .....s.. (s[0]=='\377' etc. from BOS to (s-1) is valid)
		@()--

	sample:
		@(code)
	#include <string.h>
	#include <stdlib.h>
	#include "ureg.h"
	
	int main(){
		const char* s = "ab\377c";	// "ab(NG)c", ascii
		char* sp = (char*)s;
		int* p = ureg_iconv("ASCII", "UTF32", &sp);	// ~$ iconv -l
			printf("%p %p\n", s, sp);	// p-s == 2, sp[0]='\377'
		int sz = p[0];	// ==12, ascii:7bit, UTF32:32bit + BOM 32bit
		char* bin = (char*)&p[1];
			printf("%d\n", sz);	//12, bin[0]..bin[12-1] is UTF32 str
		if(s+strlen(s) != sp){ puts("bad ascii byte"); }
		free(p);
		return 0;
	}
	//~$ cc src.c libureg.a
		@()--
@notes
@(code)
 ureg_erebin_syn_ = 0
 //	| RE_BACKSLASH_ESCAPE_IN_LISTS //"\" works as esc in [] //posix:off
 //	| RE_ICASE			// ignore case, aa==Aa	 on:a==A off:a!=A
	| RE_INTERVALS		// use {} op	on:use off:normal chars
 //	| RE_LIMITED_OPS	// +*?| are normal chars	on:yes off:special
	| RE_CHAR_CLASSES	// use [:alnum:] etc	on:yes off:no

 	| RE_CONTEXT_INDEP_ANCHORS	//$^ works except []/esc (a^c etc)
	| RE_CONTEXT_INDEP_OPS	// *+? raise err in badpos, "+a" etc
	| RE_CONTEXT_INVALID_DUP  // "{2}a" raise err   off:[{][2][}]a
	| RE_CONTEXT_INVALID_OPS  // +*? raise err, "?a"   on:yes off:[+]a
 	| RE_UNMATCHED_RIGHT_PAREN_ORD //")a" ISNT err	on:[)]a off:err
 //	| RE_INVALID_INTERVAL_ORD	// "}abc" ISNT err	on:[}] off:err

	| RE_DOT_NEWLINE	// .(dot) includes '\n'	on:inc off:exc
 //	| RE_DOT_NOT_NULL	// .(dot) excludes '\0'	on:exc off:inc
 //	| RE_HAT_LISTS_NOT_NEWLINE	//[^a] excludes '\n'  on:exc off:inc

 //	| RE_NEWLINE_ALT	// a(\n)b works as a|b, on:\n==| off:| only
	| RE_NO_BK_BRACES	// on:{} off:\{\} //needs RE_INTERVALS
	| RE_NO_BK_PARENS	// on:() off:\(\)
	| RE_NO_BK_VBAR	// on:| off:\|	//needs RE_LIMITED_OPS=off
 //	| RE_NO_BK_REFS	// use \1,\2 etc  on:nouse off:use
 //	| RE_BK_PLUS_QM	// repeat "a+" or "a\+" 	on:\+ off:+

	| RE_DEBUG		// holds dbginfo	on:yes off:no	//emsg etc
	| RE_NO_EMPTY_RANGES	// [z-a] is err  on:yes off:use as empty
	| RE_NO_GNU_OPS	//out of standard op, \< etc  on:nouse off:use
 //	| RE_NO_POSIX_BACKTRACKING	//shortest match  on:yes off:longest
 //	| RE_NO_SUB  //drop backref data, (ab)c \1=ab etc. on:yes off:hold
 ;
// https://www.gnu.org/software/gnulib/manual/html_node/Syntax-Bits.html
 
 // --posix-ERE
 //	--undefined
 //	/()/
 //	/+aa|*{/
 //	/\@/
 //	/|g/
 //	[z-a]
 //	[a-c-e]
 // --valid
 //	)ab		>>uneven parlen treat as ordinary char
 //	[]]
 //	[\]]	>>2char '\' or ']'
 //	[-a]	>>2char '-' or 'a'
 //	[ac-]
 //	/a^b/, /a$b/ >> valid, but never matched

 ureg_brebin_syn_= 0
	| RE_CHAR_CLASSES
	| RE_DOT_NEWLINE
 //	| RE_DOT_NOT_NULL	//>>for support binary
	| RE_INTERVALS
	| RE_NO_EMPTY_RANGES
	;
@()
	..gnu-regex manual have inconsistent expression(DO/NOT DO/NO/LIMIT) and
	low readability. refer to the above and the mit manual. --
	( http://web.mit.edu/gnu/doc/html/regex_2.html#SEC3 )--
	--
	important difference between BRE and ERE syntax is 
	'backref', 'altername op |', 'anchor work' and 'repeat op':
	@(code)@
	         BRE	  ERE		eg
	backref  \1-\9   nothing  B: \(a\)\1 >>aa,  E: (a)\1 >> undefined
	alter   nothing    |      B:      -         E: (aa|bb)
	anchor  BOS/EOS anywhere  B: \(^a^b\)>>a^b  E: (^a^b) >> never match
	repeat  context anywhare  B: *ab >> *ab     E: *ab >> undefined
	@()--
	I recommend you to use ERE in generally. you should use BRE only when
	you need back-references or non-support ERE command (sed, grep etc)
	additionally, quote/esc the special charactors to avoid 'context depends'
	regex.  eg) ERE: )abc >> [)]abc --
	--
	(https://stackoverflow.com/questions/40455975/why-regular-expressions-with-backreferences-are-not-regular-expressions) --
	(https://swtch.com/~rsc/regexp/regexp1.html) --
	--
	--
	''BRE doesnt have '|' op and  equivalent expression seems impossible'' --
	'  'ERE: (ab|cd)* >> ( (ab){0,1} (cd){0,1} ){0,} --
	'  'ERE: (ab|cd)+ >>  ??? --
	--
	ERE doesnt have back-reference \1-\9. close expression is possible but
	not perfect. catch  _ab_, __ab__, ___ab___ ... is: --
	'  'BRE: \(_*\)[^_]\1	--
	'  'ERE: (_[^_]*_) | (__[^_]*__) | (___[^_]*___) ...	--
	--
	--
	@(code)@
	--- benchmark:	loop(10*1000){ ureg_easy("4.6", "1234567890"); } etc
	compile FAST:    --     >>> onig(1)   >>> ureg(50)  :SLOW
	search  FAST: strstr(1) >>> onig(100) >>> ureg(300) :SLOW

	- ureg
	real 948.292 ms: ./ureg.tmp.c 881: t_bm0_sub(): msg:easy:10*1000
	real 813.072 ms: ./ureg.tmp.c 891: t_bm1_sub(): msg:new-free:10*1000
	real 24.156 ms : ./ureg.tmp.c 898: t_bm1_sub(): msg:search:10*1000
	real 787.625 ms: ./ureg.tmp.c 911: t_bm2_sub(): msg:new-free@raw:10*1000
	real 10.868 ms : ./ureg.tmp.c 918: t_bm2_sub(): msg:search@raw:10*1000
	
	- oniguruma
	real 17.268 ms : ./ureg.ts.c 264: t_bm4_sub(): msg:new-free@onig:10*1000
	real 4.473 ms  : ./ureg.ts.c 276: t_bm4_sub(): msg:search@onig:10*1000

	- strstr()
	real 0.050 ms  : ./ureg.ts.c 240: t_bm3_sub(): msg:strstr(): 10*1000


	--- concept
	- avoid complex api
	- avoid non-standard regexp/operator like PCRE
	- support binary input
	@()@
@conforming_to posix-2001+
@_ver 2021-08-08 v3.0.3
@copyright Copyright 2021 momi-g, GPLv3+
@_see
@(code)@
	https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap09.html
	https://swtch.com/~rsc/regexp/regexp1.html
	http://web.mit.edu/gnu/doc/html/regex_7.html
	http://www.kt.rim.or.jp/~kbk/regex/re_7.html
	https://regular-expressions.mobi/refunicode.html?wlr=1
	https://stackoverflow.com/questions/2359811/working-with-gnu-regex-functions-in-c-or-c
	https://stackoverflow.com/questions/8727795/does-posix-regex-h-provide-unicode-or-basically-non-ascii-characters
	https://stackoverflow.com/questions/48743106/whats-ansi-x3-4-1968-encoding
	https://www.iana.org/assignments/character-sets/character-sets.xhtml
@()@
//SH_docE*/

#ifndef ureg_dfb770dd3ce8
#define ureg_dfb770dd3ce8

#include <stdio.h>
#if ( _POSIX_C_SOURCE +0 < 200112L )
	# include "needs compiler posix-2001 or upper(c99+)"
#endif

#include <locale.h>
#include <langinfo.h>
#include <iconv.h>
typedef struct ureg_tag {
	void* data;
	char* fastmap;
	char* lc;
	int rawflg;
	const char* p;
	int sz;
	const char* pbk[10];
	int szbk[10];
} ureg_t;

ureg_t* ureg_new_impl(int rawflg, const char* rstr, int rstrsz, int syn, ...);
void ureg_free(ureg_t* obj);
int ureg_search_impl(int topflg, ureg_t* obj, const char* tgtstr, int slen, ...);

int ureg_ere_syn(void);
int ureg_bre_syn(void);

ureg_t ureg_easy_impl(const char* reg, const char* s, int rlen, int slen, int syn, ...);
void* ureg_iconv_impl(const char* fromenc, const char* toenc, char** str, int strsz, ...);

#define ureg_new(...)		  ureg_new_impl(0,__VA_ARGS__, -1,-1 )
#define ureg_new_raw(...)	  ureg_new_impl(1,__VA_ARGS__, -1,-1 )
#define ureg_search(obj, ...)	  ureg_search_impl(0,obj, __VA_ARGS__, -1 )
#define ureg_search_head(obj,...)	ureg_search_impl(1, obj,__VA_ARGS__, -1 )
#define ureg_easy(reg, ...)		ureg_easy_impl(reg, __VA_ARGS__,-1,-1,-1)
#define ureg_iconv(fromenc, toenc, ...)		ureg_iconv_impl(fromenc, toenc, __VA_ARGS__, -1)

#endif	//ureg_dfb770dd3ce8
