1407 lines
33 KiB
C
1407 lines
33 KiB
C
/*
|
|
* lsfd-filter.c - filtering engine for lsfd
|
|
*
|
|
* Copyright (C) 2021 Red Hat, Inc.
|
|
* Copyright (C) 2021 Masatake YAMATO <yamato@redhat.com>
|
|
*
|
|
* This file may be redistributed under the terms of the
|
|
* GNU Lesser General Public License.
|
|
*/
|
|
|
|
#include "lsfd-filter.h"
|
|
|
|
#include "nls.h"
|
|
#include "strutils.h"
|
|
#include "xalloc.h"
|
|
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <regex.h> /* regcomp(), regexec() */
|
|
|
|
/*
|
|
* Definitions
|
|
*/
|
|
#define COL_HEADER_EXTRA_CHARS ":-_%" /* ??? */
|
|
#define GOT_ERROR(PARSERorFILTER)(*((PARSERorFILTER)->errmsg))
|
|
|
|
/*
|
|
* Types
|
|
*/
|
|
|
|
enum token_type {
|
|
TOKEN_NAME, /* [A-Za-z_][-_:%A-Za-z0-9]* */
|
|
TOKEN_STR, /* "...", '...' */
|
|
TOKEN_DEC, /* [1-9][0-9]+, NOTE: negative value is no dealt. */
|
|
TOKEN_HEX, /* 0x[0-9a-f]+ not implemented */
|
|
TOKEN_OCT, /* 0[1-7]+ not implemented */
|
|
TOKEN_TRUE, /* true */
|
|
TOKEN_FALSE, /* false */
|
|
TOKEN_OPEN, /* ( */
|
|
TOKEN_CLOSE, /* ) */
|
|
TOKEN_OP1, /* !, not */
|
|
TOKEN_OP2, /* TODO: =*, !* (glob match with fnmatch() */
|
|
TOKEN_EOF,
|
|
};
|
|
|
|
enum op1_type {
|
|
OP1_NOT,
|
|
};
|
|
|
|
enum op2_type {
|
|
OP2_EQ,
|
|
OP2_NE,
|
|
OP2_AND,
|
|
OP2_OR,
|
|
OP2_LT,
|
|
OP2_LE,
|
|
OP2_GT,
|
|
OP2_GE,
|
|
OP2_RE_MATCH,
|
|
OP2_RE_UNMATCH,
|
|
};
|
|
|
|
struct token {
|
|
enum token_type type;
|
|
union {
|
|
char *str;
|
|
unsigned long long num;
|
|
enum op1_type op1;
|
|
enum op2_type op2;
|
|
} val;
|
|
};
|
|
|
|
struct token_class {
|
|
const char *name;
|
|
void (*free)(struct token *);
|
|
void (*dump)(struct token *, FILE *);
|
|
};
|
|
|
|
struct parameter {
|
|
struct libscols_column *cl;
|
|
bool has_value;
|
|
union {
|
|
const char *str;
|
|
unsigned long long num;
|
|
bool boolean;
|
|
} val;
|
|
};
|
|
|
|
struct parser {
|
|
const char *expr;
|
|
const char *cursor;
|
|
int paren_level;
|
|
struct libscols_table *tb;
|
|
int (*column_name_to_id)(const char *, void *);
|
|
struct libscols_column *(*add_column_by_id)(struct libscols_table *, int, void*);
|
|
void *data;
|
|
struct parameter *parameters;
|
|
#define ERRMSG_LEN 128
|
|
char errmsg[ERRMSG_LEN];
|
|
};
|
|
|
|
enum node_type {
|
|
NODE_STR,
|
|
NODE_NUM,
|
|
NODE_BOOL,
|
|
NODE_RE,
|
|
NODE_OP1,
|
|
NODE_OP2,
|
|
};
|
|
|
|
struct node {
|
|
enum node_type type;
|
|
};
|
|
|
|
struct op1_class {
|
|
const char *name;
|
|
/* Return true if acceptable. */
|
|
bool (*is_acceptable)(struct node *, struct parameter *, struct libscols_line *);
|
|
/* Return true if o.k. */
|
|
bool (*check_type)(struct parser *, struct op1_class *, struct node *);
|
|
};
|
|
|
|
struct op2_class {
|
|
const char *name;
|
|
/* Return true if acceptable. */
|
|
bool (*is_acceptable)(struct node *, struct node *, struct parameter *, struct libscols_line *);
|
|
/* Return true if o.k. */
|
|
bool (*check_type)(struct parser *, struct op2_class *, struct node *, struct node *);
|
|
};
|
|
|
|
#define VAL(NODE,FIELD) (((struct node_val *)(NODE))->val.FIELD)
|
|
#define PINDEX(NODE) (((struct node_val *)(NODE))->pindex)
|
|
struct node_val {
|
|
struct node base;
|
|
int pindex;
|
|
union {
|
|
char *str;
|
|
unsigned long long num;
|
|
bool boolean;
|
|
regex_t re;
|
|
} val;
|
|
};
|
|
|
|
struct node_op1 {
|
|
struct node base;
|
|
struct op1_class *opclass;
|
|
struct node *arg;
|
|
};
|
|
|
|
struct node_op2 {
|
|
struct node base;
|
|
struct op2_class *opclass;
|
|
struct node *args[2];
|
|
};
|
|
|
|
struct node_class {
|
|
const char *name;
|
|
void (*free)(struct node *);
|
|
void (*dump)(struct node *, struct parameter*, int, FILE *);
|
|
};
|
|
|
|
struct lsfd_filter {
|
|
struct libscols_table *table;
|
|
struct node *node;
|
|
struct parameter *parameters;
|
|
int nparams;
|
|
char errmsg[ERRMSG_LEN];
|
|
};
|
|
|
|
/*
|
|
* Prototypes
|
|
*/
|
|
static struct node *node_val_new(enum node_type, int pindex);
|
|
static void node_free (struct node *);
|
|
static bool node_apply(struct node *, struct parameter *, struct libscols_line *);
|
|
static void node_dump (struct node *, struct parameter *, int, FILE *);
|
|
|
|
static struct token *token_new (void);
|
|
static void token_free(struct token *);
|
|
#ifdef DEBUG
|
|
static void token_dump(struct token *, FILE *);
|
|
#endif /* DEBUG */
|
|
|
|
static void token_free_str(struct token *);
|
|
|
|
static void token_dump_str(struct token *, FILE *);
|
|
static void token_dump_num(struct token *, FILE *);
|
|
static void token_dump_op1(struct token *, FILE *);
|
|
static void token_dump_op2(struct token *, FILE *);
|
|
|
|
static bool op1_not(struct node *, struct parameter*, struct libscols_line *);
|
|
static bool op1_check_type_bool_or_op(struct parser *, struct op1_class *, struct node *);
|
|
|
|
static bool op2_eq (struct node *, struct node *, struct parameter*, struct libscols_line *);
|
|
static bool op2_ne (struct node *, struct node *, struct parameter*, struct libscols_line *);
|
|
static bool op2_and(struct node *, struct node *, struct parameter*, struct libscols_line *);
|
|
static bool op2_or (struct node *, struct node *, struct parameter*, struct libscols_line *);
|
|
static bool op2_lt (struct node *, struct node *, struct parameter*, struct libscols_line *);
|
|
static bool op2_le (struct node *, struct node *, struct parameter*, struct libscols_line *);
|
|
static bool op2_gt (struct node *, struct node *, struct parameter*, struct libscols_line *);
|
|
static bool op2_ge (struct node *, struct node *, struct parameter*, struct libscols_line *);
|
|
static bool op2_re_match (struct node *, struct node *, struct parameter*, struct libscols_line *);
|
|
static bool op2_re_unmatch (struct node *, struct node *, struct parameter*, struct libscols_line *);
|
|
|
|
static bool op2_check_type_eq_or_bool_or_op(struct parser *, struct op2_class *, struct node *, struct node *);
|
|
static bool op2_check_type_boolean_or_op (struct parser *, struct op2_class *, struct node *, struct node *);
|
|
static bool op2_check_type_num (struct parser *, struct op2_class *, struct node *, struct node *);
|
|
static bool op2_check_type_re (struct parser *, struct op2_class *, struct node *, struct node *);
|
|
|
|
static void node_str_free(struct node *);
|
|
static void node_re_free (struct node *);
|
|
static void node_op1_free(struct node *);
|
|
static void node_op2_free(struct node *);
|
|
|
|
static void node_str_dump (struct node *, struct parameter*, int, FILE *);
|
|
static void node_num_dump (struct node *, struct parameter*, int, FILE *);
|
|
static void node_bool_dump(struct node *, struct parameter*, int, FILE *);
|
|
static void node_re_dump (struct node *, struct parameter*, int, FILE *);
|
|
static void node_op1_dump (struct node *, struct parameter*, int, FILE *);
|
|
static void node_op2_dump (struct node *, struct parameter*, int, FILE *);
|
|
|
|
static struct node *dparser_compile(struct parser *);
|
|
|
|
/*
|
|
* Data
|
|
*/
|
|
#define TOKEN_CLASS(TOKEN) (&token_classes[(TOKEN)->type])
|
|
static struct token_class token_classes [] = {
|
|
[TOKEN_NAME] = {
|
|
.name = "NAME",
|
|
.free = token_free_str,
|
|
.dump = token_dump_str,
|
|
},
|
|
[TOKEN_STR] = {
|
|
.name = "STR",
|
|
.free = token_free_str,
|
|
.dump = token_dump_str,
|
|
},
|
|
[TOKEN_DEC] = {
|
|
.name = "DEC",
|
|
.dump = token_dump_num,
|
|
},
|
|
[TOKEN_TRUE] = {
|
|
.name = "true",
|
|
},
|
|
[TOKEN_FALSE] = {
|
|
.name = "false",
|
|
},
|
|
[TOKEN_OPEN] = {
|
|
.name = "OPEN",
|
|
},
|
|
[TOKEN_CLOSE] = {
|
|
.name = "CLOSE",
|
|
},
|
|
[TOKEN_OP1] = {
|
|
.name = "OP1",
|
|
.dump = token_dump_op1,
|
|
},
|
|
[TOKEN_OP2] = {
|
|
.name = "OP2",
|
|
.dump = token_dump_op2,
|
|
},
|
|
[TOKEN_EOF] = {
|
|
.name = "TOKEN_EOF",
|
|
},
|
|
};
|
|
|
|
#define TOKEN_OP1_CLASS(TOKEN) (&(op1_classes[(TOKEN)->val.op1]))
|
|
static struct op1_class op1_classes [] = {
|
|
[OP1_NOT] = {
|
|
.name = "!",
|
|
.is_acceptable = op1_not,
|
|
.check_type = op1_check_type_bool_or_op,
|
|
},
|
|
};
|
|
|
|
#define TOKEN_OP2_CLASS(TOKEN) (&(op2_classes[(TOKEN)->val.op2]))
|
|
static struct op2_class op2_classes [] = {
|
|
[OP2_EQ] = {
|
|
.name = "==",
|
|
.is_acceptable = op2_eq,
|
|
.check_type = op2_check_type_eq_or_bool_or_op
|
|
},
|
|
[OP2_NE] = {
|
|
.name = "!=",
|
|
.is_acceptable = op2_ne,
|
|
.check_type = op2_check_type_eq_or_bool_or_op,
|
|
},
|
|
[OP2_AND] = {
|
|
.name = "&&",
|
|
.is_acceptable = op2_and,
|
|
.check_type = op2_check_type_boolean_or_op,
|
|
},
|
|
[OP2_OR] = {
|
|
.name = "||",
|
|
.is_acceptable = op2_or,
|
|
.check_type = op2_check_type_boolean_or_op,
|
|
},
|
|
[OP2_LT] = {
|
|
.name = "<",
|
|
.is_acceptable = op2_lt,
|
|
.check_type = op2_check_type_num,
|
|
},
|
|
[OP2_LE] = {
|
|
.name = "<=",
|
|
.is_acceptable = op2_le,
|
|
.check_type = op2_check_type_num,
|
|
},
|
|
[OP2_GT] = {
|
|
.name = ">",
|
|
.is_acceptable = op2_gt,
|
|
.check_type = op2_check_type_num,
|
|
},
|
|
[OP2_GE] = {
|
|
.name = ">=",
|
|
.is_acceptable = op2_ge,
|
|
.check_type = op2_check_type_num,
|
|
},
|
|
[OP2_RE_MATCH] = {
|
|
.name = "=~",
|
|
.is_acceptable = op2_re_match,
|
|
.check_type = op2_check_type_re,
|
|
},
|
|
[OP2_RE_UNMATCH] = {
|
|
.name = "!~",
|
|
.is_acceptable = op2_re_unmatch,
|
|
.check_type = op2_check_type_re,
|
|
},
|
|
};
|
|
|
|
#define NODE_CLASS(NODE) (&node_classes[(NODE)->type])
|
|
static struct node_class node_classes[] = {
|
|
[NODE_STR] = {
|
|
.name = "STR",
|
|
.free = node_str_free,
|
|
.dump = node_str_dump,
|
|
},
|
|
[NODE_NUM] = {
|
|
.name = "NUM",
|
|
.dump = node_num_dump,
|
|
},
|
|
[NODE_BOOL] = {
|
|
.name = "BOOL",
|
|
.dump = node_bool_dump,
|
|
},
|
|
[NODE_RE] = {
|
|
.name = "STR",
|
|
.free = node_re_free,
|
|
.dump = node_re_dump,
|
|
},
|
|
[NODE_OP1] = {
|
|
.name = "OP1",
|
|
.free = node_op1_free,
|
|
.dump = node_op1_dump,
|
|
},
|
|
[NODE_OP2] = {
|
|
.name = "OP2",
|
|
.free = node_op2_free,
|
|
.dump = node_op2_dump,
|
|
}
|
|
};
|
|
|
|
/*
|
|
* Functions
|
|
*/
|
|
static int strputc(char **a, const char b)
|
|
{
|
|
return strappend(a, (char [2]){b, '\0'});
|
|
}
|
|
|
|
static void xstrputc(char **a, const char b)
|
|
{
|
|
int rc = strputc(a, b);
|
|
if (rc < 0)
|
|
errx(EXIT_FAILURE, _("failed to allocate memory"));
|
|
}
|
|
|
|
static void parser_init(struct parser *parser, const char *const expr, struct libscols_table *tb,
|
|
int ncols,
|
|
int (*column_name_to_id)(const char *, void *),
|
|
struct libscols_column *(*add_column_by_id)(struct libscols_table *, int, void*),
|
|
void *data)
|
|
{
|
|
parser->expr = expr;
|
|
parser->cursor = parser->expr;
|
|
parser->paren_level = 0;
|
|
parser->tb = tb;
|
|
parser->column_name_to_id = column_name_to_id;
|
|
parser->add_column_by_id = add_column_by_id;
|
|
parser->data = data;
|
|
parser->parameters = xcalloc(ncols, sizeof(struct parameter));
|
|
parser->errmsg[0] = '\0';
|
|
}
|
|
|
|
static char parser_getc(struct parser *parser)
|
|
{
|
|
char c = *parser->cursor;
|
|
if (c != '\0')
|
|
parser->cursor++;
|
|
return c;
|
|
}
|
|
|
|
static void parser_ungetc(struct parser *parser, char c)
|
|
{
|
|
assert(parser->cursor > parser->expr);
|
|
if (c != '\0')
|
|
parser->cursor--;
|
|
}
|
|
|
|
static void parser_read_str(struct parser *parser, struct token *token, char delimiter)
|
|
{
|
|
bool escape = false;
|
|
while (1) {
|
|
char c = parser_getc(parser);
|
|
|
|
if (c == '\0') {
|
|
snprintf(parser->errmsg, ERRMSG_LEN,
|
|
_("error: string literal is not terminated: %s"),
|
|
token->val.str? : "");
|
|
return;
|
|
} else if (escape) {
|
|
switch (c) {
|
|
case '\\':
|
|
case '\'':
|
|
case '"':
|
|
xstrputc(&token->val.str, c);
|
|
break;
|
|
case 'n':
|
|
xstrputc(&token->val.str, '\n');
|
|
break;
|
|
case 't':
|
|
xstrputc(&token->val.str, '\t');
|
|
break;
|
|
/* TODO: \f, \r, ... */
|
|
default:
|
|
xstrputc(&token->val.str, '\\');
|
|
xstrputc(&token->val.str, c);
|
|
return;
|
|
}
|
|
escape = false;
|
|
}
|
|
else if (c == delimiter)
|
|
return;
|
|
else if (c == '\\')
|
|
escape = true;
|
|
else
|
|
xstrputc(&token->val.str, c);
|
|
}
|
|
}
|
|
|
|
static void parser_read_name(struct parser *parser, struct token *token)
|
|
{
|
|
while (1) {
|
|
char c = parser_getc(parser);
|
|
if (c == '\0')
|
|
break;
|
|
if (strchr(COL_HEADER_EXTRA_CHARS, c) || isalnum((unsigned char)c)) {
|
|
xstrputc(&token->val.str, c);
|
|
continue;
|
|
}
|
|
parser_ungetc(parser, c);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int parser_read_dec(struct parser *parser, struct token *token)
|
|
{
|
|
int rc = 0;
|
|
while (1) {
|
|
char c = parser_getc(parser);
|
|
if (c == '\0')
|
|
break;
|
|
if (isdigit((unsigned char)c)) {
|
|
xstrputc(&token->val.str, c);
|
|
continue;
|
|
}
|
|
parser_ungetc(parser, c);
|
|
break;
|
|
}
|
|
|
|
errno = 0;
|
|
unsigned long long num = strtoull(token->val.str, NULL, 10);
|
|
rc = errno;
|
|
free(token->val.str);
|
|
token->val.num = num;
|
|
return rc;
|
|
}
|
|
|
|
static struct token *parser_read(struct parser *parser)
|
|
{
|
|
struct token *t = token_new();
|
|
char c, c0;
|
|
|
|
do
|
|
c = parser_getc(parser);
|
|
while (isspace((unsigned char)c));
|
|
|
|
switch (c) {
|
|
case '\0':
|
|
t->type = TOKEN_EOF;
|
|
break;
|
|
case '(':
|
|
t->type = TOKEN_OPEN;
|
|
parser->paren_level++;
|
|
break;
|
|
case ')':
|
|
t->type = TOKEN_CLOSE;
|
|
parser->paren_level--;
|
|
if (parser->paren_level < 0)
|
|
snprintf(parser->errmsg, ERRMSG_LEN,
|
|
_("error: unbalanced parenthesis: %s"), parser->cursor - 1);
|
|
break;
|
|
case '!':
|
|
c0 = parser_getc(parser);
|
|
if (c0 == '=') {
|
|
t->type = TOKEN_OP2;
|
|
t->val.op2 = OP2_NE;
|
|
break;
|
|
} else if (c0 == '~') {
|
|
t->type = TOKEN_OP2;
|
|
t->val.op2 = OP2_RE_UNMATCH;
|
|
break;
|
|
}
|
|
parser_ungetc(parser, c0);
|
|
t->type = TOKEN_OP1;
|
|
t->val.op1 = OP1_NOT;
|
|
break;
|
|
case '<':
|
|
t->type = TOKEN_OP2;
|
|
c0 = parser_getc(parser);
|
|
if (c0 == '=') {
|
|
t->val.op2 = OP2_LE;
|
|
break;
|
|
}
|
|
parser_ungetc(parser, c0);
|
|
t->val.op2 = OP2_LT;
|
|
break;
|
|
case '>':
|
|
t->type = TOKEN_OP2;
|
|
c0 = parser_getc(parser);
|
|
if (c0 == '=') {
|
|
t->val.op2 = OP2_GE;
|
|
break;
|
|
}
|
|
parser_ungetc(parser, c0);
|
|
t->val.op2 = OP2_GT;
|
|
break;
|
|
case '=':
|
|
c0 = parser_getc(parser);
|
|
if (c0 == '=') {
|
|
t->type = TOKEN_OP2;
|
|
t->val.op2 = OP2_EQ;
|
|
break;
|
|
} else if (c0 == '~') {
|
|
t->type = TOKEN_OP2;
|
|
t->val.op2 = OP2_RE_MATCH;
|
|
break;
|
|
}
|
|
snprintf(parser->errmsg, ERRMSG_LEN,
|
|
_("error: unexpected character %c after ="), c0);
|
|
break;
|
|
case '&':
|
|
c0 = parser_getc(parser);
|
|
if (c0 == '&') {
|
|
t->type = TOKEN_OP2;
|
|
t->val.op2 = OP2_AND;
|
|
break;
|
|
}
|
|
snprintf(parser->errmsg, ERRMSG_LEN,
|
|
_("error: unexpected character %c after ="), c0);
|
|
break;
|
|
case '|':
|
|
c0 = parser_getc(parser);
|
|
if (c0 == '|') {
|
|
t->type = TOKEN_OP2;
|
|
t->val.op2= OP2_OR;
|
|
break;
|
|
}
|
|
snprintf(parser->errmsg, ERRMSG_LEN,
|
|
_("error: unexpected character %c after ="), c0);
|
|
break;
|
|
case '"':
|
|
case '\'':
|
|
t->type = TOKEN_STR;
|
|
parser_read_str(parser, t, c);
|
|
break;
|
|
default:
|
|
if (isalpha((unsigned char)c) || c == '_') {
|
|
xstrputc(&t->val.str, c);
|
|
parser_read_name(parser, t);
|
|
if (strcmp(t->val.str, "true") == 0) {
|
|
free(t->val.str);
|
|
t->type = TOKEN_TRUE;
|
|
} else if (strcmp(t->val.str, "false") == 0) {
|
|
free(t->val.str);
|
|
t->type = TOKEN_FALSE;
|
|
} else if (strcmp(t->val.str, "or") == 0) {
|
|
free(t->val.str);
|
|
t->type = TOKEN_OP2;
|
|
t->val.op2= OP2_OR;
|
|
} else if (strcmp(t->val.str, "and") == 0) {
|
|
free(t->val.str);
|
|
t->type = TOKEN_OP2;
|
|
t->val.op2= OP2_AND;
|
|
} else if (strcmp(t->val.str, "eq") == 0) {
|
|
free(t->val.str);
|
|
t->type = TOKEN_OP2;
|
|
t->val.op2= OP2_EQ;
|
|
} else if (strcmp(t->val.str, "ne") == 0) {
|
|
free(t->val.str);
|
|
t->type = TOKEN_OP2;
|
|
t->val.op2= OP2_NE;
|
|
} else if (strcmp(t->val.str, "lt") == 0) {
|
|
free(t->val.str);
|
|
t->type = TOKEN_OP2;
|
|
t->val.op2 = OP2_LT;
|
|
} else if (strcmp(t->val.str, "le") == 0) {
|
|
free(t->val.str);
|
|
t->type = TOKEN_OP2;
|
|
t->val.op2 = OP2_LE;
|
|
} else if (strcmp(t->val.str, "gt") == 0) {
|
|
free(t->val.str);
|
|
t->type = TOKEN_OP2;
|
|
t->val.op2 = OP2_GT;
|
|
} else if (strcmp(t->val.str, "ge") == 0) {
|
|
free(t->val.str);
|
|
t->type = TOKEN_OP2;
|
|
t->val.op2 = OP2_GE;
|
|
} else if (strcmp(t->val.str, "not") == 0) {
|
|
free(t->val.str);
|
|
t->type = TOKEN_OP1;
|
|
t->val.op1 = OP1_NOT;
|
|
} else
|
|
t->type = TOKEN_NAME;
|
|
break;
|
|
} else if (isdigit((unsigned char)c)) {
|
|
t->type = TOKEN_DEC;
|
|
xstrputc(&t->val.str, c);
|
|
if (parser_read_dec(parser, t) != 0)
|
|
snprintf(parser->errmsg, ERRMSG_LEN,
|
|
_("error: failed to convert input to number"));
|
|
break;
|
|
}
|
|
snprintf(parser->errmsg, ERRMSG_LEN,
|
|
_("error: unexpected character %c"), c);
|
|
break;
|
|
}
|
|
return t;
|
|
}
|
|
|
|
static void parameter_init(struct parameter *param, struct libscols_column *cl)
|
|
{
|
|
param->cl = cl;
|
|
param->has_value = false;
|
|
}
|
|
|
|
static struct libscols_column *search_column(struct libscols_table *tb, const char *name)
|
|
{
|
|
size_t len = scols_table_get_ncols(tb);
|
|
size_t i;
|
|
|
|
for (i = 0; i < len; i++) {
|
|
struct libscols_column *cl = scols_table_get_column(tb, i);
|
|
const char *n = scols_column_get_name(cl);
|
|
|
|
if (n && strcmp(n, name) == 0)
|
|
return cl;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static struct node *dparser_compile1(struct parser *parser, struct node *last)
|
|
{
|
|
struct token *t = parser_read(parser);
|
|
|
|
if (GOT_ERROR(parser)) {
|
|
token_free(t);
|
|
return NULL;
|
|
}
|
|
|
|
if (t->type == TOKEN_EOF) {
|
|
token_free(t);
|
|
return last;
|
|
}
|
|
if (t->type == TOKEN_CLOSE) {
|
|
token_free(t);
|
|
return last;
|
|
}
|
|
|
|
if (last) {
|
|
switch (t->type) {
|
|
case TOKEN_NAME:
|
|
case TOKEN_STR:
|
|
case TOKEN_DEC:
|
|
case TOKEN_TRUE:
|
|
case TOKEN_FALSE:
|
|
case TOKEN_OPEN:
|
|
case TOKEN_OP1:
|
|
snprintf(parser->errmsg, ERRMSG_LEN,
|
|
_("error: unexpected token: %s after %s"), t->val.str,
|
|
NODE_CLASS(last)->name);
|
|
token_free(t);
|
|
return NULL;
|
|
default:
|
|
break;
|
|
}
|
|
} else {
|
|
switch (t->type) {
|
|
case TOKEN_OP2:
|
|
snprintf(parser->errmsg, ERRMSG_LEN,
|
|
_("error: empty left side expression: %s"),
|
|
TOKEN_OP2_CLASS(t)->name);
|
|
token_free(t);
|
|
return NULL;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
struct node *node = NULL;
|
|
switch (t->type) {
|
|
case TOKEN_NAME: {
|
|
int col_id = parser->column_name_to_id(t->val.str, parser->data);
|
|
if (col_id == LSFD_FILTER_UNKNOWN_COL_ID) {
|
|
snprintf(parser->errmsg, ERRMSG_LEN,
|
|
_("error: no such column: %s"), t->val.str);
|
|
token_free(t);
|
|
return NULL;
|
|
|
|
}
|
|
|
|
struct libscols_column *cl = search_column(parser->tb, t->val.str);
|
|
if (!cl) {
|
|
cl = parser->add_column_by_id(parser->tb, col_id, parser->data);
|
|
if (!cl) {
|
|
snprintf(parser->errmsg, ERRMSG_LEN,
|
|
_("error: cannot add a column to table: %s"), t->val.str);
|
|
token_free(t);
|
|
return NULL;
|
|
}
|
|
scols_column_set_flags(cl, SCOLS_FL_HIDDEN);
|
|
}
|
|
parameter_init(parser->parameters + col_id, cl);
|
|
|
|
int jtype = scols_column_get_json_type(cl);
|
|
int ntype;
|
|
switch (jtype) {
|
|
case SCOLS_JSON_STRING:
|
|
ntype = NODE_STR;
|
|
break;
|
|
case SCOLS_JSON_NUMBER:
|
|
ntype = NODE_NUM;
|
|
break;
|
|
case SCOLS_JSON_BOOLEAN:
|
|
ntype = NODE_BOOL;
|
|
break;
|
|
default:
|
|
snprintf(parser->errmsg, ERRMSG_LEN,
|
|
_("error: unsupported column data type: %d, column: %s"),
|
|
jtype, t->val.str);
|
|
return NULL;
|
|
}
|
|
node = node_val_new(ntype, col_id);
|
|
token_free(t);
|
|
return node;
|
|
}
|
|
|
|
case TOKEN_STR:
|
|
node = node_val_new(NODE_STR, -1);
|
|
VAL(node, str) = xstrdup(t->val.str);
|
|
token_free(t);
|
|
return node;
|
|
|
|
case TOKEN_DEC:
|
|
node = node_val_new(NODE_NUM, -1);
|
|
VAL(node, num) = t->val.num;
|
|
token_free(t);
|
|
return node;
|
|
|
|
case TOKEN_TRUE:
|
|
case TOKEN_FALSE:
|
|
node = node_val_new(NODE_BOOL, -1);
|
|
VAL(node, boolean) = (t->type == TOKEN_TRUE);
|
|
token_free(t);
|
|
return node;
|
|
|
|
case TOKEN_OPEN:
|
|
token_free(t);
|
|
return dparser_compile(parser);
|
|
|
|
case TOKEN_OP1: {
|
|
struct node *op1_right = dparser_compile1(parser, NULL);
|
|
struct op1_class *op1_class = TOKEN_OP1_CLASS(t);
|
|
|
|
token_free(t);
|
|
|
|
if (GOT_ERROR(parser)) {
|
|
node_free(op1_right);
|
|
return NULL;
|
|
}
|
|
|
|
if (op1_right == NULL) {
|
|
snprintf(parser->errmsg, ERRMSG_LEN,
|
|
_("error: empty right side expression: %s"),
|
|
op1_class->name);
|
|
return NULL;
|
|
}
|
|
|
|
if (!op1_class->check_type(parser, op1_class, op1_right)) {
|
|
node_free(op1_right);
|
|
return NULL;
|
|
}
|
|
|
|
node = xmalloc(sizeof(struct node_op1));
|
|
node->type = NODE_OP1;
|
|
((struct node_op1 *)node)->opclass = op1_class;
|
|
((struct node_op1 *)node)->arg = op1_right;
|
|
|
|
return node;
|
|
}
|
|
|
|
case TOKEN_OP2: {
|
|
struct node *op2_right = dparser_compile1(parser, NULL);
|
|
struct op2_class *op2_class = TOKEN_OP2_CLASS(t);
|
|
|
|
token_free(t);
|
|
|
|
if (GOT_ERROR(parser)) {
|
|
node_free(op2_right);
|
|
return NULL;
|
|
}
|
|
if (op2_right == NULL) {
|
|
snprintf(parser->errmsg, ERRMSG_LEN,
|
|
_("error: empty right side expression: %s"),
|
|
op2_class->name);
|
|
return NULL;
|
|
}
|
|
|
|
if (!op2_class->check_type(parser, op2_class, last, op2_right)) {
|
|
node_free(op2_right);
|
|
return NULL;
|
|
}
|
|
|
|
node = xmalloc(sizeof(struct node_op2));
|
|
node->type = NODE_OP2;
|
|
((struct node_op2 *)node)->opclass = op2_class;
|
|
((struct node_op2 *)node)->args[0] = last;
|
|
((struct node_op2 *)node)->args[1] = op2_right;
|
|
|
|
return node;
|
|
}
|
|
|
|
default:
|
|
warnx("unexpected token type: %d", t->type);
|
|
token_free(t);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
static struct node *dparser_compile(struct parser *parser)
|
|
{
|
|
struct node *node = NULL;
|
|
|
|
while (true) {
|
|
struct node *node0 = dparser_compile1(parser, node);
|
|
if (GOT_ERROR(parser)) {
|
|
node_free(node);
|
|
return NULL;
|
|
}
|
|
|
|
if (node == node0) {
|
|
if (node == NULL)
|
|
strncpy(parser->errmsg,
|
|
_("error: empty filter expression"),
|
|
ERRMSG_LEN - 1);
|
|
return node;
|
|
}
|
|
node = node0;
|
|
}
|
|
}
|
|
|
|
static struct token *token_new(void)
|
|
{
|
|
return xcalloc(1, sizeof(struct token));
|
|
}
|
|
|
|
static void token_free(struct token *token)
|
|
{
|
|
if (TOKEN_CLASS(token)->free)
|
|
TOKEN_CLASS(token)->free(token);
|
|
free(token);
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
static void token_dump(struct token *token, FILE *stream)
|
|
{
|
|
fprintf(stream, "<%s>", TOKEN_CLASS(token)->name);
|
|
if (TOKEN_CLASS(token)->dump)
|
|
TOKEN_CLASS(token)->dump(token, stream);
|
|
fputc('\n', stream);
|
|
}
|
|
#endif /* DEBUG */
|
|
|
|
static void token_free_str(struct token *token)
|
|
{
|
|
free(token->val.str);
|
|
}
|
|
|
|
static void token_dump_str(struct token *token, FILE *stream)
|
|
{
|
|
fputs(token->val.str, stream);
|
|
}
|
|
|
|
static void token_dump_num(struct token *token, FILE *stream)
|
|
{
|
|
fprintf(stream, "%llu", token->val.num);
|
|
}
|
|
|
|
static void token_dump_op1(struct token *token, FILE *stream)
|
|
{
|
|
fputs(TOKEN_OP1_CLASS(token)->name, stream);
|
|
}
|
|
|
|
static void token_dump_op2(struct token *token, FILE *stream)
|
|
{
|
|
fputs(TOKEN_OP2_CLASS(token)->name, stream);
|
|
}
|
|
|
|
static struct node *node_val_new(enum node_type type, int pindex)
|
|
{
|
|
struct node *node = xmalloc(sizeof(struct node_val));
|
|
node->type = type;
|
|
PINDEX(node) = pindex;
|
|
return node;
|
|
}
|
|
|
|
static void node_free(struct node *node)
|
|
{
|
|
if (node == NULL)
|
|
return;
|
|
if (NODE_CLASS(node)->free)
|
|
NODE_CLASS(node)->free(node);
|
|
free(node);
|
|
}
|
|
|
|
static bool node_apply(struct node *node, struct parameter *params, struct libscols_line *ln)
|
|
{
|
|
if (!node)
|
|
return true;
|
|
|
|
switch (node->type) {
|
|
case NODE_OP1: {
|
|
struct node_op1 *node_op1 = (struct node_op1*)node;
|
|
return node_op1->opclass->is_acceptable(node_op1->arg, params, ln);
|
|
}
|
|
case NODE_OP2: {
|
|
struct node_op2 *node_op2 = (struct node_op2*)node;
|
|
return node_op2->opclass->is_acceptable(node_op2->args[0], node_op2->args[1], params, ln);
|
|
}
|
|
case NODE_BOOL:
|
|
if (PINDEX(node) < 0)
|
|
return VAL(node,boolean);
|
|
|
|
if (!params[PINDEX(node)].has_value) {
|
|
const char *data = scols_line_get_column_data(ln, params[PINDEX(node)].cl);
|
|
if (data == NULL)
|
|
return false;
|
|
params[PINDEX(node)].val.boolean = !*data ? false :
|
|
*data == '0' ? false :
|
|
*data == 'N' || *data == 'n' ? false : true;
|
|
params[PINDEX(node)].has_value = true;
|
|
}
|
|
return params[PINDEX(node)].val.boolean;
|
|
default:
|
|
warnx(_("unexpected type in filter application: %s"), NODE_CLASS(node)->name);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static void node_dump(struct node *node, struct parameter *param, int depth, FILE *stream)
|
|
{
|
|
int i;
|
|
|
|
if (!node)
|
|
return;
|
|
|
|
for (i = 0; i < depth; i++)
|
|
fputc(' ', stream);
|
|
fputs(NODE_CLASS(node)->name, stream);
|
|
if (NODE_CLASS(node)->dump)
|
|
NODE_CLASS(node)->dump(node, param, depth, stream);
|
|
}
|
|
|
|
static void node_str_dump(struct node *node, struct parameter* params, int depth __attribute__((__unused__)), FILE *stream)
|
|
{
|
|
if (PINDEX(node) >= 0)
|
|
fprintf(stream, ": |%s|\n", scols_column_get_name(params[PINDEX(node)].cl));
|
|
else
|
|
fprintf(stream, ": '%s'\n", VAL(node,str));
|
|
}
|
|
|
|
static void node_num_dump(struct node *node, struct parameter* params, int depth __attribute__((__unused__)), FILE *stream)
|
|
{
|
|
if (PINDEX(node) >= 0)
|
|
fprintf(stream, ": |%s|\n", scols_column_get_name(params[PINDEX(node)].cl));
|
|
else
|
|
fprintf(stream, ": %llu\n", VAL(node,num));
|
|
}
|
|
|
|
static void node_bool_dump(struct node *node, struct parameter* params, int depth __attribute__((__unused__)), FILE *stream)
|
|
{
|
|
if (PINDEX(node) >= 0)
|
|
fprintf(stream, ": |%s|\n", scols_column_get_name(params[PINDEX(node)].cl));
|
|
else
|
|
fprintf(stream, ": %s\n",
|
|
VAL(node,boolean)
|
|
? token_classes[TOKEN_TRUE].name
|
|
: token_classes[TOKEN_FALSE].name);
|
|
}
|
|
|
|
static void node_re_dump(struct node *node, struct parameter* params __attribute__((__unused__)),
|
|
int depth __attribute__((__unused__)), FILE *stream)
|
|
{
|
|
fprintf(stream, ": #<regexp %p>\n", &VAL(node,re));
|
|
}
|
|
|
|
static void node_op1_dump(struct node *node, struct parameter* params, int depth, FILE *stream)
|
|
{
|
|
fprintf(stream, ": %s\n", ((struct node_op1 *)node)->opclass->name);
|
|
node_dump(((struct node_op1 *)node)->arg, params, depth + 4, stream);
|
|
}
|
|
|
|
static void node_op2_dump(struct node *node, struct parameter* params, int depth, FILE *stream)
|
|
{
|
|
int i;
|
|
|
|
fprintf(stream, ": %s\n", ((struct node_op2 *)node)->opclass->name);
|
|
for (i = 0; i < 2; i++)
|
|
node_dump(((struct node_op2 *)node)->args[i], params, depth + 4, stream);
|
|
}
|
|
|
|
static void node_str_free(struct node *node)
|
|
{
|
|
if (PINDEX(node) < 0)
|
|
free(VAL(node,str));
|
|
}
|
|
|
|
static void node_re_free(struct node *node)
|
|
{
|
|
regfree(&VAL(node,re));
|
|
}
|
|
|
|
static void node_op1_free(struct node *node)
|
|
{
|
|
node_free(((struct node_op1 *)node)->arg);
|
|
}
|
|
|
|
static void node_op2_free(struct node *node)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < 2; i++)
|
|
node_free(((struct node_op2 *)node)->args[i]);
|
|
}
|
|
|
|
static bool op1_not(struct node *node, struct parameter* params, struct libscols_line * ln)
|
|
{
|
|
return !node_apply(node, params, ln);
|
|
}
|
|
|
|
static bool op1_check_type_bool_or_op(struct parser* parser, struct op1_class *op1_class,
|
|
struct node *node)
|
|
{
|
|
if (! (node->type == NODE_OP1 || node->type == NODE_OP2 || node->type == NODE_BOOL)) {
|
|
snprintf(parser->errmsg, ERRMSG_LEN,
|
|
_("error: unexpected operand type %s for: %s"),
|
|
NODE_CLASS(node)->name,
|
|
op1_class->name);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
#define OP2_GET_STR(NODE,DEST) do { \
|
|
int pindex = PINDEX(NODE); \
|
|
if (pindex < 0) \
|
|
DEST = VAL(NODE,str); \
|
|
else { \
|
|
struct parameter *p = params + pindex; \
|
|
if (!p->has_value) { \
|
|
p->val.str = scols_line_get_column_data(ln, p->cl); \
|
|
if (p->val.str == NULL) return false; \
|
|
p->has_value = true; \
|
|
} \
|
|
DEST = p->val.str; \
|
|
} \
|
|
} while(0)
|
|
|
|
#define OP2_GET_NUM(NODE,DEST) do { \
|
|
int pindex = PINDEX(NODE); \
|
|
if (pindex < 0) \
|
|
DEST = VAL(NODE,num); \
|
|
else { \
|
|
struct parameter *p = params + pindex; \
|
|
if (!p->has_value) { \
|
|
const char *tmp = scols_line_get_column_data(ln, p->cl); \
|
|
if (tmp == NULL) return false; \
|
|
p->val.num = strtoull(tmp, NULL, 10); \
|
|
p->has_value = true; \
|
|
} \
|
|
DEST = p->val.num; \
|
|
} \
|
|
} while(0)
|
|
|
|
#define OP2_EQ_BODY(OP,ELSEVAL) do { \
|
|
if (left->type == NODE_STR) { \
|
|
const char *lv, *rv; \
|
|
OP2_GET_STR(left,lv); \
|
|
OP2_GET_STR(right,rv); \
|
|
return strcmp(lv, rv) OP 0; \
|
|
} else if (left->type == NODE_NUM) { \
|
|
unsigned long long lv, rv; \
|
|
OP2_GET_NUM(left,lv); \
|
|
OP2_GET_NUM(right,rv); \
|
|
return lv OP rv; \
|
|
} else { \
|
|
return node_apply(left, params, ln) OP node_apply(right, params, ln); \
|
|
} \
|
|
} while(0)
|
|
|
|
#define OP2_CMP_BODY(OP) do { \
|
|
unsigned long long lv, rv; \
|
|
OP2_GET_NUM(left,lv); \
|
|
OP2_GET_NUM(right,rv); \
|
|
return (lv OP rv); \
|
|
} while(0)
|
|
static bool op2_eq(struct node *left, struct node *right, struct parameter *params, struct libscols_line *ln)
|
|
{
|
|
OP2_EQ_BODY(==, false);
|
|
}
|
|
|
|
static bool op2_ne(struct node *left, struct node *right, struct parameter *params, struct libscols_line *ln)
|
|
{
|
|
OP2_EQ_BODY(!=, true);
|
|
}
|
|
|
|
static bool op2_and(struct node *left, struct node *right, struct parameter *params, struct libscols_line *ln)
|
|
{
|
|
return node_apply(left, params, ln) && node_apply(right, params, ln);
|
|
}
|
|
|
|
static bool op2_or(struct node *left, struct node *right, struct parameter *params, struct libscols_line *ln)
|
|
{
|
|
return node_apply(left, params, ln) || node_apply(right, params, ln);
|
|
}
|
|
|
|
static bool op2_lt(struct node *left, struct node *right, struct parameter *params, struct libscols_line *ln)
|
|
{
|
|
OP2_CMP_BODY(<);
|
|
}
|
|
|
|
static bool op2_le(struct node *left, struct node *right, struct parameter *params, struct libscols_line *ln)
|
|
{
|
|
OP2_CMP_BODY(<=);
|
|
}
|
|
|
|
static bool op2_gt(struct node *left, struct node *right, struct parameter *params, struct libscols_line *ln)
|
|
{
|
|
OP2_CMP_BODY(>);
|
|
}
|
|
|
|
static bool op2_ge(struct node *left, struct node *right, struct parameter *params, struct libscols_line *ln)
|
|
{
|
|
OP2_CMP_BODY(>=);
|
|
}
|
|
|
|
static bool op2_re_match(struct node *left, struct node *right,
|
|
struct parameter *params, struct libscols_line *ln)
|
|
{
|
|
const char *str;
|
|
OP2_GET_STR(left, str);
|
|
|
|
return (regexec(&VAL(right,re), str, 0, NULL, 0) == 0);
|
|
}
|
|
|
|
static bool op2_re_unmatch(struct node *left, struct node *right,
|
|
struct parameter *params, struct libscols_line *ln)
|
|
{
|
|
return !op2_re_match(left, right, params, ln);
|
|
}
|
|
|
|
static bool op2_check_type_boolean_or_op(struct parser* parser, struct op2_class *op2_class,
|
|
struct node *left, struct node *right)
|
|
{
|
|
enum node_type lt = left->type, rt = right->type;
|
|
|
|
if (!(lt == NODE_OP1 || lt == NODE_OP2 || lt == NODE_BOOL)) {
|
|
snprintf(parser->errmsg, ERRMSG_LEN,
|
|
_("error: unexpected left operand type %s for: %s"),
|
|
NODE_CLASS(left)->name,
|
|
op2_class->name);
|
|
return false;
|
|
}
|
|
|
|
if (! (rt == NODE_OP1 || rt == NODE_OP2 || rt == NODE_BOOL)) {
|
|
snprintf(parser->errmsg, ERRMSG_LEN,
|
|
_("error: unexpected right operand type %s for: %s"),
|
|
NODE_CLASS(right)->name,
|
|
op2_class->name);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool op2_check_type_eq_or_bool_or_op(struct parser* parser, struct op2_class *op2_class,
|
|
struct node *left, struct node *right)
|
|
{
|
|
enum node_type lt = left->type, rt = right->type;
|
|
|
|
if (lt == rt)
|
|
return true;
|
|
|
|
return op2_check_type_boolean_or_op(parser, op2_class, left, right);
|
|
}
|
|
|
|
static bool op2_check_type_num(struct parser* parser, struct op2_class *op2_class,
|
|
struct node *left, struct node *right)
|
|
{
|
|
if (left->type != NODE_NUM) {
|
|
snprintf(parser->errmsg, ERRMSG_LEN,
|
|
_("error: unexpected left operand type %s for: %s"),
|
|
NODE_CLASS(left)->name,
|
|
op2_class->name);
|
|
return false;
|
|
}
|
|
|
|
if (right->type != NODE_NUM) {
|
|
snprintf(parser->errmsg, ERRMSG_LEN,
|
|
_("error: unexpected right operand type %s for: %s"),
|
|
NODE_CLASS(right)->name,
|
|
op2_class->name);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool op2_check_type_re(struct parser* parser, struct op2_class *op2_class,
|
|
struct node *left, struct node *right)
|
|
{
|
|
if (left->type != NODE_STR) {
|
|
snprintf(parser->errmsg, ERRMSG_LEN,
|
|
_("error: unexpected left operand type %s for: %s"),
|
|
NODE_CLASS(left)->name,
|
|
op2_class->name);
|
|
return false;
|
|
}
|
|
|
|
if (right->type != NODE_STR) {
|
|
snprintf(parser->errmsg, ERRMSG_LEN,
|
|
_("error: unexpected right operand type %s for: %s"),
|
|
NODE_CLASS(right)->name,
|
|
op2_class->name);
|
|
return false;
|
|
}
|
|
if (PINDEX(right) >= 0) {
|
|
snprintf(parser->errmsg, ERRMSG_LEN,
|
|
_("error: string literal is expected as right operand for: %s"),
|
|
op2_class->name);
|
|
return false;
|
|
}
|
|
|
|
char *regex = VAL(right, str);
|
|
VAL(right, str) = NULL;
|
|
|
|
int err = regcomp(&VAL(right, re), regex, REG_NOSUB | REG_EXTENDED);
|
|
if (err != 0) {
|
|
size_t size = regerror(err, &VAL(right, re), NULL, 0);
|
|
char *buf = xmalloc(size + 1);
|
|
|
|
regerror(err, &VAL(right, re), buf, size);
|
|
|
|
snprintf(parser->errmsg, ERRMSG_LEN,
|
|
_("error: could not compile regular expression %s: %s"),
|
|
regex, buf);
|
|
free(buf);
|
|
return false;
|
|
}
|
|
right->type = NODE_RE;
|
|
free(regex);
|
|
return true;
|
|
}
|
|
|
|
struct lsfd_filter *lsfd_filter_new(const char *const expr, struct libscols_table *tb,
|
|
int ncols,
|
|
int (*column_name_to_id)(const char *, void *),
|
|
struct libscols_column *(*add_column_by_id)(struct libscols_table *, int, void*),
|
|
void *data)
|
|
{
|
|
struct parser parser;
|
|
int i;
|
|
struct node *node;
|
|
struct lsfd_filter *filter;
|
|
|
|
parser_init(&parser, expr, tb, ncols,
|
|
column_name_to_id,
|
|
add_column_by_id,
|
|
data);
|
|
|
|
node = dparser_compile(&parser);
|
|
|
|
filter = xmalloc(sizeof(struct lsfd_filter));
|
|
filter->errmsg[0] = '\0';
|
|
if (GOT_ERROR(&parser)) {
|
|
strcpy(filter->errmsg, parser.errmsg);
|
|
return filter;
|
|
}
|
|
assert(node);
|
|
if (parser.paren_level > 0) {
|
|
node_free(node);
|
|
strncpy(filter->errmsg, _("error: unbalanced parenthesis: ("), ERRMSG_LEN - 1);
|
|
return filter;
|
|
}
|
|
if (*parser.cursor != '\0') {
|
|
node_free(node);
|
|
snprintf(filter->errmsg, ERRMSG_LEN,
|
|
_("error: garbage at the end of expression: %s"), parser.cursor);
|
|
return filter;
|
|
}
|
|
if (node->type == NODE_STR || node->type == NODE_NUM) {
|
|
node_free(node);
|
|
snprintf(filter->errmsg, ERRMSG_LEN,
|
|
_("error: bool expression is expected: %s"), expr);
|
|
return filter;
|
|
}
|
|
|
|
filter->table = tb;
|
|
scols_ref_table(filter->table);
|
|
filter->node = node;
|
|
filter->parameters = parser.parameters;
|
|
filter->nparams = ncols;
|
|
for (i = 0; i < filter->nparams; i++) {
|
|
if (filter->parameters[i].cl)
|
|
scols_ref_column(filter->parameters[i].cl);
|
|
}
|
|
return filter;
|
|
}
|
|
|
|
const char *lsfd_filter_get_errmsg(struct lsfd_filter *filter)
|
|
{
|
|
if (GOT_ERROR(filter))
|
|
return filter->errmsg;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void lsfd_filter_dump(struct lsfd_filter *filter, FILE *stream)
|
|
{
|
|
if (!filter) {
|
|
fputs("EMPTY\n", stream);
|
|
return;
|
|
}
|
|
|
|
if (GOT_ERROR(filter)) {
|
|
fprintf(stream, "ERROR: %s\n", filter->errmsg);
|
|
return;
|
|
}
|
|
|
|
node_dump(filter->node, filter->parameters, 0, stream);
|
|
}
|
|
|
|
void lsfd_filter_free(struct lsfd_filter *filter)
|
|
{
|
|
int i;
|
|
|
|
if (!filter)
|
|
return;
|
|
|
|
if (!GOT_ERROR(filter)) {
|
|
for (i = 0; i < filter->nparams; i++) {
|
|
if (filter->parameters[i].cl)
|
|
scols_unref_column(filter->parameters[i].cl);
|
|
}
|
|
scols_unref_table(filter->table);
|
|
node_free(filter->node);
|
|
}
|
|
free(filter->parameters);
|
|
free(filter);
|
|
}
|
|
|
|
bool lsfd_filter_apply(struct lsfd_filter *filter, struct libscols_line * ln)
|
|
{
|
|
int i;
|
|
|
|
if (!filter)
|
|
return true;
|
|
|
|
if (GOT_ERROR(filter))
|
|
return false;
|
|
|
|
for (i = 0; i < filter->nparams; i++)
|
|
filter->parameters[i].has_value = false;
|
|
|
|
return node_apply(filter->node, filter->parameters, ln);
|
|
}
|