//
// pattern.c - Compile strings into BP pattern objects that can be matched against.
//
#include
#include “pattern.h” #include “utf8.h” #include “utils.h”
#define Pattern(_tag, _start, _end, _min, max, …)
allocatepat((bppatt){.type = _tag,
.start = _start,
.end = end,
.minmatchlen = min,
.maxmatchlen = max,
.tagged.tag = {VA_ARGS}})
#define UNBOUNDED(pat) ((pat)->max_matchlen == -1)
static bppatt *allocated_pats = NULL;
attribute((nonnull)) static bppatt *bppatternnl(const char *str, const char *end, bool allow_nl); attribute((nonnull)) static bppatt *bp_simplepattern(const char *str, const char *end);
// For error-handling purposes, use setjmp/longjmp to break out of deeply // recursive function calls when a parse error occurs. bool isintrycatch = false; static jmpbuf errjmp; static maybepatt parseerror = {.success = false};
#define TRY_PATTERN
bool wasintrycatch = isintrycatch;
if (!isintrycatch) {
isintrycatch = true;
if (setjmp(errjmp)) return parseerror;
}
#define ENDTRYPATTERN
if (!wasintrycatch) isintrycatch = false;
static inline void parseerr(const char *start, const char *end, const char *msg) { if (!isintrycatch) { fprintf(stderr, “Parse error: %s\n%.*s\n”, msg, (int)(end - start), start); exit(1); } parseerror.value.error.start = start; parseerror.value.error.end = end; parseerror.value.error.msg = msg; longjmp(errjmp, 1); }
// // Allocate a new pattern for this file (ensuring it will be automatically // freed when the file is freed) // public bppatt *allocatepat(bppatt pat) { static sizet nextpatid = 1; bppatt *allocated = new (bppatt); *allocated = pat; allocated->home = &allocatedpats; allocated->next = allocatedpats; allocated->id = nextpatid++; if (allocatedpats) allocatedpats->home = &allocated->next; allocated_pats = allocated; return allocated; }
// // Helper function to initialize a range object. // attribute((nonnull(1, 2, 5))) static bppatt *newrange(const char *start, const char *end, sizet min, ssizet max, bppatt *repeating, bppatt *sep) { sizet minlen = min * repeating->minmatchlen + (min > 0 ? min - 1 : 0) * (sep ? sep->minmatchlen : 0); ssizet maxlen = (max == -1 || UNBOUNDED(repeating) || (max != 0 && max != 1 && sep && UNBOUNDED(sep))) ? (ssizet)-1 : max * repeating->maxmatchlen + (ssizet)(max > 0 ? min - 1 : 0) * (ssizet)(sep ? sep->minmatchlen : 0); return Pattern(BPREPEAT, start, end, minlen, maxlen, .min = min, .max = max, .repeatpat = repeating, .sep = sep); }
//
// Take a pattern and expand it into a chain of patterns if it’s followed by
// any patterns (e.g. “xy”), otherwise return the original input.
//
attribute((nonnull)) static bppatt *expandchain(bppatt *first, const char *end, bool allownl) {
const char *str = afterspaces(first->end, allownl, end);
bppatt *second = bpsimplepattern(str, end);
if (second == NULL) return first;
second = expandchain(second, end, allownl);
return chaintogether(first, second);
}
// // Match trailing => replacements (with optional pattern beforehand) // attribute((nonnull)) static bppatt *expandreplacements(bppatt *replacepat, const char *end, bool allownl) { const char *str = replacepat->end; while (matchstr(&str, “=>”, allownl, end)) { const char *repstr; sizet replen; if (matchchar(&str, ‘“‘, allownl, end) || matchchar(&str, ‘\’’, allownl, end) || matchchar(&str, ‘}’, allownl, end) || matchchar(&str, ‘\002’, allownl, end)) { char closequote = str[-1] == ‘}’ ? ‘{’ : (str[-1] == ‘\002’ ? ‘\003’ : str[-1]); repstr = str; for (; str < end && str != closequote; str = next_char(str, end)) { if (str == ‘\‘) { if (!str[1] || str[1] == ‘\n’) parseerr(str, str + 1, “There should be an escape sequence after this backslash.”); str = nextchar(str, end); } } replen = (size_t)(str - repstr); (void)matchchar(&str, closequote, true, end); } else { repstr = ““; replen = 0; }
replace_pat = Pattern(BP_REPLACE, replace_pat->start, str, replace_pat->min_matchlen, replace_pat->max_matchlen,
.pat = replace_pat, .text = repstr, .len = replen);
}
return replace_pat;
}
//
// Take a pattern and parse any “=>” replacements and then expand it into a
// chain of choices if it’s followed by any “/”-separated patterns (e.g.
// “x/y”), otherwise return the original input.
//
attribute((nonnull)) static bppatt *expandchoices(bppatt *first, const char *end, bool allownl) {
first = expandchain(first, end, allownl);
first = expandreplacements(first, end, allownl);
const char *str = first->end;
if (!matchchar(&str, ‘/’, allownl, end)) return first;
str = afterspaces(str, allownl, end);
bppatt *second = bpsimplepattern(str, end);
if (second) str = second->end;
if (matchstr(&str, “=>”, allownl, end))
second = expandreplacements(second ? second : Pattern(BPSTRING, str - 2, str - 2, 0, 0), end, allownl);
if (!second) parseerr(str, str, “There should be a pattern here after a ‘/’”);
second = expandchoices(second, end, allownl);
return eitherpat(first, second);
}
// // Given two patterns, return a new pattern for the first pattern followed by // the second. If either pattern is NULL, return the other. // public bppatt *chaintogether(bppatt *first, bppat_t *second) { if (first == NULL) return second; if (second == NULL) return first;
if (first->type == BP_STRING && first->max_matchlen == 0) return second;
if (second->type == BP_STRING && second->max_matchlen == 0) return first;
if (first->type == BP_DEFINITIONS && second->type == BP_DEFINITIONS) {
return Pattern(BP_CHAIN, first->start, second->end, second->min_matchlen, second->max_matchlen, .first = first,
.second = second);
}
size_t minlen = first->min_matchlen + second->min_matchlen;
ssize_t maxlen = (UNBOUNDED(first) || UNBOUNDED(second)) ? (ssize_t)-1 : first->max_matchlen + second->max_matchlen;
return Pattern(BP_CHAIN, first->start, second->end, minlen, maxlen, .first = first, .second = second);
}
// // Given two patterns, return a new pattern for matching either the first // pattern or the second. If either pattern is NULL, return the other. // public bppatt *eitherpat(bppatt *first, bppatt *second) { if (first == NULL) return second; if (second == NULL) return first; sizet minlen = first->minmatchlen < second->minmatchlen ? first->minmatchlen : second->minmatchlen; ssizet maxlen = (UNBOUNDED(first) || UNBOUNDED(second)) ? (ssizet)-1 : (first->maxmatchlen > second->maxmatchlen ? first->maxmatchlen : second->maxmatchlen); return Pattern(BP_OTHERWISE, first->start, second->end, minlen, maxlen, .first = first, .second = second); }
//
// Parse a definition
//
attribute((nonnull)) static bppatt *bpdefinition(const char *start, const char end) {
if (start >= end || !(isalpha(start) || *start == ‘‘)) return NULL;
const char *str = aftername(start, end);
sizet namelen = (sizet)(str - start);
if (!matchchar(&str, ‘:’, false, end)) return NULL;
bool istagged = str < end && *str == ‘:’ && matchchar(&str, ‘:’, false, end);
bppatt *def = bppatternnl(str, end, false);
if (!def) parseerr(str, end, “Could not parse this definition.”);
str = def->end;
(void)matchchar(&str, ‘;’, false, end); // Optional semicolon
if (istagged) { // id:: foo means define a rule named id that gives captures an id tag
def = Pattern(BPTAGGED, def->start, def->end, def->minmatchlen, def->maxmatchlen, .pat = def, .name = start,
.namelen = namelen);
}
bppatt *next_def = bpdefinition(afterspaces(str, true, end), end);
return Pattern(BPDEFINITIONS, start, nextdef ? nextdef->end : str, 0, -1, .name = start, .namelen = namelen,
.meaning = def, .nextdef = nextdef);
}
//
// Compile a string of BP code into a BP pattern object.
//
attribute((nonnull)) static bppatt *bpsimplepattern(const char *str, const char end,
bool insidestringpattern) {
str = afterspaces(str, false, end);
if (!str) return NULL;
const char *start = str;
char c = str;
str = next_char(str, end);
switch © {
// Any char (dot)
case ‘.’: {
// As a special case, 3+ dots is parsed as a series of “any char” followed by a single “upto”
// In other words, ...foo parses as (.)(..foo) instead of (..(.)) (foo)
// This is so that ... can mean “at least one character upto” instead of “upto any character”,
// which is tautologically the same as matching any single character.
if (str == ‘.’ && (str + 1 >= end || str[1] != ‘.’)) { // “..”
str = nextchar(str, end);
enum bppattypee type = BPUPTO;
bppatt *extraarg = NULL;
if (matchchar(&str, ‘%’, false, end)) {
extraarg = bpsimplepattern(str, end);
if (extraarg) str = extraarg->end;
else parseerr(str, str, “There should be a pattern to skip here after the ‘%’”);
} else if (matchchar(&str, ‘=’, false, end)) {
extraarg = bpsimplepattern(str, end);
if (extraarg) str = extraarg->end;
else parseerr(str, str, “There should be a pattern here after the ‘=’”);
type = BPUPTOSTRICT;
}
bppatt *target;
if (insidestringpattern) {
target = NULL;
} else {
target = bpsimplepattern(str, end);
// Bugfix: echo "foo@" | bp '{..}{"@"}' should be parsed as .."@"
// not ‘(..””) “@”’
while (target && target->type == BPSTRING && target->maxmatchlen == 0)
target = bpsimplepattern(target->end, end);
}
return type == BPUPTO ? Pattern(BPUPTO, start, str, 0, -1, .target = target, .skip = extraarg)
: Pattern(BPUPTOSTRICT, start, str, 0, -1, .target = target, .skip = extraarg);
} else {
return Pattern(BPANYCHAR, start, str, 1, UTF8MAXCHARLEN);
}
}
// Char literals
case ‘': {
bp_pat_t *all = NULL;
do { // Comma-separated items:
if (str >= end || !*str || *str == '\n')
parse_err(str, str, "There should be a character here after the '’”);
const char *c1_loc = str;
str = next_char(c1_loc, end);
if (*str == '-') { // Range
const char *c2_loc = ++str;
if (next_char(c1_loc, end) > c1_loc + 1 || next_char(c2_loc, end) > c2_loc + 1)
parse_err(start, next_char(c2_loc, end), "Sorry, UTF-8 character ranges are not yet supported.");
char c1 = *c1_loc, c2 = *c2_loc;
if (!c2 || c2 == '\n')
parse_err(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(c2_loc, end);
bp_pat_t *pat =
Pattern(BP_RANGE, start == c1_loc - 1 ? start : c1_loc, str, 1, 1, .low = c1, .high = c2);
all = either_pat(all, pat);
} else {
size_t len = (size_t)(str - c1_loc);
bp_pat_t *pat = Pattern(BP_STRING, start, str, len, (ssize_t)len, .string = strndup(c1_loc, len));
all = either_pat(all, pat);
}
} while (*str++ == ',');
return all;
}
// Escapes
case '\\': {
if (!*str || *str == '\n') parse_err(str, str, "There should be an escape sequence here after this backslash.");
bp_pat_t *all = NULL;
do { // Comma-separated items:
const char *itemstart = str - 1;
if (*str == 'N') { // \N (nodent)
all = either_pat(all, Pattern(BP_NODENT, itemstart, ++str, 1, -1));
continue;
} else if (*str == 'C') { // \C (current indent)
all = either_pat(all, Pattern(BP_CURDENT, itemstart, ++str, 1, -1));
continue;
} else if (*str == 'i') { // \i (identifier char)
all = either_pat(all, Pattern(BP_ID_CONTINUE, itemstart, ++str, 1, -1));
continue;
} else if (*str == 'I') { // \I (identifier char, not including numbers)
all = either_pat(all, Pattern(BP_ID_START, itemstart, ++str, 1, -1));
continue;
} else if (*str == 'b') { // \b word boundary
all = either_pat(all, Pattern(BP_WORD_BOUNDARY, itemstart, ++str, 0, 0));
continue;
}
const char *opstart = str;
unsigned char e_low = (unsigned char)unescapechar(str, &str, end);
if (str == opstart) parse_err(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(str, end) != str + 1)
parse_err(start, next_char(str, end), "Sorry, UTF8 escape sequences are not supported in ranges.");
const char *seqstart = str;
e_high = (unsigned char)unescapechar(str, &str, end);
if (str == seqstart) parse_err(seqstart, str + 1, "This value isn't a valid escape sequence");
if (e_high < e_low)
parse_err(start, str, "Escape ranges should be low-to-high, but this is high-to-low.");
}
bp_pat_t *esc = Pattern(BP_RANGE, start, str, 1, 1, .low = e_low, .high = e_high);
all = either_pat(all, esc);
} while (*str == ',' && str++ < end);
return all;
}
// Word boundary
case '|': {
return Pattern(BP_WORD_BOUNDARY, start, str, 0, 0);
}
// String literal
case '"':
case '\'':
case '\002':
case '}': {
char endquote = c == '\002' ? '\003' : (c == '}' ? '{' : c);
char *litstart = (char *)str;
while (str < end && *str != endquote)
str = next_char(str, end);
size_t len = (size_t)(str - litstart);
str = next_char(str, end);
if (c == '}') ++start; // Don't include the "}" in the pattern source range
return Pattern(BP_STRING, start, str, len, (ssize_t)len, .string = strndup(litstart, len));
}
// Not <pat>
case '!': {
bp_pat_t *p = bp_simplepattern(str, end);
if (!p) parse_err(str, str, "There should be a pattern after this '!'");
return Pattern(BP_NOT, start, p->end, 0, 0, .pat = p);
}
// 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, end)) {
str = after_spaces(str, false, end);
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, end)) {
min = (size_t)n1, max = -1;
} else {
min = (size_t)n1, max = (ssize_t)n1;
}
bp_pat_t *repeating = bp_simplepattern(str, end);
if (!repeating) parse_err(str, str, "There should be a pattern after this repetition count.");
str = repeating->end;
bp_pat_t *sep = NULL;
if (matchchar(&str, '%', false, end)) {
sep = bp_simplepattern(str, end);
if (!sep) parse_err(str, str, "There should be a separator pattern after this '%%'");
str = sep->end;
} else {
str = repeating->end;
}
return new_range(start, str, min, max, repeating, sep);
}
// Lookbehind
case '<': {
bp_pat_t *behind = bp_simplepattern(str, end);
if (!behind) parse_err(str, str, "There should be a pattern after this '<'");
return Pattern(BP_AFTER, start, behind->end, 0, 0, .pat = behind);
}
// Lookahead
case '>': {
bp_pat_t *ahead = bp_simplepattern(str, end);
if (!ahead) parse_err(str, str, "There should be a pattern after this '>'");
return Pattern(BP_BEFORE, start, ahead->end, 0, 0, .pat = ahead);
}
// Parentheses
case '(': {
bp_pat_t *pat = bp_pattern_nl(str, end, true);
if (!pat) parse_err(str, str, "There should be a valid pattern after this parenthesis.");
str = pat->end;
if (!matchchar(&str, ')', true, end)) parse_err(str, str, "Missing paren: )");
pat->start = start;
pat->end = str;
return pat;
}
// Square brackets
case '[': {
bp_pat_t *maybe = bp_pattern_nl(str, end, true);
if (!maybe) parse_err(str, str, "There should be a valid pattern after this square bracket.");
str = maybe->end;
(void)matchchar(&str, ']', true, end);
return new_range(start, str, 0, 1, maybe, NULL);
}
// Repeating
case '*':
case '+': {
size_t min = (size_t)(c == '*' ? 0 : 1);
bp_pat_t *repeating = bp_simplepattern(str, end);
if (!repeating) parse_err(str, str, "There should be a valid pattern to repeat here");
str = repeating->end;
bp_pat_t *sep = NULL;
if (matchchar(&str, '%', false, end)) {
sep = bp_simplepattern(str, end);
if (!sep) parse_err(str, str, "There should be a separator pattern after the '%%' here.");
str = sep->end;
}
return new_range(start, str, min, -1, repeating, sep);
}
// Capture
case '@': {
if (matchchar(&str, ':', false, end)) { // Tagged capture @:Foo=pat
const char *name = str;
str = after_name(name, end);
if (str <= name) parse_err(start, str, "There should be an identifier after this '@:'");
size_t namelen = (size_t)(str - name);
bp_pat_t *p = NULL;
if (matchchar(&str, '=', false, end)) {
p = bp_simplepattern(str, end);
if (p) str = p->end;
}
return Pattern(BP_TAGGED, start, str, p ? p->min_matchlen : 0, p ? p->max_matchlen : 0, .pat = p,
.name = name, .namelen = namelen);
}
const char *name = NULL;
size_t namelen = 0;
const char *a = after_name(str, end);
const char *eq = a;
bool backreffable = false;
if (a > str && matchchar(&eq, ':', false, end)) {
name = str;
namelen = (size_t)(a - str);
str = eq;
backreffable = true;
} else if (a > str && !matchstr(&eq, "=>", false, end) && matchchar(&eq, '=', false, end)) {
name = str;
namelen = (size_t)(a - str);
str = eq;
}
bp_pat_t *pat = bp_simplepattern(str, end);
if (!pat) parse_err(str, str, "There should be a valid pattern here to capture after the '@'");
return Pattern(BP_CAPTURE, start, pat->end, pat->min_matchlen, pat->max_matchlen, .pat = pat, .name = name,
.namelen = namelen, .backreffable = backreffable);
}
// Start of file/line
case '^': {
if (*str == '^') return Pattern(BP_START_OF_FILE, start, ++str, 0, 0);
return Pattern(BP_START_OF_LINE, start, str, 0, 0);
}
// End of file/line:
case '$': {
if (*str == '$') return Pattern(BP_END_OF_FILE, start, ++str, 0, 0);
return Pattern(BP_END_OF_LINE, start, str, 0, 0);
}
default: {
bp_pat_t *def = _bp_definition(start, end);
if (def) return def;
// Reference
if (!isalpha(c) && c != '_') return NULL;
str = after_name(start, end);
size_t namelen = (size_t)(str - start);
return Pattern(BP_REF, start, str, 0, -1, .name = start, .len = namelen);
}
}
}
// // Similar to bpsimplepattern, except that the pattern begins with an implicit // ‘}’ open quote that can be closed with ‘{’ // public maybepatt bpstringpattern(const char *str, const char *end) { TRY_PATTERN if (!end) end = str + strlen(str); char *start = (char *)str; while (str < end && *str != ‘{’) str = nextchar(str, end); sizet len = (sizet)(str - start); bppatt *pat = len > 0 ? Pattern(BPSTRING, start, str, len, (ssizet)len, .string = strndup(start, len)) : NULL; str += 1; if (str < end) { bppatt *interp = bppatternnl(str, end, true); if (interp) pat = chaintogether(pat, interp); pat->end = end; } ENDTRYPATTERN return (maybepatt){.success = true, .value.pat = pat}; }
// // Wrapper for bpsimplepattern() that expands any postfix operators (~, !~) // static bppatt *bpsimplepattern(const char *str, const char *end) { const char *start = str; bppat_t *pat = bpsimplepattern(str, end, false); if (pat == NULL) return pat; str = pat->end;
// Expand postfix operators (if any)
while (str < end) {
enum bp_pattype_e type;
if (matchchar(&str, '~', false, end)) type = BP_MATCH;
else if (matchstr(&str, "!~", false, end)) type = BP_NOT_MATCH;
else break;
bp_pat_t *first = pat;
bp_pat_t *second = bp_simplepattern(str, end);
if (!second) parse_err(str, str, "There should be a valid pattern here");
pat = type == BP_MATCH ? Pattern(BP_MATCH, start, second->end, first->min_matchlen, first->max_matchlen,
.pat = first, .must_match = second)
: Pattern(BP_NOT_MATCH, start, second->end, first->min_matchlen, first->max_matchlen,
.pat = first, .must_not_match = second);
str = pat->end;
}
return pat;
}
// // Given a pattern and a replacement string, compile the two into a BP // replace pattern. // public maybepatt bpreplacement(bppat_t *replacepat, const char *replacement, const char *end) { const char p = replacement; if (!end) end = replacement + strlen(replacement); TRY_PATTERN for (; p < end; p++) { if (p == ‘\‘) { if (!p[1] || p[1] == ‘\n’) parse_err(p, p, “There should be an escape sequence or pattern here after this backslash.”); ++p; } } ENDTRYPATTERN sizet rlen = (sizet)(p - replacement); char *rcpy = new (char[rlen + 1]); memcpy(rcpy, replacement, rlen); bppatt *pat = Pattern(BPREPLACE, replacepat->start, replacepat->end, replacepat->minmatchlen, replacepat->maxmatchlen, .pat = replacepat, .text = rcpy, .len = rlen); return (maybepat_t){.success = true, .value.pat = pat}; }
static bppatt *bppatternnl(const char *str, const char *end, bool allownl) { str = afterspaces(str, allownl, end); bppatt *pat = bpsimplepattern(str, end); if (pat != NULL) pat = expandchoices(pat, end, allownl); if (matchstr(&str, “=>”, allownl, end)) pat = expandreplacements(pat ? pat : Pattern(BPSTRING, str - 2, str - 2, 0, 0), end, allownl); return pat; }
// // Return a new back reference to an existing match. // public bppatt *bprawliteral(const char *str, sizet len) { return Pattern(BPSTRING, str, &str[len], len, (ssize_t)len, .string = strndup(str, len)); }
// // Compile a string representing a BP pattern into a pattern object. // public maybepatt bppattern(const char *str, const char *end) { str = afterspaces(str, true, end); if (!end) end = str + strlen(str); TRY_PATTERN bppatt *ret = bppatternnl(str, end, false); ENDTRYPATTERN if (ret && afterspaces(ret->end, true, end) < end) return (maybepatt){.success = false, .value.error.start = ret->end, .value.error.end = end, .value.error.msg = “Failed to parse this part of the pattern”}; else if (ret) return (maybepatt){.success = true, .value.pat = ret}; else return (maybepat_t){.success = false, .value.error.start = str, .value.error.end = end, .value.error.msg = “Failed to parse this pattern”}; }
public void freeallpats(void) { while (allocatedpats) { bppatt *tofree = allocatedpats; allocated_pats = tofree->next; delete (&tofree); } }
public void deletepat(bppatt **atpat, bool recursive) { bppatt *pat = *at_pat; if (!pat) return;
#define T(tag, …)
case tag: {
auto _data = When(pat, tag);
VA_ARGS;
break;
}
#define F(field) deletepat(&data->field, true)
if (recursive) {
switch (pat->type) {
T(BPDEFINITIONS, F(meaning), F(nextdef))
T(BPREPEAT, F(sep), F(repeatpat))
T(BPCHAIN, F(first), F(second))
T(BPUPTO, F(target), F(skip))
T(BPUPTOSTRICT, F(target), F(skip))
T(BPOTHERWISE, F(first), F(second))
T(BPMATCH, F(pat), F(mustmatch))
T(BPNOTMATCH, F(pat), F(mustnotmatch))
T(BPREPLACE, F(pat))
T(BPCAPTURE, F(pat))
T(BPTAGGED, F(pat))
T(BPNOT, F(pat))
T(BPAFTER, F(pat))
T(BPBEFORE, F(pat))
T(BPLEFTRECURSION, F(fallback))
T(
BPSTRING, if (data->string) {
free((char *)_data->string);
_data->string = NULL;
})
default: break;
}
}
#undef F
#undef T
if (pat->home) *(pat->home) = pat->next;
if (pat->next) pat->next->home = pat->home;
delete (at_pat);
}
int fprintpattern(FILE *stream, bppatt *pat) {
if (!pat) return fputs(“(null)”, stream);
switch (pat->type) {
#define CASE(name, …)
case BP##name: {
auto_type data = pat->tagged.BP_##name;
(void)data;
int _printed = fputs(#name, stream);
VA_ARGS;
return _printed;
}
#define FMT(…) _printed += fprintf(stream, VA_ARGS)
#define PAT(p) printed += fprintpattern(stream, p)
CASE(ERROR)
CASE(ANYCHAR)
CASE(IDSTART)
CASE(IDCONTINUE)
CASE(STRING, FMT(“("%s")”, data.string))
CASE(RANGE, FMT(“(‘%c’-’%c’)”, data.low, data.high))
CASE(NOT, FMT(“(“); PAT(data.pat); FMT(“)”))
CASE(UPTO, FMT(“(“); PAT(data.target); FMT(“, skip=”); PAT(data.skip); FMT(“)”))
CASE(UPTOSTRICT, FMT(“(“); PAT(data.target); FMT(“, skip=)”); PAT(data.skip))
CASE(REPEAT, FMT(“(%u-%d, “, data.min, data.max); PAT(data.repeatpat); FMT(“, sep=”); PAT(data.sep); FMT(“)”))
CASE(BEFORE, FMT(“(“); PAT(data.pat); FMT(“)”))
CASE(AFTER, FMT(“(“); PAT(data.pat); FMT(“)”))
CASE(CAPTURE, FMT(“(“); PAT(data.pat);
FMT(“, name=%.s, backref=%s)”, data.namelen, data.name, data.backreffable ? “yes” : “no”))
CASE(OTHERWISE, FMT(“(“); PAT(data.first); FMT(“, “); PAT(data.second); FMT(“)”))
CASE(CHAIN, FMT(“(“); PAT(data.first); FMT(“, “); PAT(data.second); FMT(“)”))
CASE(MATCH, FMT(“(“); PAT(data.pat); FMT(“, matches=”); PAT(data.mustmatch); FMT(“)”))
CASE(NOTMATCH, FMT(“(“); PAT(data.pat); FMT(“, mustnotmatch=”); PAT(data.mustnotmatch); FMT(“)”))
CASE(REPLACE, FMT(“(“); PAT(data.pat); FMT(“, "%.s")”, data.len, data.text))
CASE(REF, FMT(“(%.s)”, data.len, data.name))
CASE(NODENT)
CASE(CURDENT)
CASE(STARTOFFILE)
CASE(STARTOFLINE)
CASE(ENDOFFILE)
CASE(ENDOFLINE)
CASE(WORD_BOUNDARY)
CASE(DEFINITIONS, FMT(“(%.s=”, data.namelen, data.name); PAT(data.meaning); FMT(“); “); PAT(data.next_def))
CASE(TAGGED, FMT(“(%.*s=”, data.namelen, data.name); PAT(data.pat);
FMT(“ backref=%s)”, data.backreffable ? “yes” : “no”))
#undef PAT
#undef FMT
#undef P
default: return fputs(“???”, stream);
}
}
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,:0
