Style change: added cino=:0 (i.e. case statements on same indentation as

switch). Also fixed issue where $$ would fail to match with trailing
newline on file
This commit is contained in:
Bruce Hill 2021-08-28 16:05:30 -07:00
parent d44806f746
commit f8860c385e
20 changed files with 699 additions and 698 deletions

2
bp.c
View File

@ -562,4 +562,4 @@ int main(int argc, char *argv[])
exit(found > 0 ? EXIT_SUCCESS : EXIT_FAILURE);
}
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0

View File

@ -68,4 +68,4 @@ def_t *free_defs(def_t *defs, def_t *stop)
return defs;
}
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0

View File

@ -17,4 +17,4 @@ __attribute__((nonnull(1)))
def_t *free_defs(def_t *defs, def_t *stop);
#endif
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0

View File

@ -65,9 +65,9 @@ static void _explain_matches(match_node_t *firstmatch, int depth, const char *te
for (size_t i = 0; i < viz_typelen; i++) {
switch (viz_type[i]) {
case '\n': printf(""); break;
case '\t': printf(""); break;
default: printf("%c", viz_type[i]); break;
case '\n': printf(""); break;
case '\t': printf(""); break;
default: printf("%c", viz_type[i]); break;
}
}
@ -169,3 +169,5 @@ void explain_match(match_t *m)
_explain_matches(&first, 0, m->start, (size_t)(m->end - m->start));
printf("\033[?7h"); // Re-enable line wrapping
}
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0

View File

@ -10,4 +10,4 @@ __attribute__((nonnull))
void explain_match(match_t *m);
#endif
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0

View File

@ -383,4 +383,4 @@ void cache_destroy(file_t *f)
f->cache.size = 0;
}
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0

View File

@ -53,4 +53,4 @@ __attribute__((nonnull))
void cache_destroy(file_t *f);
#endif
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0

12
json.c
View File

@ -29,11 +29,11 @@ static int _json_match(const char *text, match_t *m, int comma, bool verbose)
printf("{\"rule\":\"");
for (const char *c = m->pat->start; c < m->pat->end; c++) {
switch (*c) {
case '"': printf("\\\""); break;
case '\\': printf("\\\\"); break;
case '\t': printf("\\t"); break;
case '\n': printf(""); break;
default: printf("%c", *c); break;
case '"': printf("\\\""); break;
case '\\': printf("\\\\"); break;
case '\t': printf("\\t"); break;
case '\n': printf(""); break;
default: printf("%c", *c); break;
}
}
printf("\",\"start\":%ld,\"end\":%ld,\"children\":[",
@ -52,4 +52,4 @@ void json_match(const char *text, match_t *m, bool verbose)
(void)_json_match(text, m, 0, verbose);
}
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0

2
json.h
View File

@ -12,4 +12,4 @@ __attribute__((nonnull))
void json_match(const char *text, match_t *m, bool verbose);
#endif
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0

720
match.c
View File

@ -106,25 +106,25 @@ static pat_t *first_pat(def_t *defs, pat_t *pat)
{
for (pat_t *p = pat; p; ) {
switch (p->type) {
case BP_BEFORE:
p = p->args.pat; break;
case BP_REPEAT:
if (p->args.repetitions.min == 0)
return p;
p = p->args.repetitions.repeat_pat; break;
case BP_CAPTURE:
p = p->args.capture.capture_pat; break;
case BP_CHAIN: case BP_MATCH: case BP_NOT_MATCH:
p = p->args.multiple.first; break;
case BP_REPLACE:
p = p->args.replace.pat; break;
case BP_REF: {
pat_t *p2 = deref(defs, p);
if (p2 == p) return p2;
p = p2;
break;
}
default: return p;
case BP_BEFORE:
p = p->args.pat; break;
case BP_REPEAT:
if (p->args.repetitions.min == 0)
return p;
p = p->args.repetitions.repeat_pat; break;
case BP_CAPTURE:
p = p->args.capture.capture_pat; break;
case BP_CHAIN: case BP_MATCH: case BP_NOT_MATCH:
p = p->args.multiple.first; break;
case BP_REPLACE:
p = p->args.replace.pat; break;
case BP_REF: {
pat_t *p2 = deref(defs, p);
if (p2 == p) return p2;
p = p2;
break;
}
default: return p;
}
}
return pat;
@ -187,376 +187,376 @@ match_t *next_match(def_t *defs, file_t *f, match_t *prev, pat_t *pat, pat_t *sk
static match_t *match(def_t *defs, file_t *f, const char *str, pat_t *pat, bool ignorecase)
{
switch (pat->type) {
case BP_DEFINITION: {
def_t *defs2 = with_def(defs, pat->args.def.namelen, pat->args.def.name, pat->args.def.def);
match_t *m = match(defs2, f, str, pat->args.def.pat ? pat->args.def.pat : pat->args.def.def, ignorecase);
defs = free_defs(defs2, defs);
return m;
case BP_DEFINITION: {
def_t *defs2 = with_def(defs, pat->args.def.namelen, pat->args.def.name, pat->args.def.def);
match_t *m = match(defs2, f, str, pat->args.def.pat ? pat->args.def.pat : pat->args.def.def, ignorecase);
defs = free_defs(defs2, defs);
return m;
}
case BP_LEFTRECURSION: {
// Left recursion occurs when a pattern directly or indirectly
// invokes itself at the same position in the text. It's handled as
// a special case, but if a pattern invokes itself at a later
// point, it can be handled with normal recursion.
// See: left-recursion.md for more details.
if (str == pat->args.leftrec.at) {
++pat->args.leftrec.visits;
return pat->args.leftrec.match;
} else {
return match(defs, f, str, pat->args.leftrec.fallback, ignorecase);
}
case BP_LEFTRECURSION: {
// Left recursion occurs when a pattern directly or indirectly
// invokes itself at the same position in the text. It's handled as
// a special case, but if a pattern invokes itself at a later
// point, it can be handled with normal recursion.
// See: left-recursion.md for more details.
if (str == pat->args.leftrec.at) {
++pat->args.leftrec.visits;
return pat->args.leftrec.match;
} else {
return match(defs, f, str, pat->args.leftrec.fallback, ignorecase);
}
}
case BP_ANYCHAR: {
return (str < f->end && *str != '\n') ? new_match(defs, pat, str, next_char(f, str), NULL) : NULL;
}
case BP_ID_START: {
return (str < f->end && isidstart(f, str)) ? new_match(defs, pat, str, next_char(f, str), NULL) : NULL;
}
case BP_ID_CONTINUE: {
return (str < f->end && isidcontinue(f, str)) ? new_match(defs, pat, str, next_char(f, str), NULL) : NULL;
}
case BP_START_OF_FILE: {
return (str == f->start) ? new_match(defs, pat, str, str, NULL) : NULL;
}
case BP_START_OF_LINE: {
return (str == f->start || str[-1] == '\n') ? new_match(defs, pat, str, str, NULL) : NULL;
}
case BP_END_OF_FILE: {
return (str == f->end) ? new_match(defs, pat, str, str, NULL) : NULL;
}
case BP_END_OF_LINE: {
return (str == f->end || *str == '\n') ? new_match(defs, pat, str, str, NULL) : NULL;
}
case BP_WORD_BOUNDARY: {
return (str == f->start || isidcontinue(f, str) != isidcontinue(f, prev_char(f, str))) ? new_match(defs, pat, str, str, NULL) : NULL;
}
case BP_STRING: {
if (&str[pat->min_matchlen] > f->end) return NULL;
if (pat->min_matchlen > 0 && (ignorecase ? memicmp : memcmp)(str, pat->args.string, pat->min_matchlen) != 0)
return NULL;
return new_match(defs, pat, str, str + pat->min_matchlen, NULL);
}
case BP_RANGE: {
if (str >= f->end) return NULL;
if ((unsigned char)*str < pat->args.range.low || (unsigned char)*str > pat->args.range.high)
return NULL;
return new_match(defs, pat, str, str+1, NULL);
}
case BP_NOT: {
match_t *m = match(defs, f, str, pat->args.pat, ignorecase);
if (m != NULL) {
recycle_if_unused(&m);
return NULL;
}
return new_match(defs, pat, str, str, NULL);
}
case BP_UPTO: case BP_UPTO_STRICT: {
match_t *m = new_match(defs, pat, str, str, NULL);
pat_t *target = deref(defs, pat->args.multiple.first),
*skip = deref(defs, pat->args.multiple.second);
if (!target && !skip) {
while (str < f->end && *str != '\n') ++str;
m->end = str;
return m;
}
size_t child_cap = 0, nchildren = 0;
for (const char *prev = NULL; prev < str; ) {
prev = str;
if (target) {
match_t *p = match(defs, f, str, target, ignorecase);
if (p != NULL) {
recycle_if_unused(&p);
m->end = str;
return m;
}
} else if (str == f->end) {
m->end = str;
return m;
}
if (skip) {
match_t *s = match(defs, f, str, skip, ignorecase);
if (s != NULL) {
str = s->end;
if (nchildren+2 >= child_cap) {
m->children = grow(m->children, child_cap += 5);
for (size_t i = nchildren; i < child_cap; i++) m->children[i] = NULL;
}
add_owner(&m->children[nchildren++], s);
continue;
}
}
// This isn't in the for() structure because there needs to
// be at least once chance to match the pattern, even if
// we're at the end of the string already (e.g. "..$").
if (str < f->end && *str != '\n' && pat->type != BP_UPTO_STRICT)
str = next_char(f, str);
}
}
case BP_ANYCHAR: {
return (str < f->end && *str != '\n') ? new_match(defs, pat, str, next_char(f, str), NULL) : NULL;
}
case BP_ID_START: {
return (str < f->end && isidstart(f, str)) ? new_match(defs, pat, str, next_char(f, str), NULL) : NULL;
}
case BP_ID_CONTINUE: {
return (str < f->end && isidcontinue(f, str)) ? new_match(defs, pat, str, next_char(f, str), NULL) : NULL;
}
case BP_START_OF_FILE: {
return (str == f->start) ? new_match(defs, pat, str, str, NULL) : NULL;
}
case BP_START_OF_LINE: {
return (str == f->start || str[-1] == '\n') ? new_match(defs, pat, str, str, NULL) : NULL;
}
case BP_END_OF_FILE: {
return (str == f->end || (str == f->end-1 && *str == '\n')) ? new_match(defs, pat, str, str, NULL) : NULL;
}
case BP_END_OF_LINE: {
return (str == f->end || *str == '\n') ? new_match(defs, pat, str, str, NULL) : NULL;
}
case BP_WORD_BOUNDARY: {
return (str == f->start || isidcontinue(f, str) != isidcontinue(f, prev_char(f, str))) ? new_match(defs, pat, str, str, NULL) : NULL;
}
case BP_STRING: {
if (&str[pat->min_matchlen] > f->end) return NULL;
if (pat->min_matchlen > 0 && (ignorecase ? memicmp : memcmp)(str, pat->args.string, pat->min_matchlen) != 0)
return NULL;
return new_match(defs, pat, str, str + pat->min_matchlen, NULL);
}
case BP_RANGE: {
if (str >= f->end) return NULL;
if ((unsigned char)*str < pat->args.range.low || (unsigned char)*str > pat->args.range.high)
return NULL;
return new_match(defs, pat, str, str+1, NULL);
}
case BP_NOT: {
match_t *m = match(defs, f, str, pat->args.pat, ignorecase);
if (m != NULL) {
recycle_if_unused(&m);
return NULL;
}
case BP_REPEAT: {
match_t *m = new_match(defs, pat, str, str, NULL);
size_t reps = 0;
ssize_t max = pat->args.repetitions.max;
pat_t *repeating = deref(defs, pat->args.repetitions.repeat_pat);
pat_t *sep = deref(defs, pat->args.repetitions.sep);
size_t child_cap = 0, nchildren = 0;
for (reps = 0; max == -1 || reps < (size_t)max; ++reps) {
const char *start = str;
// Separator
match_t *msep = NULL;
if (sep != NULL && reps > 0) {
msep = match(defs, f, str, sep, ignorecase);
if (msep == NULL) break;
str = msep->end;
return new_match(defs, pat, str, str, NULL);
}
case BP_UPTO: case BP_UPTO_STRICT: {
match_t *m = new_match(defs, pat, str, str, NULL);
pat_t *target = deref(defs, pat->args.multiple.first),
*skip = deref(defs, pat->args.multiple.second);
if (!target && !skip) {
while (str < f->end && *str != '\n') ++str;
m->end = str;
return m;
}
size_t child_cap = 0, nchildren = 0;
for (const char *prev = NULL; prev < str; ) {
prev = str;
if (target) {
match_t *p = match(defs, f, str, target, ignorecase);
if (p != NULL) {
recycle_if_unused(&p);
m->end = str;
return m;
}
match_t *mp = match(defs, f, str, repeating, ignorecase);
if (mp == NULL) {
str = start;
if (msep) recycle_if_unused(&msep);
break;
}
if (mp->end == start && reps > 0) {
// Since no forward progress was made on either `repeating`
// or `sep` and BP does not have mutable state, it's
// guaranteed that no progress will be made on the next
// loop either. We know that this will continue to loop
// until reps==max, so let's just cut to the chase instead
// of looping infinitely.
if (msep) recycle_if_unused(&msep);
recycle_if_unused(&mp);
if (pat->args.repetitions.max == -1)
reps = ~(size_t)0;
else
reps = (size_t)pat->args.repetitions.max;
break;
}
if (msep) {
} else if (str == f->end) {
m->end = str;
return m;
}
if (skip) {
match_t *s = match(defs, f, str, skip, ignorecase);
if (s != NULL) {
str = s->end;
if (nchildren+2 >= child_cap) {
m->children = grow(m->children, child_cap += 5);
for (size_t i = nchildren; i < child_cap; i++) m->children[i] = NULL;
}
add_owner(&m->children[nchildren++], msep);
add_owner(&m->children[nchildren++], s);
continue;
}
}
// This isn't in the for() structure because there needs to
// be at least once chance to match the pattern, even if
// we're at the end of the string already (e.g. "..$").
if (str < f->end && *str != '\n' && pat->type != BP_UPTO_STRICT)
str = next_char(f, str);
}
recycle_if_unused(&m);
return NULL;
}
case BP_REPEAT: {
match_t *m = new_match(defs, pat, str, str, NULL);
size_t reps = 0;
ssize_t max = pat->args.repetitions.max;
pat_t *repeating = deref(defs, pat->args.repetitions.repeat_pat);
pat_t *sep = deref(defs, pat->args.repetitions.sep);
size_t child_cap = 0, nchildren = 0;
for (reps = 0; max == -1 || reps < (size_t)max; ++reps) {
const char *start = str;
// Separator
match_t *msep = NULL;
if (sep != NULL && reps > 0) {
msep = match(defs, f, str, sep, ignorecase);
if (msep == NULL) break;
str = msep->end;
}
match_t *mp = match(defs, f, str, repeating, ignorecase);
if (mp == NULL) {
str = start;
if (msep) recycle_if_unused(&msep);
break;
}
if (mp->end == start && reps > 0) {
// Since no forward progress was made on either `repeating`
// or `sep` and BP does not have mutable state, it's
// guaranteed that no progress will be made on the next
// loop either. We know that this will continue to loop
// until reps==max, so let's just cut to the chase instead
// of looping infinitely.
if (msep) recycle_if_unused(&msep);
recycle_if_unused(&mp);
if (pat->args.repetitions.max == -1)
reps = ~(size_t)0;
else
reps = (size_t)pat->args.repetitions.max;
break;
}
if (msep) {
if (nchildren+2 >= child_cap) {
m->children = grow(m->children, child_cap += 5);
for (size_t i = nchildren; i < child_cap; i++) m->children[i] = NULL;
}
add_owner(&m->children[nchildren++], mp);
str = mp->end;
add_owner(&m->children[nchildren++], msep);
}
if (reps < (size_t)pat->args.repetitions.min) {
recycle_if_unused(&m);
return NULL;
if (nchildren+2 >= child_cap) {
m->children = grow(m->children, child_cap += 5);
for (size_t i = nchildren; i < child_cap; i++) m->children[i] = NULL;
}
m->end = str;
return m;
add_owner(&m->children[nchildren++], mp);
str = mp->end;
}
case BP_AFTER: {
pat_t *back = deref(defs, pat->args.pat);
if (!back) return NULL;
// We only care about the region from the backtrack pos up to the
// current pos, so mock it out as a file slice.
// TODO: this breaks ^/^^/$/$$, but that can probably be ignored
// because you rarely need to check those in a backtrack.
file_t slice;
slice_file(&slice, f, f->start, str);
for (const char *pos = &str[-(long)back->min_matchlen];
pos >= f->start && (back->max_matchlen == -1 || pos >= &str[-(int)back->max_matchlen]);
pos = prev_char(f, pos)) {
cache_destroy(&slice);
slice.start = (char*)pos;
match_t *m = match(defs, &slice, pos, back, ignorecase);
// Match should not go past str (i.e. (<"AB" "B") should match "ABB", but not "AB")
if (m && m->end != str)
recycle_if_unused(&m);
else if (m) {
cache_destroy(&slice);
return new_match(defs, pat, str, str, MATCHES(m));
}
if (pos == f->start) break;
// To prevent extreme performance degradation, don't keep
// walking backwards endlessly over newlines.
if (back->max_matchlen == -1 && *pos == '\n') break;
}
cache_destroy(&slice);
if (reps < (size_t)pat->args.repetitions.min) {
recycle_if_unused(&m);
return NULL;
}
case BP_BEFORE: {
match_t *after = match(defs, f, str, pat->args.pat, ignorecase);
return after ? new_match(defs, pat, str, str, MATCHES(after)) : NULL;
}
case BP_CAPTURE: {
match_t *p = match(defs, f, str, pat->args.pat, ignorecase);
return p ? new_match(defs, pat, str, p->end, MATCHES(p)) : NULL;
}
case BP_OTHERWISE: {
match_t *m = match(defs, f, str, pat->args.multiple.first, ignorecase);
return m ? m : match(defs, f, str, pat->args.multiple.second, ignorecase);
}
case BP_CHAIN: {
match_t *m1 = match(defs, f, str, pat->args.multiple.first, ignorecase);
if (m1 == NULL) return NULL;
m->end = str;
return m;
}
case BP_AFTER: {
pat_t *back = deref(defs, pat->args.pat);
if (!back) return NULL;
match_t *m2;
// Push backrefs and run matching, then cleanup
if (m1->pat->type == BP_CAPTURE && m1->pat->args.capture.name) {
// Temporarily add a rule that the backref name matches the
// exact string of the original match (no replacements)
size_t len = (size_t)(m1->end - m1->start);
pat_t *backref = new_pat(f, m1->start, m1->end, len, (ssize_t)len, BP_STRING);
backref->args.string = m1->start;
// We only care about the region from the backtrack pos up to the
// current pos, so mock it out as a file slice.
// TODO: this breaks ^/^^/$/$$, but that can probably be ignored
// because you rarely need to check those in a backtrack.
file_t slice;
slice_file(&slice, f, f->start, str);
for (const char *pos = &str[-(long)back->min_matchlen];
pos >= f->start && (back->max_matchlen == -1 || pos >= &str[-(int)back->max_matchlen]);
pos = prev_char(f, pos)) {
cache_destroy(&slice);
slice.start = (char*)pos;
match_t *m = match(defs, &slice, pos, back, ignorecase);
// Match should not go past str (i.e. (<"AB" "B") should match "ABB", but not "AB")
if (m && m->end != str)
recycle_if_unused(&m);
else if (m) {
cache_destroy(&slice);
return new_match(defs, pat, str, str, MATCHES(m));
}
if (pos == f->start) break;
// To prevent extreme performance degradation, don't keep
// walking backwards endlessly over newlines.
if (back->max_matchlen == -1 && *pos == '\n') break;
}
cache_destroy(&slice);
return NULL;
}
case BP_BEFORE: {
match_t *after = match(defs, f, str, pat->args.pat, ignorecase);
return after ? new_match(defs, pat, str, str, MATCHES(after)) : NULL;
}
case BP_CAPTURE: {
match_t *p = match(defs, f, str, pat->args.pat, ignorecase);
return p ? new_match(defs, pat, str, p->end, MATCHES(p)) : NULL;
}
case BP_OTHERWISE: {
match_t *m = match(defs, f, str, pat->args.multiple.first, ignorecase);
return m ? m : match(defs, f, str, pat->args.multiple.second, ignorecase);
}
case BP_CHAIN: {
match_t *m1 = match(defs, f, str, pat->args.multiple.first, ignorecase);
if (m1 == NULL) return NULL;
def_t *defs2 = with_def(defs, m1->pat->args.capture.namelen, m1->pat->args.capture.name, backref);
++m1->refcount; {
m2 = match(defs2, f, m1->end, pat->args.multiple.second, ignorecase);
if (!m2) { // No need to keep the backref in memory if it didn't match
for (pat_t **rem = &f->pats; *rem; rem = &(*rem)->next) {
if ((*rem) == backref) {
pat_t *tmp = *rem;
*rem = (*rem)->next;
free(tmp);
break;
}
match_t *m2;
// Push backrefs and run matching, then cleanup
if (m1->pat->type == BP_CAPTURE && m1->pat->args.capture.name) {
// Temporarily add a rule that the backref name matches the
// exact string of the original match (no replacements)
size_t len = (size_t)(m1->end - m1->start);
pat_t *backref = new_pat(f, m1->start, m1->end, len, (ssize_t)len, BP_STRING);
backref->args.string = m1->start;
def_t *defs2 = with_def(defs, m1->pat->args.capture.namelen, m1->pat->args.capture.name, backref);
++m1->refcount; {
m2 = match(defs2, f, m1->end, pat->args.multiple.second, ignorecase);
if (!m2) { // No need to keep the backref in memory if it didn't match
for (pat_t **rem = &f->pats; *rem; rem = &(*rem)->next) {
if ((*rem) == backref) {
pat_t *tmp = *rem;
*rem = (*rem)->next;
free(tmp);
break;
}
}
defs = free_defs(defs2, defs);
} --m1->refcount;
} else {
m2 = match(defs, f, m1->end, pat->args.multiple.second, ignorecase);
}
if (m2 == NULL) {
recycle_if_unused(&m1);
return NULL;
}
return new_match(defs, pat, str, m2->end, MATCHES(m1, m2));
}
case BP_MATCH: case BP_NOT_MATCH: {
match_t *m1 = match(defs, f, str, pat->args.multiple.first, ignorecase);
if (m1 == NULL) return NULL;
// <p1>~<p2> matches iff the text of <p1> matches <p2>
// <p1>!~<p2> matches iff the text of <p1> does not match <p2>
file_t slice;
slice_file(&slice, f, m1->start, m1->end);
match_t *m2 = next_match(defs, &slice, NULL, pat->args.multiple.second, NULL, ignorecase);
if ((!m2 && pat->type == BP_MATCH) || (m2 && pat->type == BP_NOT_MATCH)) {
if (m2) recycle_if_unused(&m2);
cache_destroy(&slice);
recycle_if_unused(&m1);
return NULL;
}
cache_destroy(&slice);
return new_match(defs, pat, m1->start, m1->end, (pat->type == BP_MATCH) ? MATCHES(m1, m2) : NULL);
}
case BP_REPLACE: {
match_t *p = NULL;
if (pat->args.replace.pat) {
p = match(defs, f, str, pat->args.replace.pat, ignorecase);
if (p == NULL) return NULL;
}
return new_match(defs, pat, str, p ? p->end : str, MATCHES(p));
}
case BP_REF: {
match_t *cached;
if (cache_get(f, defs, str, pat, &cached))
return cached;
def_t *def = lookup(defs, pat->args.ref.len, pat->args.ref.name);
if (def == NULL)
errx(EXIT_FAILURE, "Unknown identifier: '%.*s'", (int)pat->args.ref.len, pat->args.ref.name);
pat_t *ref = def->pat;
pat_t rec_op = {
.type = BP_LEFTRECURSION,
.start = ref->start,
.end = ref->end,
.min_matchlen = 0,
.max_matchlen = -1,
.args.leftrec = {
.match = NULL,
.visits = 0,
.at = str,
.fallback = ref,
},
};
def_t defs2 = {
.namelen = def->namelen,
.name = def->name,
.pat = &rec_op,
.next = defs,
};
const char *prev = str;
match_t *m = match(&defs2, f, str, ref, ignorecase);
if (m == NULL) {
cache_save(f, defs, str, pat, NULL);
return NULL;
}
while (rec_op.args.leftrec.visits > 0) {
rec_op.args.leftrec.visits = 0;
remove_ownership(&rec_op.args.leftrec.match);
add_owner(&rec_op.args.leftrec.match, m);
prev = m->end;
match_t *m2 = match(&defs2, f, str, ref, ignorecase);
if (m2 == NULL) break;
if (m2->end <= prev) {
recycle_if_unused(&m2);
break;
}
m = m2;
}
// This match wrapper mainly exists for record-keeping purposes.
// However, it also keeps `m` from getting garbage collected with
// leftrec.match is GC'd. It also helps with visualization of match
// results.
// OPTIMIZE: remove this if necessary
match_t *wrap = new_match(defs, pat, m->start, m->end, MATCHES(m));
cache_save(f, defs, str, pat, wrap);
if (rec_op.args.leftrec.match)
remove_ownership(&rec_op.args.leftrec.match);
return wrap;
defs = free_defs(defs2, defs);
} --m1->refcount;
} else {
m2 = match(defs, f, m1->end, pat->args.multiple.second, ignorecase);
}
case BP_NODENT: {
if (*str != '\n') return NULL;
const char *start = str;
size_t linenum = get_line_number(f, str);
const char *p = get_line(f, linenum);
if (p < f->start) p = f->start; // Can happen with recursive matching
// Current indentation:
char denter = *p;
int dents = 0;
if (denter == ' ' || denter == '\t') {
for (; *p == denter && p < f->end; ++p) ++dents;
}
// Subsequent indentation:
while (*str == '\n' || *str == '\n') ++str;
for (int i = 0; i < dents; i++)
if (&str[i] >= f->end || str[i] != denter) return NULL;
return new_match(defs, pat, start, &str[dents], NULL);
}
case BP_ERROR: {
match_t *p = pat->args.pat ? match(defs, f, str, pat->args.pat, ignorecase) : NULL;
return p ? new_match(defs, pat, str, p->end, MATCHES(p)) : NULL;
}
default: {
errx(EXIT_FAILURE, "Unknown pattern type: %u", pat->type);
if (m2 == NULL) {
recycle_if_unused(&m1);
return NULL;
}
return new_match(defs, pat, str, m2->end, MATCHES(m1, m2));
}
case BP_MATCH: case BP_NOT_MATCH: {
match_t *m1 = match(defs, f, str, pat->args.multiple.first, ignorecase);
if (m1 == NULL) return NULL;
// <p1>~<p2> matches iff the text of <p1> matches <p2>
// <p1>!~<p2> matches iff the text of <p1> does not match <p2>
file_t slice;
slice_file(&slice, f, m1->start, m1->end);
match_t *m2 = next_match(defs, &slice, NULL, pat->args.multiple.second, NULL, ignorecase);
if ((!m2 && pat->type == BP_MATCH) || (m2 && pat->type == BP_NOT_MATCH)) {
if (m2) recycle_if_unused(&m2);
cache_destroy(&slice);
recycle_if_unused(&m1);
return NULL;
}
cache_destroy(&slice);
return new_match(defs, pat, m1->start, m1->end, (pat->type == BP_MATCH) ? MATCHES(m1, m2) : NULL);
}
case BP_REPLACE: {
match_t *p = NULL;
if (pat->args.replace.pat) {
p = match(defs, f, str, pat->args.replace.pat, ignorecase);
if (p == NULL) return NULL;
}
return new_match(defs, pat, str, p ? p->end : str, MATCHES(p));
}
case BP_REF: {
match_t *cached;
if (cache_get(f, defs, str, pat, &cached))
return cached;
def_t *def = lookup(defs, pat->args.ref.len, pat->args.ref.name);
if (def == NULL)
errx(EXIT_FAILURE, "Unknown identifier: '%.*s'", (int)pat->args.ref.len, pat->args.ref.name);
pat_t *ref = def->pat;
pat_t rec_op = {
.type = BP_LEFTRECURSION,
.start = ref->start,
.end = ref->end,
.min_matchlen = 0,
.max_matchlen = -1,
.args.leftrec = {
.match = NULL,
.visits = 0,
.at = str,
.fallback = ref,
},
};
def_t defs2 = {
.namelen = def->namelen,
.name = def->name,
.pat = &rec_op,
.next = defs,
};
const char *prev = str;
match_t *m = match(&defs2, f, str, ref, ignorecase);
if (m == NULL) {
cache_save(f, defs, str, pat, NULL);
return NULL;
}
while (rec_op.args.leftrec.visits > 0) {
rec_op.args.leftrec.visits = 0;
remove_ownership(&rec_op.args.leftrec.match);
add_owner(&rec_op.args.leftrec.match, m);
prev = m->end;
match_t *m2 = match(&defs2, f, str, ref, ignorecase);
if (m2 == NULL) break;
if (m2->end <= prev) {
recycle_if_unused(&m2);
break;
}
m = m2;
}
// This match wrapper mainly exists for record-keeping purposes.
// However, it also keeps `m` from getting garbage collected with
// leftrec.match is GC'd. It also helps with visualization of match
// results.
// OPTIMIZE: remove this if necessary
match_t *wrap = new_match(defs, pat, m->start, m->end, MATCHES(m));
cache_save(f, defs, str, pat, wrap);
if (rec_op.args.leftrec.match)
remove_ownership(&rec_op.args.leftrec.match);
return wrap;
}
case BP_NODENT: {
if (*str != '\n') return NULL;
const char *start = str;
size_t linenum = get_line_number(f, str);
const char *p = get_line(f, linenum);
if (p < f->start) p = f->start; // Can happen with recursive matching
// Current indentation:
char denter = *p;
int dents = 0;
if (denter == ' ' || denter == '\t') {
for (; *p == denter && p < f->end; ++p) ++dents;
}
// Subsequent indentation:
while (*str == '\n' || *str == '\n') ++str;
for (int i = 0; i < dents; i++)
if (&str[i] >= f->end || str[i] != denter) return NULL;
return new_match(defs, pat, start, &str[dents], NULL);
}
case BP_ERROR: {
match_t *p = pat->args.pat ? match(defs, f, str, pat->args.pat, ignorecase) : NULL;
return p ? new_match(defs, pat, str, p->end, MATCHES(p)) : NULL;
}
default: {
errx(EXIT_FAILURE, "Unknown pattern type: %u", pat->type);
return NULL;
}
}
}
@ -703,4 +703,4 @@ size_t free_all_matches(void)
return count;
}
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0

View File

@ -22,4 +22,4 @@ size_t free_all_matches(void);
size_t recycle_all_matches(void);
#endif
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0

544
pattern.c
View File

@ -234,291 +234,291 @@ static pat_t *_bp_simplepattern(file_t *f, const char *str)
char c = *str;
str = next_char(f, str);
switch (c) {
// Any char (dot)
case '.': {
if (*str == '.') { // ".."
pat_t *skip = NULL;
str = next_char(f, str);
char skipper = *str;
if (matchchar(&str, '%', false) || matchchar(&str, '=', false)) {
skip = bp_simplepattern(f, str);
if (!skip)
file_err(f, str, str, "There should be a pattern to skip here after the '%c'", skipper);
str = skip->end;
}
pat_t *upto = new_pat(f, start, str, 0, -1, skipper == '=' ? BP_UPTO_STRICT : BP_UPTO);
upto->args.multiple.second = skip;
return upto;
} else {
return new_pat(f, start, str, 1, UTF8_MAXCHARLEN, BP_ANYCHAR);
}
}
// Char literals
case '`': {
pat_t *all = NULL;
do { // Comma-separated items:
if (str >= f->end || !*str || *str == '\n')
file_err(f, str, str, "There should be a character here after the '`'");
const char *c1_loc = str;
str = next_char(f, c1_loc);
if (*str == '-') { // Range
const char *c2_loc = ++str;
if (next_char(f, c1_loc) > c1_loc+1 || next_char(f, c2_loc) > c2_loc+1)
file_err(f, start, next_char(f, c2_loc), "Sorry, UTF-8 character ranges are not yet supported.");
char c1 = *c1_loc, c2 = *c2_loc;
if (!c2 || c2 == '\n')
file_err(f, str, str, "There should be a character here to complete the character range.");
if (c1 > c2) { // Swap order
char tmp = c1;
c1 = c2;
c2 = tmp;
}
str = next_char(f, c2_loc);
pat_t *pat = new_pat(f, start == c1_loc - 1 ? start : c1_loc, str, 1, 1, BP_RANGE);
pat->args.range.low = (unsigned char)c1;
pat->args.range.high = (unsigned char)c2;
all = either_pat(f, all, pat);
} else {
size_t len = (size_t)(str - c1_loc);
pat_t *pat = new_pat(f, start, str, len, (ssize_t)len, BP_STRING);
pat->args.string = c1_loc;
all = either_pat(f, all, pat);
}
} while (*str++ == ',');
return all;
}
// Escapes
case '\\': {
if (!*str || *str == '\n')
file_err(f, str, str, "There should be an escape sequence here after this backslash.");
pat_t *all = NULL;
do { // Comma-separated items:
const char *itemstart = str-1;
if (*str == 'N') { // \N (nodent)
all = either_pat(f, all, new_pat(f, itemstart, ++str, 1, -1, BP_NODENT));
continue;
} else if (*str == 'i') { // \i (identifier char)
all = either_pat(f, all, new_pat(f, itemstart, ++str, 1, -1, BP_ID_CONTINUE));
continue;
} else if (*str == 'I') { // \I (identifier char, not including numbers)
all = either_pat(f, all, new_pat(f, itemstart, ++str, 1, -1, BP_ID_START));
continue;
} else if (*str == 'b') { // \b word boundary
all = either_pat(f, all, new_pat(f, itemstart, ++str, 0, 0, BP_WORD_BOUNDARY));
continue;
}
const char *opstart = str;
unsigned char e_low = (unsigned char)unescapechar(str, &str);
if (str == opstart)
file_err(f, start, str+1, "This isn't a valid escape sequence.");
unsigned char e_high = e_low;
if (*str == '-') { // Escape range (e.g. \x00-\xFF)
++str;
if (next_char(f, str) != str+1)
file_err(f, start, next_char(f, str), "Sorry, UTF8 escape sequences are not supported in ranges.");
const char *seqstart = str;
e_high = (unsigned char)unescapechar(str, &str);
if (str == seqstart)
file_err(f, seqstart, str+1, "This value isn't a valid escape sequence");
if (e_high < e_low)
file_err(f, start, str, "Escape ranges should be low-to-high, but this is high-to-low.");
}
pat_t *esc = new_pat(f, start, str, 1, 1, BP_RANGE);
esc->args.range.low = e_low;
esc->args.range.high = e_high;
all = either_pat(f, all, esc);
} while (*str++ == ',');
return all;
}
// Word boundary
case '|': {
return new_pat(f, start, str, 0, 0, BP_WORD_BOUNDARY);
}
// String literal
case '"': case '\'': case '\002': case '{': {
char endquote = c == '\002' ? '\003' : (c == '{' ? '}' : c);
char *litstart = (char*)str;
while (str < f->end && *str != endquote)
str = next_char(f, str);
size_t len = (size_t)(str - litstart);
// Any char (dot)
case '.': {
if (*str == '.') { // ".."
pat_t *skip = NULL;
str = next_char(f, str);
char skipper = *str;
if (matchchar(&str, '%', false) || matchchar(&str, '=', false)) {
skip = bp_simplepattern(f, str);
if (!skip)
file_err(f, str, str, "There should be a pattern to skip here after the '%c'", skipper);
str = skip->end;
}
pat_t *upto = new_pat(f, start, str, 0, -1, skipper == '=' ? BP_UPTO_STRICT : BP_UPTO);
upto->args.multiple.second = skip;
return upto;
} else {
return new_pat(f, start, str, 1, UTF8_MAXCHARLEN, BP_ANYCHAR);
}
}
// Char literals
case '`': {
pat_t *all = NULL;
do { // Comma-separated items:
if (str >= f->end || !*str || *str == '\n')
file_err(f, str, str, "There should be a character here after the '`'");
pat_t *pat = new_pat(f, start, str, len, (ssize_t)len, BP_STRING);
pat->args.string = litstart;
return pat;
}
// Not <pat>
case '!': {
pat_t *p = bp_simplepattern(f, str);
if (!p) file_err(f, str, str, "There should be a pattern after this '!'");
pat_t *not = new_pat(f, start, p->end, 0, 0, BP_NOT);
not->args.pat = p;
return not;
}
// Number of repetitions: <N>(-<N> / - / + / "")
case '0': case '1': case '2': case '3': case '4': case '5':
case '6': case '7': case '8': case '9': {
size_t min = 0;
ssize_t max = -1;
--str;
long n1 = strtol(str, (char**)&str, 10);
if (matchchar(&str, '-', false)) {
str = after_spaces(str, false);
const char *numstart = str;
long n2 = strtol(str, (char**)&str, 10);
if (str == numstart) min = 0, max = (ssize_t)n1;
else min = (size_t)n1, max = (ssize_t)n2;
} else if (matchchar(&str, '+', false)) {
min = (size_t)n1, max = -1;
const char *c1_loc = str;
str = next_char(f, c1_loc);
if (*str == '-') { // Range
const char *c2_loc = ++str;
if (next_char(f, c1_loc) > c1_loc+1 || next_char(f, c2_loc) > c2_loc+1)
file_err(f, start, next_char(f, c2_loc), "Sorry, UTF-8 character ranges are not yet supported.");
char c1 = *c1_loc, c2 = *c2_loc;
if (!c2 || c2 == '\n')
file_err(f, str, str, "There should be a character here to complete the character range.");
if (c1 > c2) { // Swap order
char tmp = c1;
c1 = c2;
c2 = tmp;
}
str = next_char(f, c2_loc);
pat_t *pat = new_pat(f, start == c1_loc - 1 ? start : c1_loc, str, 1, 1, BP_RANGE);
pat->args.range.low = (unsigned char)c1;
pat->args.range.high = (unsigned char)c2;
all = either_pat(f, all, pat);
} else {
min = (size_t)n1, max = (ssize_t)n1;
size_t len = (size_t)(str - c1_loc);
pat_t *pat = new_pat(f, start, str, len, (ssize_t)len, BP_STRING);
pat->args.string = c1_loc;
all = either_pat(f, all, pat);
}
pat_t *repeating = bp_simplepattern(f, str);
if (!repeating)
file_err(f, str, str, "There should be a pattern after this repetition count.");
str = repeating->end;
pat_t *sep = NULL;
if (matchchar(&str, '%', false)) {
sep = bp_simplepattern(f, str);
if (!sep)
file_err(f, str, str, "There should be a separator pattern after this '%%'");
str = sep->end;
} else {
str = repeating->end;
}
return new_range(f, start, str, min, max, repeating, sep);
}
// Lookbehind
case '<': {
pat_t *behind = bp_simplepattern(f, str);
if (!behind)
file_err(f, str, str, "There should be a pattern after this '<'");
str = behind->end;
str = behind->end;
pat_t *pat = new_pat(f, start, str, 0, 0, BP_AFTER);
pat->args.pat = behind;
return pat;
}
// Lookahead
case '>': {
pat_t *ahead = bp_simplepattern(f, str);
if (!ahead)
file_err(f, str, str, "There should be a pattern after this '>'");
str = ahead->end;
pat_t *pat = new_pat(f, start, str, 0, 0, BP_BEFORE);
pat->args.pat = ahead;
return pat;
}
// Parentheses
case '(': {
if (start + 2 < f->end && strncmp(start, "(!)", 3) == 0) { // (!) errors
str = start + 3;
pat_t *pat = bp_simplepattern(f, str);
if (!pat) pat = new_pat(f, str, str, 0, 0, BP_STRING);
pat = expand_replacements(f, pat, false);
pat_t *error = new_pat(f, start, pat->end, pat->min_matchlen, pat->max_matchlen, BP_ERROR);
error->args.pat = pat;
return error;
} while (*str++ == ',');
return all;
}
// Escapes
case '\\': {
if (!*str || *str == '\n')
file_err(f, str, str, "There should be an escape sequence here after this backslash.");
pat_t *all = NULL;
do { // Comma-separated items:
const char *itemstart = str-1;
if (*str == 'N') { // \N (nodent)
all = either_pat(f, all, new_pat(f, itemstart, ++str, 1, -1, BP_NODENT));
continue;
} else if (*str == 'i') { // \i (identifier char)
all = either_pat(f, all, new_pat(f, itemstart, ++str, 1, -1, BP_ID_CONTINUE));
continue;
} else if (*str == 'I') { // \I (identifier char, not including numbers)
all = either_pat(f, all, new_pat(f, itemstart, ++str, 1, -1, BP_ID_START));
continue;
} else if (*str == 'b') { // \b word boundary
all = either_pat(f, all, new_pat(f, itemstart, ++str, 0, 0, BP_WORD_BOUNDARY));
continue;
}
pat_t *pat = bp_pattern_nl(f, str, true);
if (!pat)
file_err(f, str, str, "There should be a valid pattern after this parenthesis.");
str = pat->end;
if (!matchchar(&str, ')', true)) file_err(f, str, str, "Missing paren: )");
pat->start = start;
pat->end = str;
return pat;
const char *opstart = str;
unsigned char e_low = (unsigned char)unescapechar(str, &str);
if (str == opstart)
file_err(f, start, str+1, "This isn't a valid escape sequence.");
unsigned char e_high = e_low;
if (*str == '-') { // Escape range (e.g. \x00-\xFF)
++str;
if (next_char(f, str) != str+1)
file_err(f, start, next_char(f, str), "Sorry, UTF8 escape sequences are not supported in ranges.");
const char *seqstart = str;
e_high = (unsigned char)unescapechar(str, &str);
if (str == seqstart)
file_err(f, seqstart, str+1, "This value isn't a valid escape sequence");
if (e_high < e_low)
file_err(f, start, str, "Escape ranges should be low-to-high, but this is high-to-low.");
}
pat_t *esc = new_pat(f, start, str, 1, 1, BP_RANGE);
esc->args.range.low = e_low;
esc->args.range.high = e_high;
all = either_pat(f, all, esc);
} while (*str++ == ',');
return all;
}
// Word boundary
case '|': {
return new_pat(f, start, str, 0, 0, BP_WORD_BOUNDARY);
}
// String literal
case '"': case '\'': case '\002': case '{': {
char endquote = c == '\002' ? '\003' : (c == '{' ? '}' : c);
char *litstart = (char*)str;
while (str < f->end && *str != endquote)
str = next_char(f, str);
size_t len = (size_t)(str - litstart);
str = next_char(f, str);
pat_t *pat = new_pat(f, start, str, len, (ssize_t)len, BP_STRING);
pat->args.string = litstart;
return pat;
}
// Not <pat>
case '!': {
pat_t *p = bp_simplepattern(f, str);
if (!p) file_err(f, str, str, "There should be a pattern after this '!'");
pat_t *not = new_pat(f, start, p->end, 0, 0, BP_NOT);
not->args.pat = p;
return not;
}
// Number of repetitions: <N>(-<N> / - / + / "")
case '0': case '1': case '2': case '3': case '4': case '5':
case '6': case '7': case '8': case '9': {
size_t min = 0;
ssize_t max = -1;
--str;
long n1 = strtol(str, (char**)&str, 10);
if (matchchar(&str, '-', false)) {
str = after_spaces(str, false);
const char *numstart = str;
long n2 = strtol(str, (char**)&str, 10);
if (str == numstart) min = 0, max = (ssize_t)n1;
else min = (size_t)n1, max = (ssize_t)n2;
} else if (matchchar(&str, '+', false)) {
min = (size_t)n1, max = -1;
} else {
min = (size_t)n1, max = (ssize_t)n1;
}
// Square brackets
case '[': {
pat_t *maybe = bp_pattern_nl(f, str, true);
if (!maybe)
file_err(f, str, str, "There should be a valid pattern after this square bracket.");
str = maybe->end;
(void)matchchar(&str, ']', true);
return new_range(f, start, str, 0, 1, maybe, NULL);
}
// Repeating
case '*': case '+': {
size_t min = (size_t)(c == '*' ? 0 : 1);
pat_t *repeating = bp_simplepattern(f, str);
if (!repeating)
file_err(f, str, str, "There should be a valid pattern here after the '%c'", c);
pat_t *repeating = bp_simplepattern(f, str);
if (!repeating)
file_err(f, str, str, "There should be a pattern after this repetition count.");
str = repeating->end;
pat_t *sep = NULL;
if (matchchar(&str, '%', false)) {
sep = bp_simplepattern(f, str);
if (!sep)
file_err(f, str, str, "There should be a separator pattern after this '%%'");
str = sep->end;
} else {
str = repeating->end;
pat_t *sep = NULL;
if (matchchar(&str, '%', false)) {
sep = bp_simplepattern(f, str);
if (!sep)
file_err(f, str, str, "There should be a separator pattern after the '%%' here.");
str = sep->end;
}
return new_range(f, start, str, min, -1, repeating, sep);
}
// Capture
case '@': {
const char *name = NULL;
size_t namelen = 0;
const char *a = after_name(str);
const char *eq = a;
if (a > str && !matchstr(&eq, "=>", false) && matchchar(&eq, '=', false)) {
name = str;
namelen = (size_t)(a-str);
str = eq;
}
return new_range(f, start, str, min, max, repeating, sep);
}
// Lookbehind
case '<': {
pat_t *behind = bp_simplepattern(f, str);
if (!behind)
file_err(f, str, str, "There should be a pattern after this '<'");
str = behind->end;
str = behind->end;
pat_t *pat = new_pat(f, start, str, 0, 0, BP_AFTER);
pat->args.pat = behind;
return pat;
}
// Lookahead
case '>': {
pat_t *ahead = bp_simplepattern(f, str);
if (!ahead)
file_err(f, str, str, "There should be a pattern after this '>'");
str = ahead->end;
pat_t *pat = new_pat(f, start, str, 0, 0, BP_BEFORE);
pat->args.pat = ahead;
return pat;
}
// Parentheses
case '(': {
if (start + 2 < f->end && strncmp(start, "(!)", 3) == 0) { // (!) errors
str = start + 3;
pat_t *pat = bp_simplepattern(f, str);
if (!pat)
file_err(f, str, str, "There should be a valid pattern here to capture after the '@'");
if (!pat) pat = new_pat(f, str, str, 0, 0, BP_STRING);
pat = expand_replacements(f, pat, false);
pat_t *error = new_pat(f, start, pat->end, pat->min_matchlen, pat->max_matchlen, BP_ERROR);
error->args.pat = pat;
return error;
}
pat_t *capture = new_pat(f, start, pat->end, pat->min_matchlen, pat->max_matchlen, BP_CAPTURE);
capture->args.capture.capture_pat = pat;
capture->args.capture.name = name;
capture->args.capture.namelen = namelen;
return capture;
pat_t *pat = bp_pattern_nl(f, str, true);
if (!pat)
file_err(f, str, str, "There should be a valid pattern after this parenthesis.");
str = pat->end;
if (!matchchar(&str, ')', true)) file_err(f, str, str, "Missing paren: )");
pat->start = start;
pat->end = str;
return pat;
}
// Square brackets
case '[': {
pat_t *maybe = bp_pattern_nl(f, str, true);
if (!maybe)
file_err(f, str, str, "There should be a valid pattern after this square bracket.");
str = maybe->end;
(void)matchchar(&str, ']', true);
return new_range(f, start, str, 0, 1, maybe, NULL);
}
// Repeating
case '*': case '+': {
size_t min = (size_t)(c == '*' ? 0 : 1);
pat_t *repeating = bp_simplepattern(f, str);
if (!repeating)
file_err(f, str, str, "There should be a valid pattern here after the '%c'", c);
str = repeating->end;
pat_t *sep = NULL;
if (matchchar(&str, '%', false)) {
sep = bp_simplepattern(f, str);
if (!sep)
file_err(f, str, str, "There should be a separator pattern after the '%%' here.");
str = sep->end;
}
// Start of file/line
case '^': {
if (*str == '^')
return new_pat(f, start, ++str, 0, 0, BP_START_OF_FILE);
return new_pat(f, start, str, 0, 0, BP_START_OF_LINE);
return new_range(f, start, str, min, -1, repeating, sep);
}
// Capture
case '@': {
const char *name = NULL;
size_t namelen = 0;
const char *a = after_name(str);
const char *eq = a;
if (a > str && !matchstr(&eq, "=>", false) && matchchar(&eq, '=', false)) {
name = str;
namelen = (size_t)(a-str);
str = eq;
}
// End of file/line:
case '$': {
if (*str == '$')
return new_pat(f, start, ++str, 0, 0, BP_END_OF_FILE);
return new_pat(f, start, str, 0, 0, BP_END_OF_LINE);
}
default: {
// Reference
if (!isalpha(c) && c != '_') return NULL;
str = after_name(start);
size_t namelen = (size_t)(str - start);
if (matchchar(&str, ':', false)) { // Definitions
pat_t *def = bp_pattern_nl(f, str, false);
if (!def) file_err(f, str, f->end, "Could not parse this definition.");
str = def->end;
(void)matchchar(&str, ';', false); // Optional semicolon
str = after_spaces(str, true);
pat_t *pat = bp_pattern_nl(f, str, false);
if (pat) str = pat->end;
else pat = def;
pat_t *ret = new_pat(f, start, str, pat->min_matchlen, pat->max_matchlen, BP_DEFINITION);
ret->args.def.name = start;
ret->args.def.namelen = namelen;
ret->args.def.def = def;
ret->args.def.pat = pat;
return ret;
}
pat_t *ref = new_pat(f, start, str, 0, -1, BP_REF);
ref->args.ref.name = start;
ref->args.ref.len = namelen;
return ref;
pat_t *pat = bp_simplepattern(f, str);
if (!pat)
file_err(f, str, str, "There should be a valid pattern here to capture after the '@'");
pat_t *capture = new_pat(f, start, pat->end, pat->min_matchlen, pat->max_matchlen, BP_CAPTURE);
capture->args.capture.capture_pat = pat;
capture->args.capture.name = name;
capture->args.capture.namelen = namelen;
return capture;
}
// Start of file/line
case '^': {
if (*str == '^')
return new_pat(f, start, ++str, 0, 0, BP_START_OF_FILE);
return new_pat(f, start, str, 0, 0, BP_START_OF_LINE);
}
// End of file/line:
case '$': {
if (*str == '$')
return new_pat(f, start, ++str, 0, 0, BP_END_OF_FILE);
return new_pat(f, start, str, 0, 0, BP_END_OF_LINE);
}
default: {
// Reference
if (!isalpha(c) && c != '_') return NULL;
str = after_name(start);
size_t namelen = (size_t)(str - start);
if (matchchar(&str, ':', false)) { // Definitions
pat_t *def = bp_pattern_nl(f, str, false);
if (!def) file_err(f, str, f->end, "Could not parse this definition.");
str = def->end;
(void)matchchar(&str, ';', false); // Optional semicolon
str = after_spaces(str, true);
pat_t *pat = bp_pattern_nl(f, str, false);
if (pat) str = pat->end;
else pat = def;
pat_t *ret = new_pat(f, start, str, pat->min_matchlen, pat->max_matchlen, BP_DEFINITION);
ret->args.def.name = start;
ret->args.def.namelen = namelen;
ret->args.def.def = def;
ret->args.def.pat = pat;
return ret;
}
pat_t *ref = new_pat(f, start, str, 0, -1, BP_REF);
ref->args.ref.name = start;
ref->args.ref.len = namelen;
return ref;
}
}
}
@ -600,4 +600,4 @@ pat_t *bp_pattern(file_t *f, const char *str)
return bp_pattern_nl(f, str, false);
}
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0

View File

@ -21,4 +21,4 @@ __attribute__((nonnull))
pat_t *bp_pattern(file_t *f, const char *str);
#endif
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0

View File

@ -266,4 +266,4 @@ int print_errors(file_t *f, match_t *m)
return ret;
}
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0

View File

@ -28,4 +28,4 @@ __attribute__((nonnull))
int print_errors(file_t *f, match_t *m);
#endif
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0

View File

@ -130,4 +130,4 @@ typedef struct def_s {
} def_t;
#endif
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0

2
utf8.c
View File

@ -280,4 +280,4 @@ bool isidcontinue(file_t *f, const char *str)
|| find_in_ranges(codepoint, XID_Continue_only, ARRAY_LEN(XID_Continue_only)));
}
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0

2
utf8.h
View File

@ -18,4 +18,4 @@ __attribute__((nonnull, pure))
bool isidcontinue(file_t *f, const char *str);
#endif
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0

83
utils.c
View File

@ -20,18 +20,18 @@ const char *after_spaces(const char *str, bool skip_nl)
// Skip whitespace and comments:
skip_whitespace:
switch (*str) {
case '\r': case '\n':
if (!skip_nl) break;
__attribute__ ((fallthrough));
case ' ': case '\t': {
++str;
goto skip_whitespace;
}
case '#': {
while (*str && *str != '\n') ++str;
goto skip_whitespace;
}
default: break;
case '\r': case '\n':
if (!skip_nl) break;
__attribute__ ((fallthrough));
case ' ': case '\t': {
++str;
goto skip_whitespace;
}
case '#': {
while (*str && *str != '\n') ++str;
goto skip_whitespace;
}
default: break;
}
return str;
}
@ -90,39 +90,38 @@ char unescapechar(const char *escaped, const char **end)
size_t len = 1;
unsigned char ret = (unsigned char)*escaped;
switch (*escaped) {
case 'a': ret = '\a'; break; case 'b': ret = '\b'; break;
case 'n': ret = '\n'; break; case 'r': ret = '\r'; break;
case 't': ret = '\t'; break; case 'v': ret = '\v'; break;
case 'e': ret = '\033'; break; case '\\': ret = '\\'; break;
case 'x': { // Hex
static const unsigned char hextable[255] = {
['0']=0x10, ['1']=0x1, ['2']=0x2, ['3']=0x3, ['4']=0x4,
['5']=0x5, ['6']=0x6, ['7']=0x7, ['8']=0x8, ['9']=0x9,
['a']=0xa, ['b']=0xb, ['c']=0xc, ['d']=0xd, ['e']=0xe, ['f']=0xf,
['A']=0xa, ['B']=0xb, ['C']=0xc, ['D']=0xd, ['E']=0xe, ['F']=0xf,
};
if (hextable[(int)escaped[1]] && hextable[(int)escaped[2]]) {
ret = (hextable[(int)escaped[1]] << 4) | (hextable[(int)escaped[2]] & 0xF);
len = 3;
}
break;
case 'a': ret = '\a'; break; case 'b': ret = '\b'; break;
case 'n': ret = '\n'; break; case 'r': ret = '\r'; break;
case 't': ret = '\t'; break; case 'v': ret = '\v'; break;
case 'e': ret = '\033'; break; case '\\': ret = '\\'; break;
case 'x': { // Hex
static const unsigned char hextable[255] = {
['0']=0x10, ['1']=0x1, ['2']=0x2, ['3']=0x3, ['4']=0x4,
['5']=0x5, ['6']=0x6, ['7']=0x7, ['8']=0x8, ['9']=0x9,
['a']=0xa, ['b']=0xb, ['c']=0xc, ['d']=0xd, ['e']=0xe, ['f']=0xf,
['A']=0xa, ['B']=0xb, ['C']=0xc, ['D']=0xd, ['E']=0xe, ['F']=0xf,
};
if (hextable[(int)escaped[1]] && hextable[(int)escaped[2]]) {
ret = (hextable[(int)escaped[1]] << 4) | (hextable[(int)escaped[2]] & 0xF);
len = 3;
}
case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': { // Octal
ret = (unsigned char)(escaped[0] - '0');
if ('0' <= escaped[1] && escaped[1] <= '7') {
break;
}
case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': { // Octal
ret = (unsigned char)(escaped[0] - '0');
if ('0' <= escaped[1] && escaped[1] <= '7') {
++len;
ret = (ret << 3) | (escaped[1] - '0');
if ('0' <= escaped[2] && escaped[2] <= '7') {
++len;
ret = (ret << 3) | (escaped[1] - '0');
if ('0' <= escaped[2] && escaped[2] <= '7') {
++len;
ret = (ret << 3) | (escaped[2] - '0');
}
ret = (ret << 3) | (escaped[2] - '0');
}
break;
}
default: {
if (end) *end = escaped;
return (char)0;
}
break;
}
default:
if (end) *end = escaped;
return (char)0;
}
if (end) *end = &escaped[len];
return (char)ret;
@ -151,4 +150,4 @@ void delete(void *p)
*((void**)p) = NULL;
}
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0

View File

@ -63,4 +63,4 @@ __attribute__((nonnull))
void delete(void *p);
#endif
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0