From 30b1b88690b48b7f2d16504ce98edc0e0e24ce7b Mon Sep 17 00:00:00 2001 From: drh Date: Mon, 1 Jan 2018 16:59:08 +0000 Subject: [PATCH] Experiments with the regexp.c extension, trying to get it to report the exact substring that matches the RE. FossilOrigin-Name: 3d6fba623af9356f6b02c7ef5f29c4bdeb2f8f0359bc0a3194951846afa336da --- ext/misc/regexp.c | 394 ++++++++++++++++++++++++++++++++++------------ manifest | 16 +- manifest.uuid | 2 +- 3 files changed, 302 insertions(+), 110 deletions(-) diff --git a/ext/misc/regexp.c b/ext/misc/regexp.c index b4a8ab5c04..4aaf0058f2 100644 --- a/ext/misc/regexp.c +++ b/ext/misc/regexp.c @@ -76,23 +76,23 @@ SQLITE_EXTENSION_INIT1 /* The NFA is implemented as sequence of opcodes taken from the following ** set. Each opcode has a single integer argument. */ -#define RE_OP_MATCH 1 /* Match the one character in the argument */ -#define RE_OP_ANY 2 /* Match any one character. (Implements ".") */ -#define RE_OP_ANYSTAR 3 /* Special optimized version of .* */ -#define RE_OP_FORK 4 /* Continue to both next and opcode at iArg */ -#define RE_OP_GOTO 5 /* Jump to opcode at iArg */ -#define RE_OP_ACCEPT 6 /* Halt and indicate a successful match */ -#define RE_OP_CC_INC 7 /* Beginning of a [...] character class */ -#define RE_OP_CC_EXC 8 /* Beginning of a [^...] character class */ -#define RE_OP_CC_VALUE 9 /* Single value in a character class */ -#define RE_OP_CC_RANGE 10 /* Range of values in a character class */ -#define RE_OP_WORD 11 /* Perl word character [A-Za-z0-9_] */ -#define RE_OP_NOTWORD 12 /* Not a perl word character */ -#define RE_OP_DIGIT 13 /* digit: [0-9] */ -#define RE_OP_NOTDIGIT 14 /* Not a digit */ -#define RE_OP_SPACE 15 /* space: [ \t\n\r\v\f] */ -#define RE_OP_NOTSPACE 16 /* Not a digit */ -#define RE_OP_BOUNDARY 17 /* Boundary between word and non-word */ +#define RE_OP_MATCH 0 /* Match the one character in the argument */ +#define RE_OP_ANY 1 /* Match any one character. (Implements ".") */ +#define RE_OP_CC_INC 2 /* Beginning of a [...] character class */ +#define RE_OP_CC_EXC 3 /* Beginning of a [^...] character class */ +#define RE_OP_CC_VALUE 4 /* Single value in a character class */ +#define RE_OP_CC_RANGE 5 /* Range of values in a character class */ +#define RE_OP_WORD 6 /* Perl word character [A-Za-z0-9_] */ +#define RE_OP_NOTWORD 7 /* Not a perl word character */ +#define RE_OP_DIGIT 8 /* digit: [0-9] */ +#define RE_OP_NOTDIGIT 9 /* Not a digit */ +#define RE_OP_SPACE 10 /* space: [ \t\n\r\v\f] */ +#define RE_OP_NOTSPACE 11 /* Not a digit */ +#define RE_IMMEDIATE 12 /* OPs >= this evaluate immediately */ +#define RE_OP_BOUNDARY 12 /* Boundary between word and non-word */ +#define RE_OP_FORK 13 /* Continue to both next and opcode at iArg */ +#define RE_OP_GOTO 14 /* Jump to opcode at iArg */ +#define RE_OP_ACCEPT 15 /* Halt and indicate a successful match */ /* Each opcode is a "state" in the NFA */ typedef unsigned short ReStateNumber; @@ -118,27 +118,124 @@ struct ReInput { /* A compiled NFA (or an NFA that is in the process of being compiled) is ** an instance of the following object. +** +** The application must serialize access to this object. The code here +** is threadsafe, as long as each thread is using its own ReCompiled object. +** But a single ReCompiled object is neither threadsafe nor reentrant. +** +** The iBegin value is the earliest possible start of the match. The +** true start of the match might be later. */ typedef struct ReCompiled ReCompiled; struct ReCompiled { - ReInput sIn; /* Regular expression text */ + ReInput sIn; /* Regexp text, or match string text */ const char *zErr; /* Error message to return */ char *aOp; /* Operators for the virtual machine */ int *aArg; /* Arguments to each operator */ + ReStateSet a, b; /* Used by re_match() */ unsigned (*xNextChar)(ReInput*); /* Next character function */ + unsigned char nInit; /* Number of characters in zInit */ + unsigned char bFlexStart; /* Input regexp omits the initial "^" */ + unsigned char bKeepSpan; /* When matching, remember start and end */ + unsigned char bMultiBegin; /* iBegin might not be accurate */ + unsigned int iBegin; /* Offset to first character in the match */ + unsigned int iEnd; /* Offset past the end of last match char */ + unsigned int nState; /* Number of entries in aOp[] and aArg[] */ + unsigned int nAlloc; /* Slots allocated for aOp[] and aArg[] */ unsigned char zInit[12]; /* Initial text to match */ - int nInit; /* Number of characters in zInit */ - unsigned nState; /* Number of entries in aOp[] and aArg[] */ - unsigned nAlloc; /* Slots allocated for aOp[] and aArg[] */ }; +#if SQLITE_REGEXP_DEBUG +/************************************************************************ +** The following interfaces are used for development and debugging only +** and are commented out for deployment. +*/ +#include +#include + +/* Render one character. */ +static char *re_char(int c){ + static char zBuf[20]; + if( isprint(c) && (c==' ' || !isspace(c)) ){ + sqlite3_snprintf(sizeof(zBuf),zBuf,"'%c'",c); + }else{ + sqlite3_snprintf(sizeof(zBuf),zBuf,"0x%02x",c); + } + return zBuf; +} + +/* +** Print out a regexp program. +*/ +static void re_print(ReCompiled *pRe){ + int i; + for(i=0; inState; i++){ + printf("%3d ", i); + switch( pRe->aOp[i] ){ + case RE_OP_MATCH: printf("MATCH %s\n", re_char(pRe->aArg[i])); + break; + case RE_OP_ANY: printf("ANY\n"); break; + case RE_OP_WORD: printf("WORD\n"); break; + case RE_OP_NOTWORD: printf("NOTWORD\n"); break; + case RE_OP_DIGIT: printf("DIGIT\n"); break; + case RE_OP_NOTDIGIT: printf("NOTDIGIT\n"); break; + case RE_OP_SPACE: printf("SPACE\n"); break; + case RE_OP_NOTSPACE: printf("NOTSPACE\n"); break; + case RE_OP_BOUNDARY: printf("BOUNDARY\n"); break; + case RE_OP_FORK: printf("FORK %d\n", i+pRe->aArg[i]); break; + case RE_OP_GOTO: printf("GOTO %d\n", i+pRe->aArg[i]); break; + case RE_OP_ACCEPT: printf("ACCEPT\n"); break; + case RE_OP_CC_INC: + case RE_OP_CC_EXC: { + printf("%s\n", pRe->aOp[i]==RE_OP_CC_INC ? "CC_INC" : "CC_EXC"); + int j; + int n = pRe->aArg[i]; + for(j=1; j>0 && jaOp[i+j]==RE_OP_CC_VALUE ? "VALUE" : "RANGE", + re_char(pRe->aArg[i+j])); + } + i += n-1; + break; + } + } + } +} +/* +** Print out a ReStateSet +*/ +static void restateset_print(ReStateSet *pSet){ + int i; + for(i=0; inState; i++){ + if( i ) printf(" "); + printf(" %2d", pSet->aState[i]); + } +} +/* End of debugging utilities +*****************************************************************************/ +#endif /* SQLITE_REGEXP_DEBUG */ + /* Add a state to the given state set if it is not already there */ -static void re_add_state(ReStateSet *pSet, int newState){ +static void re_add_state_to_set(ReStateSet *pSet, int newState){ unsigned i; for(i=0; inState; i++) if( pSet->aState[i]==newState ) return; pSet->aState[pSet->nState++] = (ReStateNumber)newState; } +/* Add a state to the appropriate state set. States for immediate +** evaluation are added into pRe->a. If another input token is required +** before the state can be evaluated, then it is added to pRe->b. +*/ +static void re_add_state(ReCompiled *pRe, int newState){ + ReStateSet *pSet; + if( pRe->aOp[newState]>=RE_IMMEDIATE ){ + pSet = &pRe->a; + }else{ + pSet = &pRe->b; + } + re_add_state_to_set(pSet, newState); +} + /* Extract the next unicode character from *pzIn and return it. Advance ** *pzIn to the first byte past the end of the character returned. To ** be clear: this routine converts utf8 to unicode. This routine is @@ -191,112 +288,102 @@ static int re_space_char(int c){ return c==' ' || c=='\t' || c=='\n' || c=='\r' || c=='\v' || c=='\f'; } +/* Interchange pRe->a and pRe->b */ +static void re_swap(ReCompiled *pRe){ + ReStateSet t = pRe->a; + pRe->a = pRe->b; + pRe->b = t; +} + /* Run a compiled regular expression on the zero-terminated input ** string zIn[]. Return true on a match and false if there is no match. */ static int re_match(ReCompiled *pRe, const unsigned char *zIn, int nIn){ - ReStateSet aStateSet[2], *pThis, *pNext; - ReStateNumber aSpace[100]; - ReStateNumber *pToFree; unsigned int i = 0; - unsigned int iSwap = 0; int c = RE_EOF+1; - int cPrev = 0; int rc = 0; - ReInput in; + int iIdx = -1; + int iBegin = -1; + int iReset = -1; + ReInput *pIn = &pRe->sIn; - in.z = zIn; - in.i = 0; - in.mx = nIn>=0 ? nIn : (int)strlen((char const*)zIn); + pIn->z = zIn; + pIn->i = 0; + pIn->mx = nIn>=0 ? nIn : (int)strlen((char const*)zIn); + pRe->bMultiBegin = 0; /* Look for the initial prefix match, if there is one. */ if( pRe->nInit ){ unsigned char x = pRe->zInit[0]; - while( in.i+pRe->nInit<=in.mx - && (zIn[in.i]!=x || - strncmp((const char*)zIn+in.i, (const char*)pRe->zInit, pRe->nInit)!=0) + while( pIn->i+pRe->nInit<=pIn->mx + && (zIn[pIn->i]!=x || + strncmp((const char*)zIn+pIn->i, + (const char*)pRe->zInit,pRe->nInit)!=0) ){ - in.i++; + pIn->i++; } - if( in.i+pRe->nInit>in.mx ) return 0; + if( pIn->i+pRe->nInit>pIn->mx ) return 0; } - if( pRe->nState<=(sizeof(aSpace)/(sizeof(aSpace[0])*2)) ){ - pToFree = 0; - aStateSet[0].aState = aSpace; - }else{ - pToFree = sqlite3_malloc( sizeof(ReStateNumber)*2*pRe->nState ); - if( pToFree==0 ) return -1; - aStateSet[0].aState = pToFree; - } - aStateSet[1].aState = &aStateSet[0].aState[pRe->nState]; - pNext = &aStateSet[1]; - pNext->nState = 0; - re_add_state(pNext, 0); - while( c!=RE_EOF && pNext->nState>0 ){ - cPrev = c; - c = pRe->xNextChar(&in); - pThis = pNext; - pNext = &aStateSet[iSwap]; - iSwap = 1 - iSwap; - pNext->nState = 0; - for(i=0; inState; i++){ - int x = pThis->aState[i]; +re_print(pRe); +printf("INPUT: [%.*s]\n", nIn, zIn); + pRe->b.nState = 0; + pRe->a.nState = 0; + re_add_state(pRe, 0); + iReset = -1; + c = 0; + while(1){ + for(i=0; ia.nState; i++){ + int x = pRe->a.aState[i]; +printf("%2d,%-5s %d.%d = ", pRe->sIn.i, re_char(c), i, x); +restateset_print(&pRe->a); +printf(" => "); +restateset_print(&pRe->b); +printf("\n"); switch( pRe->aOp[x] ){ case RE_OP_MATCH: { - if( pRe->aArg[x]==c ) re_add_state(pNext, x+1); - break; + if( pRe->aArg[x]!=c ) break; + /* Fall thru */ } case RE_OP_ANY: { - re_add_state(pNext, x+1); + add_next: + re_add_state(pRe, x+1); break; } case RE_OP_WORD: { - if( re_word_char(c) ) re_add_state(pNext, x+1); + if( re_word_char(c) ) goto add_next; break; } case RE_OP_NOTWORD: { - if( !re_word_char(c) ) re_add_state(pNext, x+1); + if( !re_word_char(c) ) goto add_next; break; } case RE_OP_DIGIT: { - if( re_digit_char(c) ) re_add_state(pNext, x+1); + if( re_digit_char(c) ) goto add_next; break; } case RE_OP_NOTDIGIT: { - if( !re_digit_char(c) ) re_add_state(pNext, x+1); + if( !re_digit_char(c) ) goto add_next; break; } case RE_OP_SPACE: { - if( re_space_char(c) ) re_add_state(pNext, x+1); + if( re_space_char(c) ) goto add_next; break; } case RE_OP_NOTSPACE: { - if( !re_space_char(c) ) re_add_state(pNext, x+1); + if( !re_space_char(c) ) goto add_next; break; } case RE_OP_BOUNDARY: { - if( re_word_char(c)!=re_word_char(cPrev) ) re_add_state(pThis, x+1); - break; - } - case RE_OP_ANYSTAR: { - re_add_state(pNext, x); - re_add_state(pThis, x+1); - break; - } - case RE_OP_FORK: { - re_add_state(pThis, x+pRe->aArg[x]); - re_add_state(pThis, x+1); - break; - } - case RE_OP_GOTO: { - re_add_state(pThis, x+pRe->aArg[x]); + int iCur = pIn->i; + int cNext = pRe->xNextChar(pIn); + pIn->i = iCur; + if( re_word_char(c)!=re_word_char(cNext) ){ + if( iReset>=0 ) goto add_next; + re_add_state_to_set(&pRe->b, x); + } break; } - case RE_OP_ACCEPT: { - rc = 1; - goto re_match_end; - } case RE_OP_CC_INC: case RE_OP_CC_EXC: { int j = 1; @@ -318,17 +405,51 @@ static int re_match(ReCompiled *pRe, const unsigned char *zIn, int nIn){ } } if( pRe->aOp[x]==RE_OP_CC_EXC ) hit = !hit; - if( hit ) re_add_state(pNext, x+n); + if( hit ) re_add_state(pRe, x+n); break; } + case RE_OP_FORK: { + re_add_state(pRe, x+1); + /* Fall thru */ + } + case RE_OP_GOTO: { + re_add_state(pRe, x+pRe->aArg[x]); + break; + } + case RE_OP_ACCEPT: { + if( !pRe->bKeepSpan ) return 1; + pRe->iEnd = pIn->i; +printf("ACCEPT %d\n", pIn->i); + rc = 1; + break; + } + } /* End of switch( pRe->aOp[x] ) */ + } /* End of loop over states in pRe->a.aState[] */ + if( iReset<0 ){ + if( pRe->bFlexStart ){ + iReset = pRe->b.nState; + memcpy(pRe->a.aState, pRe->b.aState, iReset*sizeof(ReStateNumber)); + }else{ + iReset = 0; + } +printf("iReset=%d\n", iReset); + }else if( c==RE_EOF || pRe->b.nState==0 ){ + break; + }else if( pRe->b.nState>iReset ){ + if( iBegin<0 ){ + iBegin = iIdx; +printf("Begin at %d\n", iIdx); + }else{ + pRe->bMultiBegin = 1; +printf("Another possible beginning: %d\n", iIdx); } } + re_swap(pRe); + pRe->b.nState = iReset; + iIdx = pIn->i; + c = pRe->xNextChar(pIn); } - for(i=0; inState; i++){ - if( pRe->aOp[pNext->aState[i]]==RE_OP_ACCEPT ){ rc = 1; break; } - } -re_match_end: - sqlite3_free(pToFree); + pRe->iBegin = iBegin; return rc; } @@ -493,12 +614,7 @@ static const char *re_subcompile_string(ReCompiled *p){ break; } case '.': { - if( rePeek(p)=='*' ){ - re_append(p, RE_OP_ANYSTAR, 0); - p->sIn.i++; - }else{ - re_append(p, RE_OP_ANY, 0); - } + re_append(p, RE_OP_ANY, 0); break; } case '*': { @@ -521,12 +637,12 @@ static const char *re_subcompile_string(ReCompiled *p){ int m = 0, n = 0; int sz, j; if( iPrev<0 ) return "'{m,n}' without operand"; - while( (c=rePeek(p))>='0' && c<='9' ){ m = m*10 + c - '0'; p->sIn.i++; } + while( (c=rePeek(p))>='0' && c<='9' ){ m = m*10 + c - '0';p->sIn.i++; } n = m; if( c==',' ){ p->sIn.i++; n = 0; - while( (c=rePeek(p))>='0' && c<='9' ){ n = n*10 + c-'0'; p->sIn.i++; } + while( (c=rePeek(p))>='0' && c<='9' ){ n = n*10 + c-'0';p->sIn.i++; } } if( c!='}' ) return "unmatched '{'"; if( n>0 && naOp); sqlite3_free(pRe->aArg); + sqlite3_free(pRe->a.aState); + sqlite3_free(pRe->b.aState); sqlite3_free(pRe); } } @@ -643,7 +761,7 @@ const char *re_compile(ReCompiled **ppRe, const char *zIn, int noCase){ if( zIn[0]=='^' ){ zIn++; }else{ - re_append(pRe, RE_OP_ANYSTAR, 0); + pRe->bFlexStart = 1; } pRe->sIn.z = (unsigned char*)zIn; pRe->sIn.i = 0; @@ -664,6 +782,12 @@ const char *re_compile(ReCompiled **ppRe, const char *zIn, int noCase){ re_free(pRe); return "unrecognized character"; } + pRe->a.aState = sqlite3_malloc64( sizeof(pRe->a.aState[0])*pRe->nState ); + pRe->b.aState = sqlite3_malloc64( sizeof(pRe->b.aState[0])*pRe->nState ); + if( pRe->a.aState==0 || pRe->b.aState==0 ){ + re_free(pRe); + return "out of memory"; + } /* The following is a performance optimization. If the regex begins with ** ".*" (if the input regex lacks an initial "^") and afterwards there are @@ -673,8 +797,8 @@ const char *re_compile(ReCompiled **ppRe, const char *zIn, int noCase){ ** regex engine over the string. Do not worry able trying to match ** unicode characters beyond plane 0 - those are very rare and this is ** just an optimization. */ - if( pRe->aOp[0]==RE_OP_ANYSTAR ){ - for(j=0, i=1; jzInit)-2 && pRe->aOp[i]==RE_OP_MATCH; i++){ + if( pRe->bFlexStart && !noCase ){ + for(j=0, i=0; jzInit)-2 && pRe->aOp[i]==RE_OP_MATCH; i++){ unsigned x = pRe->aArg[i]; if( x<=127 ){ pRe->zInit[j++] = (unsigned char)x; @@ -740,6 +864,68 @@ static void re_sql_func( } } +/* +** Implementation of the regexp_test1() SQL function. +** +** regexp_test1(PATTERN, STRING) +** +** Return the part of STRING that matches PATTERN. Or return NULL +** if there is no match. +*/ +static void re_test1_func( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + ReCompiled *pRe; /* Compiled regular expression */ + const char *zPattern; /* The regular expression */ + const unsigned char *zStr;/* String being searched */ + const char *zErr; /* Compile error message */ + int setAux = 0; /* True to invoke sqlite3_set_auxdata() */ + int rc; + + pRe = sqlite3_get_auxdata(context, 0); + if( pRe==0 ){ + zPattern = (const char*)sqlite3_value_text(argv[0]); + if( zPattern==0 ) return; + zErr = re_compile(&pRe, zPattern, 0); + if( zErr ){ + re_free(pRe); + sqlite3_result_error(context, zErr, -1); + return; + } + if( pRe==0 ){ + sqlite3_result_error_nomem(context); + return; + } + setAux = 1; + } + zStr = (const unsigned char*)sqlite3_value_text(argv[1]); + if( zStr!=0 ){ + int iBegin, iEnd; + pRe->bKeepSpan = 1; + rc = re_match(pRe, zStr, -1); + if( !rc ) goto re_test1_end; + iBegin = pRe->iBegin; + iEnd = pRe->iEnd; + if( pRe->bMultiBegin ){ + pRe->bKeepSpan = 0; + while( re_match(pRe, zStr+iBegin, iEnd-iBegin)==0 ){ + if( zStr[++iBegin]>=0xc0 ){ + while( (zStr[iBegin]&0xc0)==0x80 ) iBegin++; + } + if( iBegin>=iEnd ) goto re_test1_end; + } + } + sqlite3_result_text(context, (const char*)zStr+iBegin, + iEnd - iBegin, SQLITE_TRANSIENT); + } +re_test1_end: + if( setAux ){ + sqlite3_set_auxdata(context, 0, pRe, (void(*)(void*))re_free); + } +} + /* ** Invoke this routine to register the regexp() function with the ** SQLite database connection. @@ -756,5 +942,9 @@ int sqlite3_regexp_init( SQLITE_EXTENSION_INIT2(pApi); rc = sqlite3_create_function(db, "regexp", 2, SQLITE_UTF8, 0, re_sql_func, 0, 0); + if( rc==SQLITE_OK ){ + rc = sqlite3_create_function(db, "re_test1", 2, SQLITE_UTF8, 0, + re_test1_func, 0, 0); + } return rc; } diff --git a/manifest b/manifest index 682ec0eee7..c864bc0280 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\ssupport\sfor\sthe\ssqlite_unsupported_offset()\sSQL\sfunction\sif\sand\sonly\nif\scompiled\susing\s-DSQLITE_ENABLE_OFFSET_SQL_FUNC.\s\sUse\sthat\sdefinition\nwhen\scompiling\sthe\scommand-line\sshell. -D 2017-12-29T17:21:21.319 +C Experiments\swith\sthe\sregexp.c\sextension,\strying\sto\sget\sit\sto\sreport\sthe\nexact\ssubstring\sthat\smatches\sthe\sRE. +D 2018-01-01T16:59:08.869 F Makefile.in 804c347948b0ad5a962aea3e59413e897227e0173d0e76910f11f473e6c7ae9b F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434 F Makefile.msc 57dcce0595d4a0c4b94b6f6ba732b8e33540358adf5fb8e1aa8389d34a9677e8 @@ -282,7 +282,7 @@ F ext/misc/memvfs.c e5225bc22e79dde6b28380f3a068ddf600683a33 F ext/misc/mmapwarm.c 70b618f2d0bde43fae288ad0b7498a629f2b6f61b50a27e06fae3cd23c83af29 F ext/misc/nextchar.c 35c8b8baacb96d92abbb34a83a997b797075b342 F ext/misc/percentile.c 92699c8cd7d517ff610e6037e56506f8904dae2e -F ext/misc/regexp.c a68d25c659bd2d893cd1215667bbf75ecb9dc7d4 +F ext/misc/regexp.c 9f6655fe21efc98b5c7e51d94116689fd6e17cad817b4a9eb68d978e28dd7f8a F ext/misc/remember.c add730f0f7e7436cd15ea3fd6a90fd83c3f706ab44169f7f048438b7d6baa69c F ext/misc/rot13.c 540a169cb0d74f15522a8930b0cccdcb37a4fd071d219a5a083a319fc6e8db77 F ext/misc/scrub.c 1c5bfb8b0cd18b602fcb55755e84abf0023ac2fb @@ -1688,8 +1688,10 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P f4349c0c26611de8a7d5beb99431a575cf531cdeb0ca2413efabcf0a61e6f424 9406c0a685fd5ff3516a66402b0514a1440652822a5eecf0b7af85929f3079e8 -R 35346abc344e9d51558b9d8d5c8f2d13 -T +closed 9406c0a685fd5ff3516a66402b0514a1440652822a5eecf0b7af85929f3079e8 +P 4f1f1f521ae6da96b899e10bfeff6bc1ab7a45de0705076febaae20b441f48c6 +R 1f9db37ee5bdc6528286b18b258e44ca +T *branch * regexp-span +T *sym-regexp-span * +T -sym-trunk * U drh -Z b5b643a0e7da393bb8589918154fc3c1 +Z db39d3e98113e50f1341ba7f68cc2c1a diff --git a/manifest.uuid b/manifest.uuid index 1c26686ec7..a8a3bf5873 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -4f1f1f521ae6da96b899e10bfeff6bc1ab7a45de0705076febaae20b441f48c6 \ No newline at end of file +3d6fba623af9356f6b02c7ef5f29c4bdeb2f8f0359bc0a3194951846afa336da \ No newline at end of file -- 2.39.5