From 768988d88470ffc1c64c35d6f9d3c37a9a6f75da Mon Sep 17 00:00:00 2001 From: Daniel Silverstone Date: Sun, 11 Jun 2017 13:33:18 +0100 Subject: Simple parser for filter syntax --- include/nslog/nslog.h | 7 ++ src/Makefile | 34 +++++++++ src/filter-lexer.l | 84 +++++++++++++++++++++ src/filter-parser.y | 197 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/filter.c | 84 +++++++++++++++++++++ test/Makefile | 2 +- test/basictests.c | 43 +++++++++-- test/parse.c | 25 +++++++ 8 files changed, 468 insertions(+), 8 deletions(-) create mode 100644 src/filter-lexer.l create mode 100644 src/filter-parser.y create mode 100644 test/parse.c diff --git a/include/nslog/nslog.h b/include/nslog/nslog.h index fc3ead4..10b90a8 100644 --- a/include/nslog/nslog.h +++ b/include/nslog/nslog.h @@ -30,6 +30,7 @@ typedef enum { const char *nslog_level_name(nslog_level level); #define NSLOG_LEVEL_DD NSLOG_LEVEL_DEEPDEBUG +#define NSLOG_LEVEL_DBG NSLOG_LEVEL_DEBUG #define NSLOG_LEVEL_CHAT NSLOG_LEVEL_VERBOSE #define NSLOG_LEVEL_WARN NSLOG_LEVEL_WARNING #define NSLOG_LEVEL_ERR NSLOG_LEVEL_ERROR @@ -105,6 +106,7 @@ typedef enum { NSLOG_NO_ERROR = 0, NSLOG_NO_MEMORY = 1, NSLOG_UNCORKED = 2, + NSLOG_PARSE_ERROR = 3, } nslog_error; typedef void (*nslog_callback)(void *context, nslog_entry_context_t *ctx, @@ -145,4 +147,9 @@ nslog_filter_t *nslog_filter_unref(nslog_filter_t *filter); nslog_error nslog_filter_set_active(nslog_filter_t *filter, nslog_filter_t **prev); +char *nslog_filter_sprintf(nslog_filter_t *filter); + +nslog_error nslog_filter_from_text(const char *input, + nslog_filter_t **output); + #endif /* NSLOG_NSLOG_H_ */ diff --git a/src/Makefile b/src/Makefile index 7fbad1c..3ca70cd 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,3 +1,37 @@ DIR_SOURCES := core.c filter.c +CFLAGS := $(CFLAGS) -I$(BUILDDIR) -Isrc/ + +SOURCES := $(SOURCES) $(BUILDDIR)/filter-parser.c $(BUILDDIR)/filter-lexer.c + +$(BUILDDIR)/%-lexer.c $(BUILDDIR)/%-lexer.h: src/%-lexer.l + $(VQ)$(ECHO) " FLEX: $<" + $(Q)$(FLEX) --outfile=$(BUILDDIR)/$(*F)-lexer.c --header-file=$(BUILDDIR)/$(*F)-lexer.h $< + +$(BUILDDIR)/%-lexer.c: $(BUILDDIR)/%-parser.h + +# Bison 3.0 and later require api.prefix in curly braces +# Bison 2.6 and later require api.prefix +# Bison 2.5 and earlier require name-prefix switch +bisonvsn := $(word 4,$(shell $(BISON) --version)) +bisonmaj := $(word 1,$(subst ., ,$(bisonvsn))) +bisonmin := $(word 2,$(subst ., ,$(bisonvsn))) +ifeq ($(bisonmaj),1) + BISON_DEFINES = --name-prefix=$(*F)_ +else + ifeq ($(bisonmaj),2) + ifneq ($(findstring $(bisonmin),"0 1 2 3 4 5"),) + BISON_DEFINES = --name-prefix=$(*F)_ + else + BISON_DEFINES = --define=api.prefix=$(*F)_ + endif + else + BISON_DEFINES = --define=api.prefix={$(*F)_} + endif +endif + +$(BUILDDIR)/%-parser.c $(BUILDDIR)/%-parser.h: src/%-parser.y + $(VQ)$(ECHO) " BISON: $<" + $(Q)$(BISON) -d -t $(BISON_DEFINES) --report=all --output=$(BUILDDIR)/$(*F)-parser.c --defines=$(BUILDDIR)/$(*F)-parser.h $< + include $(NSBUILD)/Makefile.subdir diff --git a/src/filter-lexer.l b/src/filter-lexer.l new file mode 100644 index 0000000..5f75d29 --- /dev/null +++ b/src/filter-lexer.l @@ -0,0 +1,84 @@ +%{ + +/* This is a lexer for libnslog filter syntax + * + * This file is part of libnslog. + * Licensed under the MIT License, + * http://www.opensource.org/licenses/mit-license.php + * Copyright 2017 Daniel Silverstone + * + */ + +#include +#include +#include + +#include "nslog/nslog.h" + +#include "filter-parser.h" + +/* Ensure compatability with bison 2.6 and later */ +#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED && defined FILTER_STYPE_IS_DECLARED +#define YYSTYPE FILTER_STYPE +#endif + +#if ! defined YYLTYPE && ! defined YYLTYPE_IS_DECLARED && defined FILTER_LTYPE_IS_DECLARED +#define YYLTYPE FILTER_LTYPE +#endif + +%} + + +/* lexer options */ +%option never-interactive +%option bison-bridge +%option bison-locations +%option warn +%option prefix="filter_" +%option nounput +%option noinput +%option noyywrap + +whitespace [ \t]+ + +pattern [^ \t]+ + +%x st_patt + +%% + +{whitespace} { /* nothing */ } + +level: { return T_LEVEL_SPECIFIER; } +lvl: { return T_LEVEL_SPECIFIER; } + +cat: { BEGIN(st_patt); return T_CATEGORY_SPECIFIER; } +category: { BEGIN(st_patt); return T_CATEGORY_SPECIFIER; } +file: { BEGIN(st_patt); return T_FILENAME_SPECIFIER; } +filename: { BEGIN(st_patt); return T_FILENAME_SPECIFIER; } +dir: { BEGIN(st_patt); return T_DIRNAME_SPECIFIER; } +dirname: { BEGIN(st_patt); return T_DIRNAME_SPECIFIER; } +func: { BEGIN(st_patt); return T_FUNCNAME_SPECIFIER; } +funcname: { BEGIN(st_patt); return T_FUNCNAME_SPECIFIER; } + +"&&" { return T_OP_AND; } +"||" { return T_OP_OR; } + +DEEPDEBUG { yylval->level = NSLOG_LEVEL_DEEPDEBUG; return T_LEVEL; } +DDEBUG { yylval->level = NSLOG_LEVEL_DEEPDEBUG; return T_LEVEL; } +DD { yylval->level = NSLOG_LEVEL_DEEPDEBUG; return T_LEVEL; } +DEBUG { yylval->level = NSLOG_LEVEL_DEBUG; return T_LEVEL; } +DBG { yylval->level = NSLOG_LEVEL_DEBUG; return T_LEVEL; } +VERBOSE { yylval->level = NSLOG_LEVEL_VERBOSE; return T_LEVEL; } +CHAT { yylval->level = NSLOG_LEVEL_VERBOSE; return T_LEVEL; } +INFO { yylval->level = NSLOG_LEVEL_INFO; return T_LEVEL; } +WARNING { yylval->level = NSLOG_LEVEL_WARNING; return T_LEVEL; } +WARN { yylval->level = NSLOG_LEVEL_WARNING; return T_LEVEL; } +ERROR { yylval->level = NSLOG_LEVEL_ERROR; return T_LEVEL; } +ERR { yylval->level = NSLOG_LEVEL_ERROR; return T_LEVEL; } +CRITICAL { yylval->level = NSLOG_LEVEL_CRITICAL; return T_LEVEL; } +CRIT { yylval->level = NSLOG_LEVEL_CRITICAL; return T_LEVEL; } + +{pattern} { yylval->patt = yytext; BEGIN(INITIAL); return T_PATTERN; } + +. { return (int) yytext[0]; } diff --git a/src/filter-parser.y b/src/filter-parser.y new file mode 100644 index 0000000..bb225b6 --- /dev/null +++ b/src/filter-parser.y @@ -0,0 +1,197 @@ +%{ +/* This is a bison parser for libnslog's filter syntax + * + * This file is part of libnslog. + * Licensed under the MIT License, + * http://www.opensource.org/licenses/mit-license.php + * Copyright 2017 Daniel Silverstone + * + */ + +#include "nslog/nslog.h" +#include + +#include "filter-parser.h" +#include "filter-lexer.h" + +static void filter_error(FILTER_LTYPE *loc, nslog_filter_t **output, const char *msg) +{ + (void)loc; + (void)output; + (void)msg; +} + +%} + +%locations +%pure-parser +%parse-param { nslog_filter_t **output } + +%union { + char *patt; + nslog_level level; + nslog_filter_t *filter; +} + +%token T_PATTERN +%token T_LEVEL + +%token T_CATEGORY_SPECIFIER +%token T_FILENAME_SPECIFIER +%token T_LEVEL_SPECIFIER +%token T_DIRNAME_SPECIFIER +%token T_FUNCNAME_SPECIFIER + +%token T_OP_AND +%token T_OP_OR + +%type level_filter +%type category_filter +%type filename_filter +%type dirname_filter +%type funcname_filter +%type basic_filter + +%type and_filter +%type or_filter +%type xor_filter +%type binary_filter +%type not_filter + +%type filter +%type toplevel + +%start toplevel + +%% + + /* + part ::= [^: \t\n]+ +level-name ::= 'DEEPDEBUG' | 'DD' | 'DEBUG' | 'VERBOSE' | 'CHAT' | + 'WARNING' | 'WARN' | 'ERROR' | 'ERR' | 'CRITICAL' | 'CRIT' + +category-filter ::= 'cat:' part +level-filter ::= 'level:' level-name +file-filter ::= 'file:' part +dir-filter ::= 'dir:' dir + +factor ::= category-filter | level-filter | file-filter | dir-filter | + '(' expression ')' + +op ::= '&&' | '||' | '^' | 'and' | 'or' | 'xor' | 'eor' + +term ::= factor {op factor} + +expression ::= term | '!' term + */ + +level_filter: + T_LEVEL_SPECIFIER T_LEVEL + { + assert(nslog_filter_level_new($2, &$$) == NSLOG_NO_ERROR); + } + ; + +category_filter: + T_CATEGORY_SPECIFIER T_PATTERN + { + assert(nslog_filter_category_new($2, &$$) == NSLOG_NO_ERROR); + } + ; + +filename_filter: + T_FILENAME_SPECIFIER T_PATTERN + { + assert(nslog_filter_filename_new($2, &$$) == NSLOG_NO_ERROR); + } + ; + +dirname_filter: + T_DIRNAME_SPECIFIER T_PATTERN + { + assert(nslog_filter_dirname_new($2, &$$) == NSLOG_NO_ERROR); + } + ; + +funcname_filter: + T_FUNCNAME_SPECIFIER T_PATTERN + { + assert(nslog_filter_funcname_new($2, &$$) == NSLOG_NO_ERROR); + } + ; + +basic_filter: + level_filter + | + category_filter + | + filename_filter + | + dirname_filter + | + funcname_filter + ; + +and_filter: + '(' filter T_OP_AND filter ')' + { + assert(nslog_filter_and_new($2, $4, &$$) == NSLOG_NO_ERROR); + nslog_filter_unref($2); + nslog_filter_unref($4); + } + ; + +or_filter: + '(' filter T_OP_OR filter ')' + { + assert(nslog_filter_or_new($2, $4, &$$) == NSLOG_NO_ERROR); + nslog_filter_unref($2); + nslog_filter_unref($4); + } + ; + +xor_filter: + '(' filter '^' filter ')' + { + assert(nslog_filter_xor_new($2, $4, &$$) == NSLOG_NO_ERROR); + nslog_filter_unref($2); + nslog_filter_unref($4); + } + ; + +binary_filter: + and_filter + | + or_filter + | + xor_filter + ; + +not_filter: + '!' filter + { + assert(nslog_filter_not_new($2, &$$) == NSLOG_NO_ERROR); + nslog_filter_unref($2); + } + ; + +filter: + not_filter + | + binary_filter + | + basic_filter + ; + +toplevel: + filter + { + $$ = *output = $1; + } + | + error + { + (void)yylloc; + YYABORT ; + } + ; diff --git a/src/filter.c b/src/filter.c index 1dc4e81..0a478b1 100644 --- a/src/filter.c +++ b/src/filter.c @@ -14,6 +14,19 @@ #include "nslog_internal.h" +#include "filter-parser.h" + +/* Ensure compatability with bison 2.6 and later */ +#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED && defined FILTER_STYPE_IS_DECLARED +#define YYSTYPE FILTER_STYPE +#endif + +#if ! defined YYLTYPE && ! defined YYLTYPE_IS_DECLARED && defined FILTER_LTYPE_IS_DECLARED +#define YYLTYPE FILTER_LTYPE +#endif + +#include "filter-lexer.h" + typedef enum { /* Fundamentals */ NSLFK_CATEGORY = 0, @@ -308,3 +321,74 @@ bool nslog__filter_matches(nslog_entry_context_t *ctx) return true; return _nslog__filter_matches(ctx, nslog__active_filter); } + +char *nslog_filter_sprintf(nslog_filter_t *filter) +{ + char *ret = NULL; + switch (filter->kind) { + case NSLFK_CATEGORY: + ret = calloc(filter->params.str.len + 5, 1); + sprintf(ret, "cat:%s", filter->params.str.ptr); + break; + case NSLFK_LEVEL: { + const char *lvl = nslog_level_name(filter->params.level); + ret = calloc(strlen(lvl) + 5, 1); + sprintf(ret, "lvl:%s", lvl); + break; + } + case NSLFK_FILENAME: + ret = calloc(filter->params.str.len + 6, 1); + sprintf(ret, "file:%s", filter->params.str.ptr); + break; + case NSLFK_DIRNAME: + ret = calloc(filter->params.str.len + 5, 1); + sprintf(ret, "dir:%s", filter->params.str.ptr); + break; + case NSLFK_FUNCNAME: + ret = calloc(filter->params.str.len + 6, 1); + sprintf(ret, "func:%s", filter->params.str.ptr); + break; + case NSLFK_AND: + case NSLFK_OR: + case NSLFK_XOR: { + char *left = nslog_filter_sprintf(filter->params.binary.input1); + char *right = nslog_filter_sprintf(filter->params.binary.input2); + const char *op = + (filter->kind == NSLFK_AND) ? "&&" : + (filter->kind == NSLFK_OR) ? "||" : "^"; + ret = calloc(strlen(left) + strlen(right) + 7, 1); + sprintf(ret, "(%s %s %s)", left, op, right); + free(left); + free(right); + break; + } + case NSLFK_NOT: { + char *input = nslog_filter_sprintf(filter->params.unary_input); + ret = calloc(strlen(input) + 2, 1); + sprintf(ret, "!%s", input); + free(input); + break; + } + default: + assert("Unexpected kind" == NULL); + return strdup("***ERROR***"); + } + return ret; +} + +nslog_error nslog_filter_from_text(const char *input, + nslog_filter_t **output) +{ + int ret; + YY_BUFFER_STATE buffer = filter__scan_string((char *)input); + filter_push_buffer_state(buffer); + ret = filter_parse(output); + filter_lex_destroy(); + switch (ret) { + case 0: + return NSLOG_NO_ERROR; + case 2: + return NSLOG_NO_MEMORY; + } + return NSLOG_PARSE_ERROR; +} diff --git a/test/Makefile b/test/Makefile index 36d5092..5ebac01 100644 --- a/test/Makefile +++ b/test/Makefile @@ -1,3 +1,3 @@ -DIR_TEST_ITEMS := testrunner:testmain.c;basictests.c +DIR_TEST_ITEMS := testrunner:testmain.c;basictests.c parse:parse.c include $(NSBUILD)/Makefile.subdir diff --git a/test/basictests.c b/test/basictests.c index b83ed9f..d74df87 100644 --- a/test/basictests.c +++ b/test/basictests.c @@ -171,7 +171,7 @@ START_TEST (test_nslog_simple_filter_corked_message) "Captured message wasn't correct filename"); fail_unless(strcmp(captured_context.funcname, "test_nslog_simple_filter_corked_message") == 0, "Captured message wasn't correct function name"); - + } END_TEST @@ -198,7 +198,34 @@ START_TEST (test_nslog_simple_filter_uncorked_message) "Captured message wasn't correct filename"); fail_unless(strcmp(captured_context.funcname, "test_nslog_simple_filter_uncorked_message") == 0, "Captured message wasn't correct function name"); - + +} +END_TEST + +START_TEST (test_nslog_basic_filter_sprintf) +{ + char *ct = nslog_filter_sprintf(cat_test); + fail_unless(ct != NULL, "Unable to sprintf"); + fail_unless(strcmp(ct, "cat:test") == 0, + "Printed category test is wrong"); + free(ct); + ct = nslog_filter_sprintf(cat_another); + fail_unless(ct != NULL, "Unable to sprintf"); + fail_unless(strcmp(ct, "cat:another") == 0, + "Printed category another is wrong"); + free(ct); +} +END_TEST + +START_TEST (test_nslog_parse_and_sprintf) +{ + nslog_filter_t *filt; + fail_unless(nslog_filter_from_text("cat:test", &filt) == NSLOG_NO_ERROR, + "Unable to parse cat:test"); + char *ct = nslog_filter_sprintf(filt); + nslog_filter_unref(filt); + fail_unless(strcmp(ct, "cat:test") == 0, + "Printed parsed cat:test not right"); } END_TEST @@ -209,22 +236,24 @@ nslog_basic_suite(SRunner *sr) { Suite *s = suite_create("libnslog: Basic tests"); TCase *tc_basic = NULL; - + tc_basic = tcase_create("Simple log checks, no filters"); - + tcase_add_checked_fixture(tc_basic, with_simple_context_setup, with_simple_context_teardown); tcase_add_test(tc_basic, test_nslog_trivial_corked_message); tcase_add_test(tc_basic, test_nslog_trivial_uncorked_message); suite_add_tcase(s, tc_basic); - + tc_basic = tcase_create("Simple filter checks"); - + tcase_add_checked_fixture(tc_basic, with_simple_filter_context_setup, with_simple_filter_context_teardown); tcase_add_test(tc_basic, test_nslog_simple_filter_corked_message); tcase_add_test(tc_basic, test_nslog_simple_filter_uncorked_message); + tcase_add_test(tc_basic, test_nslog_basic_filter_sprintf); + tcase_add_test(tc_basic, test_nslog_parse_and_sprintf); suite_add_tcase(s, tc_basic); - + srunner_add_suite(sr, s); } diff --git a/test/parse.c b/test/parse.c new file mode 100644 index 0000000..9fca97b --- /dev/null +++ b/test/parse.c @@ -0,0 +1,25 @@ +#include "nslog/nslog.h" + +#include +#include + +int main(int argc, char **argv) +{ + if (argc != 2) { + fprintf(stderr, "usage: parse 'filtertext'\n"); + return 1; + } + nslog_filter_t *filt; + nslog_error err; + + err = nslog_filter_from_text(argv[1], &filt); + if (err != NSLOG_NO_ERROR) { + fprintf(stderr, "Unable to parse.\n"); + return 2; + } + char *ct = nslog_filter_sprintf(filt); + filt = nslog_filter_unref(filt); + printf("%s\n", ct); + free(ct); + return 0; +} -- cgit v1.2.3