mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
fc0f640736
Closes https://github.com/espressif/esp-idf/issues/9907 Closes https://github.com/espressif/esp-idf/pull/10016
1015 lines
33 KiB
C
1015 lines
33 KiB
C
/*
|
|
* SPDX-FileCopyrightText: 1998-2001,2003-2011,2013 Stewart Heitmann
|
|
*
|
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
*/
|
|
/*******************************************************************************
|
|
* arg_rex: Implements the regex command-line option
|
|
*
|
|
* This file is part of the argtable3 library.
|
|
*
|
|
* Copyright (C) 1998-2001,2003-2011,2013 Stewart Heitmann
|
|
* <sheitmann@users.sourceforge.net>
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
* * Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* * Neither the name of STEWART HEITMANN nor the names of its contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT,
|
|
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
******************************************************************************/
|
|
|
|
#include "argtable3.h"
|
|
|
|
#ifndef ARG_AMALGAMATION
|
|
#include "argtable3_private.h"
|
|
#endif
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#ifndef _TREX_H_
|
|
#define _TREX_H_
|
|
|
|
/*
|
|
* This module uses the T-Rex regular expression library to implement the regex
|
|
* logic. Here is the copyright notice of the library:
|
|
*
|
|
* Copyright (C) 2003-2006 Alberto Demichelis
|
|
*
|
|
* This software is provided 'as-is', without any express
|
|
* or implied warranty. In no event will the authors be held
|
|
* liable for any damages arising from the use of this software.
|
|
*
|
|
* Permission is granted to anyone to use this software for
|
|
* any purpose, including commercial applications, and to alter
|
|
* it and redistribute it freely, subject to the following restrictions:
|
|
*
|
|
* 1. The origin of this software must not be misrepresented;
|
|
* you must not claim that you wrote the original software.
|
|
* If you use this software in a product, an acknowledgment
|
|
* in the product documentation would be appreciated but
|
|
* is not required.
|
|
*
|
|
* 2. Altered source versions must be plainly marked as such,
|
|
* and must not be misrepresented as being the original software.
|
|
*
|
|
* 3. This notice may not be removed or altered from any
|
|
* source distribution.
|
|
*/
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
#define TRexChar char
|
|
#define MAX_CHAR 0xFF
|
|
#define _TREXC(c) (c)
|
|
#define trex_strlen strlen
|
|
#define trex_printf printf
|
|
|
|
#ifndef TREX_API
|
|
#define TREX_API extern
|
|
#endif
|
|
|
|
#define TRex_True 1
|
|
#define TRex_False 0
|
|
|
|
#define TREX_ICASE ARG_REX_ICASE
|
|
|
|
typedef unsigned int TRexBool;
|
|
typedef struct TRex TRex;
|
|
|
|
typedef struct {
|
|
const TRexChar* begin;
|
|
int len;
|
|
} TRexMatch;
|
|
|
|
#if defined(__clang__)
|
|
TREX_API TRex* trex_compile(const TRexChar* pattern, const TRexChar** error, int flags) __attribute__((optnone));
|
|
#elif defined(__GNUC__)
|
|
TREX_API TRex* trex_compile(const TRexChar* pattern, const TRexChar** error, int flags) __attribute__((optimize(0)));
|
|
#else
|
|
TREX_API TRex* trex_compile(const TRexChar* pattern, const TRexChar** error, int flags);
|
|
#endif
|
|
TREX_API void trex_free(TRex* exp);
|
|
TREX_API TRexBool trex_match(TRex* exp, const TRexChar* text);
|
|
TREX_API TRexBool trex_search(TRex* exp, const TRexChar* text, const TRexChar** out_begin, const TRexChar** out_end);
|
|
TREX_API TRexBool
|
|
trex_searchrange(TRex* exp, const TRexChar* text_begin, const TRexChar* text_end, const TRexChar** out_begin, const TRexChar** out_end);
|
|
TREX_API int trex_getsubexpcount(TRex* exp);
|
|
TREX_API TRexBool trex_getsubexp(TRex* exp, int n, TRexMatch* subexp);
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
|
|
#endif
|
|
|
|
struct privhdr {
|
|
const char* pattern;
|
|
int flags;
|
|
};
|
|
|
|
static void arg_rex_resetfn(struct arg_rex* parent) {
|
|
ARG_TRACE(("%s:resetfn(%p)\n", __FILE__, parent));
|
|
parent->count = 0;
|
|
}
|
|
|
|
static int arg_rex_scanfn(struct arg_rex* parent, const char* argval) {
|
|
int errorcode = 0;
|
|
const TRexChar* error = NULL;
|
|
TRex* rex = NULL;
|
|
TRexBool is_match = TRex_False;
|
|
|
|
if (parent->count == parent->hdr.maxcount) {
|
|
/* maximum number of arguments exceeded */
|
|
errorcode = ARG_ERR_MAXCOUNT;
|
|
} else if (!argval) {
|
|
/* a valid argument with no argument value was given. */
|
|
/* This happens when an optional argument value was invoked. */
|
|
/* leave parent argument value unaltered but still count the argument. */
|
|
parent->count++;
|
|
} else {
|
|
struct privhdr* priv = (struct privhdr*)parent->hdr.priv;
|
|
|
|
/* test the current argument value for a match with the regular expression */
|
|
/* if a match is detected, record the argument value in the arg_rex struct */
|
|
|
|
rex = trex_compile(priv->pattern, &error, priv->flags);
|
|
is_match = trex_match(rex, argval);
|
|
if (!is_match)
|
|
errorcode = ARG_ERR_REGNOMATCH;
|
|
else
|
|
parent->sval[parent->count++] = argval;
|
|
|
|
trex_free(rex);
|
|
}
|
|
|
|
ARG_TRACE(("%s:scanfn(%p) returns %d\n", __FILE__, parent, errorcode));
|
|
return errorcode;
|
|
}
|
|
|
|
static int arg_rex_checkfn(struct arg_rex* parent) {
|
|
int errorcode = (parent->count < parent->hdr.mincount) ? ARG_ERR_MINCOUNT : 0;
|
|
#if 0
|
|
struct privhdr *priv = (struct privhdr*)parent->hdr.priv;
|
|
|
|
/* free the regex "program" we constructed in resetfn */
|
|
regfree(&(priv->regex));
|
|
|
|
/*printf("%s:checkfn(%p) returns %d\n",__FILE__,parent,errorcode);*/
|
|
#endif
|
|
return errorcode;
|
|
}
|
|
|
|
static void arg_rex_errorfn(struct arg_rex* parent, arg_dstr_t ds, int errorcode, const char* argval, const char* progname) {
|
|
const char* shortopts = parent->hdr.shortopts;
|
|
const char* longopts = parent->hdr.longopts;
|
|
const char* datatype = parent->hdr.datatype;
|
|
|
|
/* make argval NULL safe */
|
|
argval = argval ? argval : "";
|
|
|
|
arg_dstr_catf(ds, "%s: ", progname);
|
|
switch (errorcode) {
|
|
case ARG_ERR_MINCOUNT:
|
|
arg_dstr_cat(ds, "missing option ");
|
|
arg_print_option_ds(ds, shortopts, longopts, datatype, "\n");
|
|
break;
|
|
|
|
case ARG_ERR_MAXCOUNT:
|
|
arg_dstr_cat(ds, "excess option ");
|
|
arg_print_option_ds(ds, shortopts, longopts, argval, "\n");
|
|
break;
|
|
|
|
case ARG_ERR_REGNOMATCH:
|
|
arg_dstr_cat(ds, "illegal value ");
|
|
arg_print_option_ds(ds, shortopts, longopts, argval, "\n");
|
|
break;
|
|
|
|
default: {
|
|
#if 0
|
|
char errbuff[256];
|
|
regerror(errorcode, NULL, errbuff, sizeof(errbuff));
|
|
printf("%s\n", errbuff);
|
|
#endif
|
|
} break;
|
|
}
|
|
}
|
|
|
|
struct arg_rex* arg_rex0(const char* shortopts, const char* longopts, const char* pattern, const char* datatype, int flags, const char* glossary) {
|
|
return arg_rexn(shortopts, longopts, pattern, datatype, 0, 1, flags, glossary);
|
|
}
|
|
|
|
struct arg_rex* arg_rex1(const char* shortopts, const char* longopts, const char* pattern, const char* datatype, int flags, const char* glossary) {
|
|
return arg_rexn(shortopts, longopts, pattern, datatype, 1, 1, flags, glossary);
|
|
}
|
|
|
|
struct arg_rex* arg_rexn(const char* shortopts,
|
|
const char* longopts,
|
|
const char* pattern,
|
|
const char* datatype,
|
|
int mincount,
|
|
int maxcount,
|
|
int flags,
|
|
const char* glossary) {
|
|
size_t nbytes;
|
|
struct arg_rex* result;
|
|
struct privhdr* priv;
|
|
int i;
|
|
const TRexChar* error = NULL;
|
|
TRex* rex = NULL;
|
|
|
|
if (!pattern) {
|
|
printf("argtable: ERROR - illegal regular expression pattern \"(NULL)\"\n");
|
|
printf("argtable: Bad argument table.\n");
|
|
return NULL;
|
|
}
|
|
|
|
/* foolproof things by ensuring maxcount is not less than mincount */
|
|
maxcount = (maxcount < mincount) ? mincount : maxcount;
|
|
|
|
nbytes = sizeof(struct arg_rex) /* storage for struct arg_rex */
|
|
+ sizeof(struct privhdr) /* storage for private arg_rex data */
|
|
+ (size_t)maxcount * sizeof(char*); /* storage for sval[maxcount] array */
|
|
|
|
/* init the arg_hdr struct */
|
|
result = (struct arg_rex*)xmalloc(nbytes);
|
|
result->hdr.flag = ARG_HASVALUE;
|
|
result->hdr.shortopts = shortopts;
|
|
result->hdr.longopts = longopts;
|
|
result->hdr.datatype = datatype ? datatype : pattern;
|
|
result->hdr.glossary = glossary;
|
|
result->hdr.mincount = mincount;
|
|
result->hdr.maxcount = maxcount;
|
|
result->hdr.parent = result;
|
|
result->hdr.resetfn = (arg_resetfn*)arg_rex_resetfn;
|
|
result->hdr.scanfn = (arg_scanfn*)arg_rex_scanfn;
|
|
result->hdr.checkfn = (arg_checkfn*)arg_rex_checkfn;
|
|
result->hdr.errorfn = (arg_errorfn*)arg_rex_errorfn;
|
|
|
|
/* store the arg_rex_priv struct immediately after the arg_rex struct */
|
|
result->hdr.priv = result + 1;
|
|
priv = (struct privhdr*)(result->hdr.priv);
|
|
priv->pattern = pattern;
|
|
priv->flags = flags;
|
|
|
|
/* store the sval[maxcount] array immediately after the arg_rex_priv struct */
|
|
result->sval = (const char**)(priv + 1);
|
|
result->count = 0;
|
|
|
|
/* foolproof the string pointers by initializing them to reference empty strings */
|
|
for (i = 0; i < maxcount; i++)
|
|
result->sval[i] = "";
|
|
|
|
/* here we construct and destroy a regex representation of the regular
|
|
* expression for no other reason than to force any regex errors to be
|
|
* trapped now rather than later. If we don't, then errors may go undetected
|
|
* until an argument is actually parsed.
|
|
*/
|
|
|
|
rex = trex_compile(priv->pattern, &error, priv->flags);
|
|
if (rex == NULL) {
|
|
ARG_LOG(("argtable: %s \"%s\"\n", error ? error : _TREXC("undefined"), priv->pattern));
|
|
ARG_LOG(("argtable: Bad argument table.\n"));
|
|
}
|
|
|
|
trex_free(rex);
|
|
|
|
ARG_TRACE(("arg_rexn() returns %p\n", result));
|
|
return result;
|
|
}
|
|
|
|
/* see copyright notice in trex.h */
|
|
#include <ctype.h>
|
|
#include <setjmp.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#ifdef _UINCODE
|
|
#define scisprint iswprint
|
|
#define scstrlen wcslen
|
|
#define scprintf wprintf
|
|
#define _SC(x) L(x)
|
|
#else
|
|
#define scisprint isprint
|
|
#define scstrlen strlen
|
|
#define scprintf printf
|
|
#define _SC(x) (x)
|
|
#endif
|
|
|
|
#ifdef ARG_REX_DEBUG
|
|
#include <stdio.h>
|
|
|
|
static const TRexChar* g_nnames[] = {_SC("NONE"), _SC("OP_GREEDY"), _SC("OP_OR"), _SC("OP_EXPR"), _SC("OP_NOCAPEXPR"),
|
|
_SC("OP_DOT"), _SC("OP_CLASS"), _SC("OP_CCLASS"), _SC("OP_NCLASS"), _SC("OP_RANGE"),
|
|
_SC("OP_CHAR"), _SC("OP_EOL"), _SC("OP_BOL"), _SC("OP_WB")};
|
|
|
|
#endif
|
|
#define OP_GREEDY (MAX_CHAR + 1) /* * + ? {n} */
|
|
#define OP_OR (MAX_CHAR + 2)
|
|
#define OP_EXPR (MAX_CHAR + 3) /* parentesis () */
|
|
#define OP_NOCAPEXPR (MAX_CHAR + 4) /* parentesis (?:) */
|
|
#define OP_DOT (MAX_CHAR + 5)
|
|
#define OP_CLASS (MAX_CHAR + 6)
|
|
#define OP_CCLASS (MAX_CHAR + 7)
|
|
#define OP_NCLASS (MAX_CHAR + 8) /* negates class the [^ */
|
|
#define OP_RANGE (MAX_CHAR + 9)
|
|
#define OP_CHAR (MAX_CHAR + 10)
|
|
#define OP_EOL (MAX_CHAR + 11)
|
|
#define OP_BOL (MAX_CHAR + 12)
|
|
#define OP_WB (MAX_CHAR + 13)
|
|
|
|
#define TREX_SYMBOL_ANY_CHAR ('.')
|
|
#define TREX_SYMBOL_GREEDY_ONE_OR_MORE ('+')
|
|
#define TREX_SYMBOL_GREEDY_ZERO_OR_MORE ('*')
|
|
#define TREX_SYMBOL_GREEDY_ZERO_OR_ONE ('?')
|
|
#define TREX_SYMBOL_BRANCH ('|')
|
|
#define TREX_SYMBOL_END_OF_STRING ('$')
|
|
#define TREX_SYMBOL_BEGINNING_OF_STRING ('^')
|
|
#define TREX_SYMBOL_ESCAPE_CHAR ('\\')
|
|
|
|
typedef int TRexNodeType;
|
|
|
|
typedef struct tagTRexNode {
|
|
TRexNodeType type;
|
|
int left;
|
|
int right;
|
|
int next;
|
|
} TRexNode;
|
|
|
|
struct TRex {
|
|
const TRexChar* _eol;
|
|
const TRexChar* _bol;
|
|
const TRexChar* _p;
|
|
int _first;
|
|
int _op;
|
|
TRexNode* _nodes;
|
|
int _nallocated;
|
|
int _nsize;
|
|
int _nsubexpr;
|
|
TRexMatch* _matches;
|
|
int _currsubexp;
|
|
void* _jmpbuf;
|
|
const TRexChar** _error;
|
|
int _flags;
|
|
};
|
|
|
|
static int trex_list(TRex* exp);
|
|
|
|
static int trex_newnode(TRex* exp, TRexNodeType type) {
|
|
TRexNode n;
|
|
int newid;
|
|
n.type = type;
|
|
n.next = n.right = n.left = -1;
|
|
if (type == OP_EXPR)
|
|
n.right = exp->_nsubexpr++;
|
|
if (exp->_nallocated < (exp->_nsize + 1)) {
|
|
exp->_nallocated *= 2;
|
|
exp->_nodes = (TRexNode*)xrealloc(exp->_nodes, (size_t)exp->_nallocated * sizeof(TRexNode));
|
|
}
|
|
exp->_nodes[exp->_nsize++] = n;
|
|
newid = exp->_nsize - 1;
|
|
return (int)newid;
|
|
}
|
|
|
|
static void trex_error(TRex* exp, const TRexChar* error) {
|
|
if (exp->_error)
|
|
*exp->_error = error;
|
|
longjmp(*((jmp_buf*)exp->_jmpbuf), -1);
|
|
}
|
|
|
|
static void trex_expect(TRex* exp, int n) {
|
|
if ((*exp->_p) != n)
|
|
trex_error(exp, _SC("expected paren"));
|
|
exp->_p++;
|
|
}
|
|
|
|
static TRexChar trex_escapechar(TRex* exp) {
|
|
if (*exp->_p == TREX_SYMBOL_ESCAPE_CHAR) {
|
|
exp->_p++;
|
|
switch (*exp->_p) {
|
|
case 'v':
|
|
exp->_p++;
|
|
return '\v';
|
|
case 'n':
|
|
exp->_p++;
|
|
return '\n';
|
|
case 't':
|
|
exp->_p++;
|
|
return '\t';
|
|
case 'r':
|
|
exp->_p++;
|
|
return '\r';
|
|
case 'f':
|
|
exp->_p++;
|
|
return '\f';
|
|
default:
|
|
return (*exp->_p++);
|
|
}
|
|
} else if (!scisprint((int)(*exp->_p)))
|
|
trex_error(exp, _SC("letter expected"));
|
|
return (*exp->_p++);
|
|
}
|
|
|
|
static int trex_charclass(TRex* exp, int classid) {
|
|
int n = trex_newnode(exp, OP_CCLASS);
|
|
exp->_nodes[n].left = classid;
|
|
return n;
|
|
}
|
|
|
|
static int trex_charnode(TRex* exp, TRexBool isclass) {
|
|
TRexChar t;
|
|
if (*exp->_p == TREX_SYMBOL_ESCAPE_CHAR) {
|
|
exp->_p++;
|
|
switch (*exp->_p) {
|
|
case 'n':
|
|
exp->_p++;
|
|
return trex_newnode(exp, '\n');
|
|
case 't':
|
|
exp->_p++;
|
|
return trex_newnode(exp, '\t');
|
|
case 'r':
|
|
exp->_p++;
|
|
return trex_newnode(exp, '\r');
|
|
case 'f':
|
|
exp->_p++;
|
|
return trex_newnode(exp, '\f');
|
|
case 'v':
|
|
exp->_p++;
|
|
return trex_newnode(exp, '\v');
|
|
case 'a':
|
|
case 'A':
|
|
case 'w':
|
|
case 'W':
|
|
case 's':
|
|
case 'S':
|
|
case 'd':
|
|
case 'D':
|
|
case 'x':
|
|
case 'X':
|
|
case 'c':
|
|
case 'C':
|
|
case 'p':
|
|
case 'P':
|
|
case 'l':
|
|
case 'u': {
|
|
t = *exp->_p;
|
|
exp->_p++;
|
|
return trex_charclass(exp, t);
|
|
}
|
|
case 'b':
|
|
case 'B':
|
|
if (!isclass) {
|
|
int node = trex_newnode(exp, OP_WB);
|
|
exp->_nodes[node].left = *exp->_p;
|
|
exp->_p++;
|
|
return node;
|
|
}
|
|
/* fall through */
|
|
default:
|
|
t = *exp->_p;
|
|
exp->_p++;
|
|
return trex_newnode(exp, t);
|
|
}
|
|
} else if (!scisprint((int)(*exp->_p))) {
|
|
trex_error(exp, _SC("letter expected"));
|
|
}
|
|
t = *exp->_p;
|
|
exp->_p++;
|
|
return trex_newnode(exp, t);
|
|
}
|
|
static int trex_class(TRex* exp) {
|
|
int ret = -1;
|
|
int first = -1, chain;
|
|
if (*exp->_p == TREX_SYMBOL_BEGINNING_OF_STRING) {
|
|
ret = trex_newnode(exp, OP_NCLASS);
|
|
exp->_p++;
|
|
} else
|
|
ret = trex_newnode(exp, OP_CLASS);
|
|
|
|
if (*exp->_p == ']')
|
|
trex_error(exp, _SC("empty class"));
|
|
chain = ret;
|
|
while (*exp->_p != ']' && exp->_p != exp->_eol) {
|
|
if (*exp->_p == '-' && first != -1) {
|
|
int r, t;
|
|
if (*exp->_p++ == ']')
|
|
trex_error(exp, _SC("unfinished range"));
|
|
r = trex_newnode(exp, OP_RANGE);
|
|
if (first > *exp->_p)
|
|
trex_error(exp, _SC("invalid range"));
|
|
if (exp->_nodes[first].type == OP_CCLASS)
|
|
trex_error(exp, _SC("cannot use character classes in ranges"));
|
|
exp->_nodes[r].left = exp->_nodes[first].type;
|
|
t = trex_escapechar(exp);
|
|
exp->_nodes[r].right = t;
|
|
exp->_nodes[chain].next = r;
|
|
chain = r;
|
|
first = -1;
|
|
} else {
|
|
if (first != -1) {
|
|
int c = first;
|
|
exp->_nodes[chain].next = c;
|
|
chain = c;
|
|
first = trex_charnode(exp, TRex_True);
|
|
} else {
|
|
first = trex_charnode(exp, TRex_True);
|
|
}
|
|
}
|
|
}
|
|
if (first != -1) {
|
|
int c = first;
|
|
exp->_nodes[chain].next = c;
|
|
chain = c;
|
|
first = -1;
|
|
}
|
|
/* hack? */
|
|
exp->_nodes[ret].left = exp->_nodes[ret].next;
|
|
exp->_nodes[ret].next = -1;
|
|
return ret;
|
|
}
|
|
|
|
static int trex_parsenumber(TRex* exp) {
|
|
int ret = *exp->_p - '0';
|
|
int positions = 10;
|
|
exp->_p++;
|
|
while (isdigit((int)(*exp->_p))) {
|
|
ret = ret * 10 + (*exp->_p++ - '0');
|
|
if (positions == 1000000000)
|
|
trex_error(exp, _SC("overflow in numeric constant"));
|
|
positions *= 10;
|
|
};
|
|
return ret;
|
|
}
|
|
|
|
static int trex_element(TRex* exp) {
|
|
int ret = -1;
|
|
switch (*exp->_p) {
|
|
case '(': {
|
|
int expr, newn;
|
|
exp->_p++;
|
|
|
|
if (*exp->_p == '?') {
|
|
exp->_p++;
|
|
trex_expect(exp, ':');
|
|
expr = trex_newnode(exp, OP_NOCAPEXPR);
|
|
} else
|
|
expr = trex_newnode(exp, OP_EXPR);
|
|
newn = trex_list(exp);
|
|
exp->_nodes[expr].left = newn;
|
|
ret = expr;
|
|
trex_expect(exp, ')');
|
|
} break;
|
|
case '[':
|
|
exp->_p++;
|
|
ret = trex_class(exp);
|
|
trex_expect(exp, ']');
|
|
break;
|
|
case TREX_SYMBOL_END_OF_STRING:
|
|
exp->_p++;
|
|
ret = trex_newnode(exp, OP_EOL);
|
|
break;
|
|
case TREX_SYMBOL_ANY_CHAR:
|
|
exp->_p++;
|
|
ret = trex_newnode(exp, OP_DOT);
|
|
break;
|
|
default:
|
|
ret = trex_charnode(exp, TRex_False);
|
|
break;
|
|
}
|
|
|
|
{
|
|
TRexBool isgreedy = TRex_False;
|
|
unsigned short p0 = 0, p1 = 0;
|
|
switch (*exp->_p) {
|
|
case TREX_SYMBOL_GREEDY_ZERO_OR_MORE:
|
|
p0 = 0;
|
|
p1 = 0xFFFF;
|
|
exp->_p++;
|
|
isgreedy = TRex_True;
|
|
break;
|
|
case TREX_SYMBOL_GREEDY_ONE_OR_MORE:
|
|
p0 = 1;
|
|
p1 = 0xFFFF;
|
|
exp->_p++;
|
|
isgreedy = TRex_True;
|
|
break;
|
|
case TREX_SYMBOL_GREEDY_ZERO_OR_ONE:
|
|
p0 = 0;
|
|
p1 = 1;
|
|
exp->_p++;
|
|
isgreedy = TRex_True;
|
|
break;
|
|
case '{':
|
|
exp->_p++;
|
|
if (!isdigit((int)(*exp->_p)))
|
|
trex_error(exp, _SC("number expected"));
|
|
p0 = (unsigned short)trex_parsenumber(exp);
|
|
/*******************************/
|
|
switch (*exp->_p) {
|
|
case '}':
|
|
p1 = p0;
|
|
exp->_p++;
|
|
break;
|
|
case ',':
|
|
exp->_p++;
|
|
p1 = 0xFFFF;
|
|
if (isdigit((int)(*exp->_p))) {
|
|
p1 = (unsigned short)trex_parsenumber(exp);
|
|
}
|
|
trex_expect(exp, '}');
|
|
break;
|
|
default:
|
|
trex_error(exp, _SC(", or } expected"));
|
|
}
|
|
/*******************************/
|
|
isgreedy = TRex_True;
|
|
break;
|
|
}
|
|
if (isgreedy) {
|
|
int nnode = trex_newnode(exp, OP_GREEDY);
|
|
exp->_nodes[nnode].left = ret;
|
|
exp->_nodes[nnode].right = ((p0) << 16) | p1;
|
|
ret = nnode;
|
|
}
|
|
}
|
|
if ((*exp->_p != TREX_SYMBOL_BRANCH) && (*exp->_p != ')') && (*exp->_p != TREX_SYMBOL_GREEDY_ZERO_OR_MORE) &&
|
|
(*exp->_p != TREX_SYMBOL_GREEDY_ONE_OR_MORE) && (*exp->_p != '\0')) {
|
|
int nnode = trex_element(exp);
|
|
exp->_nodes[ret].next = nnode;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int trex_list(TRex* exp) {
|
|
int ret = -1, e;
|
|
if (*exp->_p == TREX_SYMBOL_BEGINNING_OF_STRING) {
|
|
exp->_p++;
|
|
ret = trex_newnode(exp, OP_BOL);
|
|
}
|
|
e = trex_element(exp);
|
|
if (ret != -1) {
|
|
exp->_nodes[ret].next = e;
|
|
} else
|
|
ret = e;
|
|
|
|
if (*exp->_p == TREX_SYMBOL_BRANCH) {
|
|
int temp, tright;
|
|
exp->_p++;
|
|
temp = trex_newnode(exp, OP_OR);
|
|
exp->_nodes[temp].left = ret;
|
|
tright = trex_list(exp);
|
|
exp->_nodes[temp].right = tright;
|
|
ret = temp;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static TRexBool trex_matchcclass(int cclass, TRexChar c) {
|
|
switch (cclass) {
|
|
case 'a':
|
|
return isalpha(c) ? TRex_True : TRex_False;
|
|
case 'A':
|
|
return !isalpha(c) ? TRex_True : TRex_False;
|
|
case 'w':
|
|
return (isalnum(c) || c == '_') ? TRex_True : TRex_False;
|
|
case 'W':
|
|
return (!isalnum(c) && c != '_') ? TRex_True : TRex_False;
|
|
case 's':
|
|
return isspace(c) ? TRex_True : TRex_False;
|
|
case 'S':
|
|
return !isspace(c) ? TRex_True : TRex_False;
|
|
case 'd':
|
|
return isdigit(c) ? TRex_True : TRex_False;
|
|
case 'D':
|
|
return !isdigit(c) ? TRex_True : TRex_False;
|
|
case 'x':
|
|
return isxdigit(c) ? TRex_True : TRex_False;
|
|
case 'X':
|
|
return !isxdigit(c) ? TRex_True : TRex_False;
|
|
case 'c':
|
|
return iscntrl(c) ? TRex_True : TRex_False;
|
|
case 'C':
|
|
return !iscntrl(c) ? TRex_True : TRex_False;
|
|
case 'p':
|
|
return ispunct(c) ? TRex_True : TRex_False;
|
|
case 'P':
|
|
return !ispunct(c) ? TRex_True : TRex_False;
|
|
case 'l':
|
|
return islower(c) ? TRex_True : TRex_False;
|
|
case 'u':
|
|
return isupper(c) ? TRex_True : TRex_False;
|
|
}
|
|
return TRex_False; /*cannot happen*/
|
|
}
|
|
|
|
static TRexBool trex_matchclass(TRex* exp, TRexNode* node, TRexChar c) {
|
|
do {
|
|
switch (node->type) {
|
|
case OP_RANGE:
|
|
if (exp->_flags & TREX_ICASE) {
|
|
if (c >= toupper(node->left) && c <= toupper(node->right))
|
|
return TRex_True;
|
|
if (c >= tolower(node->left) && c <= tolower(node->right))
|
|
return TRex_True;
|
|
} else {
|
|
if (c >= node->left && c <= node->right)
|
|
return TRex_True;
|
|
}
|
|
break;
|
|
case OP_CCLASS:
|
|
if (trex_matchcclass(node->left, c))
|
|
return TRex_True;
|
|
break;
|
|
default:
|
|
if (exp->_flags & TREX_ICASE) {
|
|
if (c == tolower(node->type) || c == toupper(node->type))
|
|
return TRex_True;
|
|
} else {
|
|
if (c == node->type)
|
|
return TRex_True;
|
|
}
|
|
}
|
|
} while ((node->next != -1) && ((node = &exp->_nodes[node->next]) != NULL));
|
|
return TRex_False;
|
|
}
|
|
|
|
static const TRexChar* trex_matchnode(TRex* exp, TRexNode* node, const TRexChar* str, TRexNode* next) {
|
|
TRexNodeType type = node->type;
|
|
switch (type) {
|
|
case OP_GREEDY: {
|
|
/* TRexNode *greedystop = (node->next != -1) ? &exp->_nodes[node->next] : NULL; */
|
|
TRexNode* greedystop = NULL;
|
|
int p0 = (node->right >> 16) & 0x0000FFFF, p1 = node->right & 0x0000FFFF, nmaches = 0;
|
|
const TRexChar *s = str, *good = str;
|
|
|
|
if (node->next != -1) {
|
|
greedystop = &exp->_nodes[node->next];
|
|
} else {
|
|
greedystop = next;
|
|
}
|
|
|
|
while ((nmaches == 0xFFFF || nmaches < p1)) {
|
|
const TRexChar* stop;
|
|
if ((s = trex_matchnode(exp, &exp->_nodes[node->left], s, greedystop)) == NULL)
|
|
break;
|
|
nmaches++;
|
|
good = s;
|
|
if (greedystop) {
|
|
/* checks that 0 matches satisfy the expression(if so skips) */
|
|
/* if not would always stop(for instance if is a '?') */
|
|
if (greedystop->type != OP_GREEDY || (greedystop->type == OP_GREEDY && ((greedystop->right >> 16) & 0x0000FFFF) != 0)) {
|
|
TRexNode* gnext = NULL;
|
|
if (greedystop->next != -1) {
|
|
gnext = &exp->_nodes[greedystop->next];
|
|
} else if (next && next->next != -1) {
|
|
gnext = &exp->_nodes[next->next];
|
|
}
|
|
stop = trex_matchnode(exp, greedystop, s, gnext);
|
|
if (stop) {
|
|
/* if satisfied stop it */
|
|
if (p0 == p1 && p0 == nmaches)
|
|
break;
|
|
else if (nmaches >= p0 && p1 == 0xFFFF)
|
|
break;
|
|
else if (nmaches >= p0 && nmaches <= p1)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (s >= exp->_eol)
|
|
break;
|
|
}
|
|
if (p0 == p1 && p0 == nmaches)
|
|
return good;
|
|
else if (nmaches >= p0 && p1 == 0xFFFF)
|
|
return good;
|
|
else if (nmaches >= p0 && nmaches <= p1)
|
|
return good;
|
|
return NULL;
|
|
}
|
|
case OP_OR: {
|
|
const TRexChar* asd = str;
|
|
TRexNode* temp = &exp->_nodes[node->left];
|
|
while ((asd = trex_matchnode(exp, temp, asd, NULL)) != NULL) {
|
|
if (temp->next != -1)
|
|
temp = &exp->_nodes[temp->next];
|
|
else
|
|
return asd;
|
|
}
|
|
asd = str;
|
|
temp = &exp->_nodes[node->right];
|
|
while ((asd = trex_matchnode(exp, temp, asd, NULL)) != NULL) {
|
|
if (temp->next != -1)
|
|
temp = &exp->_nodes[temp->next];
|
|
else
|
|
return asd;
|
|
}
|
|
return NULL;
|
|
break;
|
|
}
|
|
case OP_EXPR:
|
|
case OP_NOCAPEXPR: {
|
|
TRexNode* n = &exp->_nodes[node->left];
|
|
const TRexChar* cur = str;
|
|
int capture = -1;
|
|
if (node->type != OP_NOCAPEXPR && node->right == exp->_currsubexp) {
|
|
capture = exp->_currsubexp;
|
|
exp->_matches[capture].begin = cur;
|
|
exp->_currsubexp++;
|
|
}
|
|
|
|
do {
|
|
TRexNode* subnext = NULL;
|
|
if (n->next != -1) {
|
|
subnext = &exp->_nodes[n->next];
|
|
} else {
|
|
subnext = next;
|
|
}
|
|
if ((cur = trex_matchnode(exp, n, cur, subnext)) == NULL) {
|
|
if (capture != -1) {
|
|
exp->_matches[capture].begin = 0;
|
|
exp->_matches[capture].len = 0;
|
|
}
|
|
return NULL;
|
|
}
|
|
} while ((n->next != -1) && ((n = &exp->_nodes[n->next]) != NULL));
|
|
|
|
if (capture != -1)
|
|
exp->_matches[capture].len = (int)(cur - exp->_matches[capture].begin);
|
|
return cur;
|
|
}
|
|
case OP_WB:
|
|
if ((str == exp->_bol && !isspace((int)(*str))) || (str == exp->_eol && !isspace((int)(*(str - 1)))) || (!isspace((int)(*str)) && isspace((int)(*(str + 1)))) ||
|
|
(isspace((int)(*str)) && !isspace((int)(*(str + 1))))) {
|
|
return (node->left == 'b') ? str : NULL;
|
|
}
|
|
return (node->left == 'b') ? NULL : str;
|
|
case OP_BOL:
|
|
if (str == exp->_bol)
|
|
return str;
|
|
return NULL;
|
|
case OP_EOL:
|
|
if (str == exp->_eol)
|
|
return str;
|
|
return NULL;
|
|
case OP_DOT: {
|
|
str++;
|
|
}
|
|
return str;
|
|
case OP_NCLASS:
|
|
case OP_CLASS:
|
|
if (trex_matchclass(exp, &exp->_nodes[node->left], *str) ? (type == OP_CLASS ? TRex_True : TRex_False)
|
|
: (type == OP_NCLASS ? TRex_True : TRex_False)) {
|
|
str++;
|
|
return str;
|
|
}
|
|
return NULL;
|
|
case OP_CCLASS:
|
|
if (trex_matchcclass(node->left, *str)) {
|
|
str++;
|
|
return str;
|
|
}
|
|
return NULL;
|
|
default: /* char */
|
|
if (exp->_flags & TREX_ICASE) {
|
|
if (*str != tolower(node->type) && *str != toupper(node->type))
|
|
return NULL;
|
|
} else {
|
|
if (*str != node->type)
|
|
return NULL;
|
|
}
|
|
str++;
|
|
return str;
|
|
}
|
|
}
|
|
|
|
/* public api */
|
|
TRex* trex_compile(const TRexChar* pattern, const TRexChar** error, int flags) {
|
|
TRex* exp = (TRex*)xmalloc(sizeof(TRex));
|
|
exp->_eol = exp->_bol = NULL;
|
|
exp->_p = pattern;
|
|
exp->_nallocated = (int)(scstrlen(pattern) * sizeof(TRexChar));
|
|
exp->_nodes = (TRexNode*)xmalloc((size_t)exp->_nallocated * sizeof(TRexNode));
|
|
exp->_nsize = 0;
|
|
exp->_matches = 0;
|
|
exp->_nsubexpr = 0;
|
|
exp->_first = trex_newnode(exp, OP_EXPR);
|
|
exp->_error = error;
|
|
exp->_jmpbuf = xmalloc(sizeof(jmp_buf));
|
|
exp->_flags = flags;
|
|
if (setjmp(*((jmp_buf*)exp->_jmpbuf)) == 0) {
|
|
int res = trex_list(exp);
|
|
exp->_nodes[exp->_first].left = res;
|
|
if (*exp->_p != '\0')
|
|
trex_error(exp, _SC("unexpected character"));
|
|
#ifdef ARG_REX_DEBUG
|
|
{
|
|
int nsize, i;
|
|
nsize = exp->_nsize;
|
|
scprintf(_SC("\n"));
|
|
for (i = 0; i < nsize; i++) {
|
|
if (exp->_nodes[i].type > MAX_CHAR)
|
|
scprintf(_SC("[%02d] %10s "), i, g_nnames[exp->_nodes[i].type - MAX_CHAR]);
|
|
else
|
|
scprintf(_SC("[%02d] %10c "), i, exp->_nodes[i].type);
|
|
scprintf(_SC("left %02d right %02d next %02d\n"), exp->_nodes[i].left, exp->_nodes[i].right, exp->_nodes[i].next);
|
|
}
|
|
scprintf(_SC("\n"));
|
|
}
|
|
#endif
|
|
exp->_matches = (TRexMatch*)xmalloc((size_t)exp->_nsubexpr * sizeof(TRexMatch));
|
|
memset(exp->_matches, 0, (size_t)exp->_nsubexpr * sizeof(TRexMatch));
|
|
} else {
|
|
trex_free(exp);
|
|
return NULL;
|
|
}
|
|
return exp;
|
|
}
|
|
|
|
void trex_free(TRex* exp) {
|
|
if (exp) {
|
|
xfree(exp->_nodes);
|
|
xfree(exp->_jmpbuf);
|
|
xfree(exp->_matches);
|
|
xfree(exp);
|
|
}
|
|
}
|
|
|
|
TRexBool trex_match(TRex* exp, const TRexChar* text) {
|
|
const TRexChar* res = NULL;
|
|
exp->_bol = text;
|
|
exp->_eol = text + scstrlen(text);
|
|
exp->_currsubexp = 0;
|
|
res = trex_matchnode(exp, exp->_nodes, text, NULL);
|
|
if (res == NULL || res != exp->_eol)
|
|
return TRex_False;
|
|
return TRex_True;
|
|
}
|
|
|
|
TRexBool trex_searchrange(TRex* exp, const TRexChar* text_begin, const TRexChar* text_end, const TRexChar** out_begin, const TRexChar** out_end) {
|
|
const TRexChar* cur = NULL;
|
|
int node = exp->_first;
|
|
if (text_begin >= text_end)
|
|
return TRex_False;
|
|
exp->_bol = text_begin;
|
|
exp->_eol = text_end;
|
|
do {
|
|
cur = text_begin;
|
|
while (node != -1) {
|
|
exp->_currsubexp = 0;
|
|
cur = trex_matchnode(exp, &exp->_nodes[node], cur, NULL);
|
|
if (!cur)
|
|
break;
|
|
node = exp->_nodes[node].next;
|
|
}
|
|
text_begin++;
|
|
} while (cur == NULL && text_begin != text_end);
|
|
|
|
if (cur == NULL)
|
|
return TRex_False;
|
|
|
|
--text_begin;
|
|
|
|
if (out_begin)
|
|
*out_begin = text_begin;
|
|
if (out_end)
|
|
*out_end = cur;
|
|
return TRex_True;
|
|
}
|
|
|
|
TRexBool trex_search(TRex* exp, const TRexChar* text, const TRexChar** out_begin, const TRexChar** out_end) {
|
|
return trex_searchrange(exp, text, text + scstrlen(text), out_begin, out_end);
|
|
}
|
|
|
|
int trex_getsubexpcount(TRex* exp) {
|
|
return exp->_nsubexpr;
|
|
}
|
|
|
|
TRexBool trex_getsubexp(TRex* exp, int n, TRexMatch* subexp) {
|
|
if (n < 0 || n >= exp->_nsubexpr)
|
|
return TRex_False;
|
|
*subexp = exp->_matches[n];
|
|
return TRex_True;
|
|
}
|