summaryrefslogtreecommitdiffhomepage
path: root/main.c
diff options
context:
space:
mode:
authorJo-Philipp Wich <jo@mein.io>2020-08-10 17:05:03 +0200
committerJo-Philipp Wich <jo@mein.io>2020-08-21 23:04:45 +0200
commita56887df2a0f51b42d9d4013515e847b1a050c58 (patch)
tree3416726feacc13d8a94e4fda90edc1a6773fa71e /main.c
Initial commit
Signed-off-by: Jo-Philipp Wich <jo@mein.io>
Diffstat (limited to 'main.c')
-rw-r--r--main.c406
1 files changed, 406 insertions, 0 deletions
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..481c05e
--- /dev/null
+++ b/main.c
@@ -0,0 +1,406 @@
+/*
+ * Copyright (C) 2020 Jo-Philipp Wich <jo@mein.io>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/stat.h>
+
+#ifdef JSONC
+ #include <json.h>
+#else
+ #include <json-c/json.h>
+#endif
+
+#include <libubox/list.h>
+#include <libubox/utils.h>
+
+#include "lexer.h"
+#include "parser.h"
+#include "eval.h"
+
+
+struct match_item {
+ struct json_object *jsobj;
+ char *name;
+ struct list_head list;
+};
+
+static void
+print_usage(char *app)
+{
+ printf(
+ "== Usage ==\n\n"
+ " # %s [-a] [-i <file> | -s \"json...\"] {-t <pattern> | -e <pattern>}\n"
+ " -q Quiet, no errors are printed\n"
+ " -h, --help Print this help\n"
+ " -a Implicitely treat input as array, useful for JSON logs\n"
+ " -i path Specify a JSON file to parse\n"
+ " -s \"json\" Specify a JSON string to parse\n"
+ " -l limit Specify max number of results to show\n"
+ " -F separator Specify a field separator when using export\n"
+ " -t <pattern> Print the type of values matched by pattern\n"
+ " -e <pattern> Print the values matched by pattern\n"
+ " -e VAR=<pat> Serialize matched value for shell \"eval\"\n\n"
+ "== Patterns ==\n\n"
+ " Patterns are JsonPath: http://goessner.net/articles/JsonPath/\n"
+ " This tool implements $, @, [], * and the union operator ','\n"
+ " plus the usual expressions and literals.\n"
+ " It does not support the recursive child search operator '..' or\n"
+ " the '?()' and '()' filter expressions as those would require a\n"
+ " complete JavaScript engine to support them.\n\n"
+ "== Examples ==\n\n"
+ " Display the first IPv4 address on lan:\n"
+ " # ifstatus lan | %s -e '@[\"ipv4-address\"][0].address'\n\n"
+ " Extract the release string from the board information:\n"
+ " # ubus call system board | %s -e '@.release.description'\n\n"
+ " Find all interfaces which are up:\n"
+ " # ubus call network.interface dump | \\\n"
+ " %s -e '@.interface[@.up=true].interface'\n\n"
+ " Export br-lan traffic counters for shell eval:\n"
+ " # devstatus br-lan | %s -e 'RX=@.statistics.rx_bytes' \\\n"
+ " -e 'TX=@.statistics.tx_bytes'\n",
+ app, app, app, app, app);
+}
+
+static void
+print_error_context(const char *expr, size_t off)
+{
+ int eoff, eline, padlen;
+ const char *p, *nl;
+ int i;
+
+ /* skip lines until error line */
+ for (p = nl = expr, eline = 0; *p && p < expr + off; p++) {
+ if (*p == '\n') {
+ nl = p + 1;
+ eline++;
+ }
+ }
+
+ eoff = p - nl;
+
+ fprintf(stderr, "In line %u, byte %d:\n\n `", eline + 1, eoff);
+
+ for (p = nl, padlen = 0; *p != '\n' && *p != '\0'; p++) {
+ switch (*p) {
+ case '\t':
+ fprintf(stderr, " ");
+ if (p < nl + eoff)
+ padlen += 4;
+ break;
+
+ case '\r':
+ case '\v':
+ fprintf(stderr, " ");
+ if (p < nl + eoff)
+ padlen++;
+ break;
+
+ default:
+ fprintf(stderr, "%c", *p);
+ if (p < nl + eoff)
+ padlen++;
+ }
+ }
+
+ fprintf(stderr, "`\n ");
+
+ if (padlen < strlen("Near here ^")) {
+ for (i = 0; i < padlen; i++)
+ fprintf(stderr, " ");
+
+ fprintf(stderr, "^-- Near here\n");
+ }
+ else {
+ fprintf(stderr, "Near here ");
+
+ for (i = strlen("Near here "); i < padlen; i++)
+ fprintf(stderr, "-");
+
+ fprintf(stderr, "^\n");
+ }
+
+ fprintf(stderr, "\n");
+}
+
+static void
+print_error(struct ut_state *state, const char *expr)
+{
+ size_t off = state ? state->off : 0;
+ struct ut_opcode *tag;
+ bool first = true;
+ int i, max_i;
+
+ switch (state ? state->error.code : UT_ERROR_OUT_OF_MEMORY) {
+ case UT_ERROR_NO_ERROR:
+ return;
+
+ case UT_ERROR_OUT_OF_MEMORY:
+ fprintf(stderr, "Runtime error: Out of memory\n");
+ break;
+
+ case UT_ERROR_UNTERMINATED_COMMENT:
+ fprintf(stderr, "Syntax error: Unterminated comment\n");
+ break;
+
+ case UT_ERROR_UNTERMINATED_STRING:
+ fprintf(stderr, "Syntax error: Unterminated string\n");
+ break;
+
+ case UT_ERROR_UNTERMINATED_BLOCK:
+ fprintf(stderr, "Syntax error: Unterminated template block\n");
+ break;
+
+ case UT_ERROR_UNEXPECTED_CHAR:
+ fprintf(stderr, "Syntax error: Unexpected character\n");
+ break;
+
+ case UT_ERROR_OVERLONG_STRING:
+ fprintf(stderr, "Syntax error: String or label literal too long\n");
+ break;
+
+ case UT_ERROR_INVALID_ESCAPE:
+ fprintf(stderr, "Syntax error: Invalid escape sequence\n");
+ break;
+
+ case UT_ERROR_NESTED_BLOCKS:
+ fprintf(stderr, "Syntax error: Template blocks may not be nested\n");
+ break;
+
+ case UT_ERROR_UNEXPECTED_TOKEN:
+ fprintf(stderr, "Syntax error: Unexpected token\n");
+
+ for (i = 0, max_i = 0; i < sizeof(state->error.info.tokens) * 8; i++)
+ if ((state->error.info.tokens[i / 64] & ((unsigned)1 << (i % 64))) && tokennames[i])
+ max_i = i;
+
+ for (i = 0; i < sizeof(state->error.info.tokens) * 8; i++) {
+ if ((state->error.info.tokens[i / 64] & ((unsigned)1 << (i % 64))) && tokennames[i]) {
+ if (first) {
+ fprintf(stderr, "Expecting %s", tokennames[i]);
+ first = false;
+ }
+ else if (i < max_i) {
+ fprintf(stderr, ", %s", tokennames[i]);
+ }
+ else {
+ fprintf(stderr, " or %s", tokennames[i]);
+ }
+ }
+ }
+
+ fprintf(stderr, "\n");
+ break;
+
+ case UT_ERROR_EXCEPTION:
+ tag = json_object_get_userdata(state->error.info.exception);
+ off = (tag && tag->operand[0]) ? tag->operand[0]->off : 0;
+
+ fprintf(stderr, "%s\n", json_object_get_string(state->error.info.exception));
+ break;
+ }
+
+ if (off)
+ print_error_context(expr, off);
+}
+
+#ifndef NDEBUG
+static void dump(struct ut_opcode *op, int level);
+
+static void dump_node(struct ut_opcode *op) {
+ const char *p;
+
+ switch (op->type) {
+ case T_NUMBER:
+ printf("n%p [label=\"%"PRId64"\"];\n", op, json_object_get_int64(op->val));
+ break;
+
+ case T_DOUBLE:
+ printf("n%p [label=\"%f\"];\n", op, json_object_get_double(op->val));
+ break;
+
+ case T_BOOL:
+ printf("n%p [label=\"%s\"];\n", op, json_object_get_boolean(op->val) ? "true" : "false");
+ break;
+
+ case T_STRING:
+ case T_LABEL:
+ case T_TEXT:
+ printf("n%p [label=\"%s<", op, tokennames[op->type]);
+
+ for (p = json_object_get_string(op->val); *p; p++)
+ switch (*p) {
+ case '\n':
+ printf("\\\n");
+ break;
+
+ case '\t':
+ printf("\\\t");
+ break;
+
+ case '"':
+ printf("\\\"");
+ break;
+
+ default:
+ printf("%c", *p);
+ }
+
+ printf(">\"];\n");
+ break;
+
+ default:
+ printf("n%p [label=\"%s\"];\n", op, tokennames[op->type]);
+ }
+}
+
+static void dump(struct ut_opcode *op, int level) {
+ struct ut_opcode *prev, *cur;
+ int i;
+
+ if (level == 0) {
+ printf("digraph G {\nmain [shape=box];\n");
+ }
+
+ for (prev = NULL, cur = op; cur; prev = cur, cur = cur->sibling) {
+ dump_node(cur);
+
+ for (i = 0; i < sizeof(cur->operand) / sizeof(cur->operand[0]); i++) {
+ if (cur->operand[i]) {
+ dump(cur->operand[i], level + 1);
+ printf("n%p -> n%p [label=\"op%d\"];\n", cur, cur->operand[i], i + 1);
+ }
+ }
+
+ if (prev)
+ printf("n%p -> n%p [style=dotted];\n", prev, cur);
+ }
+
+ if (level == 0) {
+ printf("main -> n%p [style=dotted];\n", op);
+
+ printf("}\n");
+ }
+}
+#endif /* NDEBUG */
+
+static enum ut_error_type
+parse(const char *source, bool dumponly)
+{
+ struct ut_state *state = calloc(1, sizeof(*state));
+ enum ut_error_type err;
+
+ err = ut_parse(state, source);
+
+ if (!err) {
+ if (dumponly) {
+#ifdef NDEBUG
+ fprintf(stderr, "Debug support not compiled in\n");
+ err = UT_ERROR_EXCEPTION;
+#else /* NDEBUG */
+ dump(state->main, 0);
+#endif /* NDEBUG */
+ }
+ else {
+ err = ut_run(state);
+ }
+ }
+
+ if (err)
+ print_error(state, source);
+
+ ut_free(state);
+
+ return err;
+}
+
+int
+main(int argc, char **argv)
+{
+ size_t rlen, tlen = 0;
+ bool dumponly = false;
+ char buf[1024], *tmp;
+ char *source = NULL;
+ FILE *input = stdin;
+ int opt, rv = 0;
+
+ if (argc == 1)
+ {
+ print_usage(argv[0]);
+ goto out;
+ }
+
+ while ((opt = getopt(argc, argv, "dhi:s:")) != -1)
+ {
+ switch (opt) {
+ case 'h':
+ print_usage(argv[0]);
+ goto out;
+
+ case 'i':
+ input = fopen(optarg, "r");
+
+ if (!input) {
+ fprintf(stderr, "Failed to open %s: %s\n", optarg, strerror(errno));
+ rv = UT_ERROR_EXCEPTION;
+ goto out;
+ }
+
+ break;
+
+ case 'd':
+ dumponly = true;
+ break;
+
+ case 's':
+ source = optarg;
+ break;
+ }
+ }
+
+ if (!source) {
+ while (1) {
+ rlen = fread(buf, 1, sizeof(buf), input);
+
+ if (rlen == 0)
+ break;
+
+ tmp = realloc(source, tlen + rlen + 1);
+
+ if (!tmp) {
+ print_error(NULL, "");
+ rv = UT_ERROR_OUT_OF_MEMORY;
+ goto out;
+ }
+
+ source = tmp;
+ memcpy(source + tlen, buf, rlen);
+ source[tlen + rlen] = 0;
+ tlen += rlen;
+ }
+ }
+
+ rv = parse(source, dumponly);
+
+out:
+ if (input && input != stdin)
+ fclose(input);
+
+ return rv;
+}