--- /dev/null
+Dependencies: autoconf, gnu make, flex, bison, gcc or clang, lopsub
+
+Run
+
+ ./autogen.sh && ./configure && make && sudo make install
+
+to build and install this software.
+
+The configure script checks if all required dependencies are installed
+and prints an error message if one of them is missing.
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0
+
+.SUFFIXES:
+MAKEFLAGS += -Rr
+.ONESHELL:
+.SHELLFLAGS := -ec
+
+RM := rm -f
+MKDIR_P := mkdir -p
+
+ifeq ("$(origin CC)", "default")
+ CC := cc
+endif
+ifeq ("$(origin V)", "command line")
+ SAY =
+else
+ SAY = @echo '$(strip $(1))'
+endif
+
+COPYRIGHT_YEAR := 2018
+LOGLEVELS := LL_DEBUG,LL_INFO,LL_NOTICE,LL_WARNING,LL_ERROR,LL_CRIT,LL_EMERG
+GIT_VERSION := $(shell ./version-gen.sh)
+cc_version := $(shell $(CC) --version | head -n 1)
+build_date := $(shell date)
+uname_rs := $(shell uname -rs)
+
+all := tfortune tfortune.1
+all: $(all)
+
+deps := txp.bison.d txp.flex.d ast.d tfortune.d util.d txp.flex.d \
+ tfortune.lsg.d version.d
+
+ifeq ($(findstring clean, $(MAKECMDGOALS)),)
+-include $(deps)
+include config.mak
+endif
+
+.PRECIOUS: %.flex.c %.bison.c %.bison.h %.lsg.h %.lsg.c %.lsg.h
+
+# created by version-gen.sh
+version.c:
+
+%.lsg.c: %.suite
+ $(call SAY, LSGC $<)
+ $(LOPSUBGEN) --gen-c < $<
+
+%.lsg.h: %.suite
+ $(call SAY, LSGH $<)
+ $(LOPSUBGEN) --gen-header < $<
+
+%.1: %.suite
+ $(call SAY, LSGM $<)
+ $(LOPSUBGEN) --gen-man=$@ --version-string $(GIT_VERSION) < $<
+
+%.flex.c: %.lex
+ $(call SAY, FLEX $<)
+ $(FLEX) -o $@ $<
+
+%.bison.c %.bison.h: %.y
+ $(call SAY, BISON $<)
+ $(BISON) --defines=$(notdir $(<:.y=.bison.h)) \
+ --output=$(notdir $(<:.y=.bison.c)) $<
+
+TF_CPPFLAGS += -DCOPYRIGHT_YEAR='"$(COPYRIGHT_YEAR)"'
+TF_CPPFLAGS += -DLOGLEVELS='$(LOGLEVELS)'
+TF_CPPFLAGS += -DBUILD_DATE='"$(build_date)"'
+TF_CPPFLAGS += -DCC_VERSION='"$(cc_version)"'
+TF_CPPFLAGS += -DUNAME_RS='"$(uname_rs)"'
+TF_CPPFLAGS += -I/usr/local/include
+
+TF_CFLAGS += -g
+TF_CFLAGS += -O2
+TF_CFLAGS += -Wall
+TF_CFLAGS += -Wundef -W -Wuninitialized
+TF_CFLAGS += -Wchar-subscripts
+TF_CFLAGS += -Werror-implicit-function-declaration
+TF_CFLAGS += -Wmissing-noreturn
+TF_CFLAGS += -Wbad-function-cast
+TF_CFLAGS += -Wredundant-decls
+TF_CFLAGS += -Wdeclaration-after-statement
+TF_CFLAGS += -Wformat -Wformat-security -Wmissing-format-attribute
+
+%.flex.o: TF_CFLAGS += -Wno-all
+
+%.o: %.c tfortune.lsg.h txp.bison.h
+ $(call SAY, CC $<)
+ $(CC) \
+ -o $@ -c -MMD -MF $(*F).d \
+ -MT $@ $(TF_CPPFLAGS) $(CPPFLAGS) $(TF_CFLAGS) $(CFLAGS) $<
+
+TF_LDFLAGS=-llopsub
+tfortune: $(deps:.d=.o)
+ $(call SAY, LD $@)
+ $(CC) $^ -o $@ $(TF_LDFLAGS) $(LDFLAGS)
+
+.PHONY: all mostlyclean clean install install-strip
+
+mostlyclean:
+ $(RM) tfortune *.o *.d
+clean: mostlyclean
+ $(RM) *.lsg.* *.flex.* *.bison.* *.1 version.c
+distclean: clean
+ $(RM) config.mak config.status config.log config.h configure config.h.in
+ $(RM) -r autom4te.cache
+maintainer-clean: distclean
+ git clean -dfqx > /dev/null 2>&1
+
+mandir := $(datarootdir)/man/man1
+INSTALL ?= install
+INSTALL_PROGRAM ?= $(INSTALL) -m 755
+INSTALL_DATA ?= $(INSTALL) -m 644
+ifneq ($(findstring strip, $(MAKECMDGOALS)),)
+ strip_option := -s
+endif
+
+install install-strip: all
+ $(MKDIR_P) $(DESTDIR)$(bindir) $(DESTDIR)$(mandir)
+ $(INSTALL_PROGRAM) $(strip_option) tfortune $(DESTDIR)$(bindir)
+ $(INSTALL_DATA) tfortune.1 $(DESTDIR)$(mandir)
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#include "tf.h"
+#include "txp.bison.h"
+
+enum semantic_types {
+ ST_STRVAL,
+ ST_INTVAL,
+ ST_BOOLVAL,
+ ST_REGEX_PATTERN,
+};
+
+struct txp_context {
+ /* global context */
+ char *errmsg;
+ struct txp_ast_node *ast;
+ /* per tag expression context */
+ unsigned num_lines;
+ unsigned num_tags;
+ char **tags;
+};
+
+/*
+ * Set the error bit in the parser context and log a message.
+ *
+ * This is called if the lexer or the parser detect an error. Only the first
+ * error is logged (with a severity of "warn").
+ */
+__attribute__ ((format (printf, 3, 4)))
+void txp_parse_error(int line, struct txp_context *ctx, const char *fmt, ...)
+{
+ va_list ap;
+ char *tmp;
+
+ if (ctx->errmsg) /* we already printed an error message */
+ return;
+ va_start(ap, fmt);
+ xvasprintf(&tmp, fmt, ap);
+ va_end(ap);
+ xasprintf(&ctx->errmsg, "line %d: %s", line, tmp);
+ free(tmp);
+ WARNING_LOG("%s\n", ctx->errmsg);
+}
+
+/*
+ * Parse a (generalized) string literal.
+ *
+ * This function turns the generalized C99 string literal given by src into a C
+ * string. For example, the string literal "xyz\n" is transformed into an
+ * array containing the three characters 'x', 'y' and 'z', followed by a
+ * newline character and the terminating zero byte. The function allows to
+ * specify different quote characters so that, for example, regular expression
+ * patterns enclosed in '/' can be parsed as well. To parse a proper string
+ * literal, one has to pass two double quotes as the second argument.
+ *
+ * The function strips off the opening and leading quote characters, replaces
+ * double backslashes by single backslashes and handles the usual escapes like
+ * \n and \".
+ *
+ * The caller must make sure that the input is well-formed. The function simply
+ * aborts if the input is not a valid C99 string literal (modulo the quote
+ * characters).
+ *
+ * The return value is the offset of the first character after the closing
+ * quote. For proper string literals this will be the terminating zero byte of
+ * the input string, for regular expression patterns it is the beginning of the
+ * flags which modify the matching behaviour.
+ */
+unsigned parse_quoted_string(const char *src, const char quote_chars[2],
+ char **result)
+{
+ size_t n, len = strlen(src);
+ char *dst, *p;
+ bool backslash;
+
+ assert(len >= 2);
+ assert(src[0] == quote_chars[0]);
+ p = dst = xmalloc(len - 1);
+ backslash = false;
+ for (n = 1;; n++) {
+ char c;
+ assert(n < len);
+ c = src[n];
+ if (!backslash) {
+ if (c == '\\') {
+ backslash = true;
+ continue;
+ }
+ if (c == quote_chars[1])
+ break;
+ *p++ = c;
+ continue;
+ }
+ if (c == quote_chars[1])
+ *p++ = quote_chars[1];
+ else switch (c) {
+ case '\\': *p++ = '\\'; break;
+ case 'a': *p++ = '\a'; break;
+ case 'b': *p++ = '\b'; break;
+ case 'f': *p++ = '\f'; break;
+ case 'n': *p++ = '\n'; break;
+ case 'r': *p++ = '\r'; break;
+ case 't': *p++ = '\t'; break;
+ case 'v': *p++ = '\v'; break;
+ default: assert(false);
+ }
+ backslash = false;
+ }
+ assert(src[n] == quote_chars[1]);
+ *p = '\0';
+ *result = dst;
+ return n + 1;
+}
+
+/*
+ * Parse and compile an extended regular expression pattern, including flags.
+ *
+ * A regex pattern is identical to a C99 string literal except (a) it is
+ * enclosed in '/' characters rather than double quotes, (b) double quote
+ * characters which are part of the pattern do not need to be quoted with
+ * backslashes, but slashes must be quoted in this way, and (c) the closing
+ * slash may be followed by one or more flag characters which modify the
+ * matching behaviour.
+ *
+ * The only flags which are currently supported are 'i' to ignore case in match
+ * (REG_ICASE) and 'n' to change the handling of newline characters
+ * (REG_NEWLINE).
+ *
+ * This function calls parse_quoted_string(), hence it aborts if the input
+ * string is malformed. However, errors from regcomp(3) are returned without
+ * aborting the process. The rationale behind this difference is that passing a
+ * malformed string must be considered an implementation bug because malformed
+ * strings should be rejected earlier by the lexer.
+ */
+int txp_parse_regex_pattern(const char *src, struct txp_re_pattern *result)
+{
+ int ret;
+ char *pat;
+ unsigned n = parse_quoted_string(src, "//", &pat);
+
+ result->flags = 0;
+ for (; src[n]; n++) {
+ switch (src[n]) {
+ case 'i': result->flags |= REG_ICASE; break;
+ case 'n': result->flags |= REG_NEWLINE; break;
+ default: assert(false);
+ }
+ }
+ ret = xregcomp(&result->preg, pat, result->flags);
+ free(pat);
+ return ret;
+}
+
+static struct txp_ast_node *ast_node_raw(int id)
+{
+ struct txp_ast_node *node = xmalloc(sizeof(*node));
+ node->id = id;
+ return node;
+}
+
+/* This is non-static because it is also called from the lexer. */
+struct txp_ast_node *txp_new_ast_leaf_node(int id)
+{
+ struct txp_ast_node *node = ast_node_raw(id);
+ node->num_children = 0;
+ return node;
+}
+
+struct txp_ast_node *ast_node_new_unary(int id, struct txp_ast_node *child)
+{
+ struct txp_ast_node *node = ast_node_raw(id);
+ node->num_children = 1;
+ node->children = xmalloc(sizeof(struct txp_ast_node *));
+ node->children[0] = child;
+ return node;
+}
+
+struct txp_ast_node *ast_node_new_binary(int id, struct txp_ast_node *left,
+ struct txp_ast_node *right)
+{
+ struct txp_ast_node *node = ast_node_raw(id);
+ node->num_children = 2;
+ node->children = xmalloc(2 * sizeof(struct txp_ast_node *));
+ node->children[0] = left;
+ node->children[1] = right;
+ return node;
+}
+
+void txp_free_ast(struct txp_ast_node *root)
+{
+ if (!root)
+ return;
+ if (root->num_children > 0) {
+ int i;
+ for (i = 0; i < root->num_children; i++)
+ txp_free_ast(root->children[i]);
+ free(root->children);
+ } else {
+ union txp_semantic_value *sv = &root->sv;
+ switch (root->id) {
+ case STRING_LITERAL:
+ free(sv->strval);
+ break;
+ case REGEX_PATTERN:
+ regfree(&sv->re_pattern.preg);
+ break;
+ }
+ }
+ free(root);
+}
+
+static int eval_node(struct txp_ast_node *node, struct txp_context *ctx,
+ union txp_semantic_value *result);
+
+static void eval_binary_op(struct txp_ast_node *node, struct txp_context *ctx,
+ union txp_semantic_value *v1, union txp_semantic_value *v2)
+{
+ eval_node(node->children[0], ctx, v1);
+ eval_node(node->children[1], ctx, v2);
+}
+
+static int eval_node(struct txp_ast_node *node, struct txp_context *ctx,
+ union txp_semantic_value *result)
+{
+ int ret;
+ union txp_semantic_value v1, v2;
+
+ switch (node->id) {
+ /* strings */
+ case STRING_LITERAL:
+ result->strval = node->sv.strval;
+ return ST_STRVAL;
+ /* integers */
+ case NUM:
+ result->intval = node->sv.intval;
+ return ST_INTVAL;
+ case '+':
+ eval_binary_op(node, ctx, &v1, &v2);
+ result->intval = v1.intval + v2.intval;
+ return ST_INTVAL;
+ case '-':
+ eval_binary_op(node, ctx, &v1, &v2);
+ result->intval = v1.intval - v2.intval;
+ return ST_INTVAL;
+ case '*':
+ eval_binary_op(node, ctx, &v1, &v2);
+ result->intval = v1.intval * v2.intval;
+ return ST_INTVAL;
+ case '/':
+ eval_binary_op(node, ctx, &v1, &v2);
+ if (v2.intval == 0) {
+ static bool warned;
+ if (!warned)
+ ERROR_LOG("division by zero\n");
+ warned = true;
+ result->intval = 0;
+ } else
+ result->intval = v1.intval / v2.intval;
+ return ST_INTVAL;
+ case NEG:
+ eval_node(node->children[0], ctx, &v1);
+ result->intval = -v1.intval;
+ return ST_INTVAL;
+ case NUM_LINES:
+ result->intval = ctx->num_lines;
+ return ST_INTVAL;
+ /* bools */
+ case TRUE:
+ result->boolval = true;
+ return ST_BOOLVAL;
+ case FALSE:
+ result->boolval = false;
+ return ST_BOOLVAL;
+ case OR:
+ eval_binary_op(node, ctx, &v1, &v2);
+ result->boolval = v1.boolval || v2.boolval;
+ return ST_BOOLVAL;
+ case AND:
+ eval_binary_op(node, ctx, &v1, &v2);
+ result->boolval = v1.boolval && v2.boolval;
+ return ST_BOOLVAL;
+ case NOT:
+ eval_node(node->children[0], ctx, &v1);
+ result->boolval = !v1.boolval;
+ return ST_BOOLVAL;
+ case EQUAL:
+ ret = eval_node(node->children[0], ctx, &v1);
+ eval_node(node->children[1], ctx, &v2);
+ if (ret == ST_STRVAL)
+ result->boolval = !strcmp(v1.strval, v2.strval);
+ else
+ result->boolval = v1.intval == v2.intval;
+ return ST_BOOLVAL;
+ case NOT_EQUAL:
+ ret = eval_node(node->children[0], ctx, &v1);
+ eval_node(node->children[1], ctx, &v2);
+ if (ret == ST_STRVAL)
+ result->boolval = strcmp(v1.strval, v2.strval);
+ else
+ result->boolval = v1.intval != v2.intval;
+ return ST_BOOLVAL;
+ case '<':
+ eval_binary_op(node, ctx, &v1, &v2);
+ result->boolval = v1.intval < v2.intval;
+ return ST_BOOLVAL;
+ case '>':
+ eval_binary_op(node, ctx, &v1, &v2);
+ result->boolval = v1.intval > v2.intval;
+ return ST_BOOLVAL;
+ case LESS_OR_EQUAL:
+ eval_binary_op(node, ctx, &v1, &v2);
+ result->boolval = v1.intval <= v2.intval;
+ return ST_BOOLVAL;
+ case GREATER_OR_EQUAL:
+ eval_binary_op(node, ctx, &v1, &v2);
+ result->boolval = v1.intval >= v2.intval;
+ return ST_BOOLVAL;
+ case REGEX_MATCH:
+ eval_binary_op(node, ctx, &v1, &v2);
+ result->boolval = regexec(&v2.re_pattern.preg, v1.strval,
+ 0, NULL, 0) == 0;
+ return ST_BOOLVAL;
+ case REGEX_PATTERN:
+ result->re_pattern = node->sv.re_pattern;
+ return ST_REGEX_PATTERN;
+ default:
+ EMERG_LOG("bug: invalid node id %d\n", node->id);
+ exit(EXIT_FAILURE);
+ }
+}
+
+bool txp_eval_ast(struct txp_ast_node *root, struct txp_context *ctx)
+{
+ union txp_semantic_value v;
+ int ret = eval_node(root, ctx, &v);
+
+ if (ret == ST_INTVAL)
+ return v.intval != 0;
+ if (ret == ST_STRVAL)
+ return v.strval[0] != 0;
+ if (ret == ST_BOOLVAL)
+ return v.boolval;
+ assert(false);
+}
+
+int txp_yylex_init(txp_yyscan_t *yyscanner);
+struct yy_buffer_state *txp_yy_scan_bytes(const char *buf, int len,
+ txp_yyscan_t yyscanner);
+void txp_yy_delete_buffer(struct yy_buffer_state *bs, txp_yyscan_t yyscanner);
+int txp_yylex_destroy(txp_yyscan_t yyscanner);
+void txp_yyset_lineno(int lineno, txp_yyscan_t scanner);
+
+/*
+ * Initialize the tag expression parser.
+ *
+ * This allocates and sets up the internal structures of the tag expression
+ * parser and creates an abstract syntax tree from the given epigram (including
+ * the tags). It must be called before txp_eval_ast() can be called.
+ *
+ * The context pointer returned by this function may be passed to mp_eval_ast()
+ * to determine whether an epigram is admissible.
+ *
+ * The error message pointer may be NULL in which case no error message is
+ * returned. Otherwise, the caller must free the returned string.
+ */
+int txp_init(const char *definition, int nbytes, struct txp_context **result,
+ char **errmsg)
+{
+ int ret;
+ txp_yyscan_t scanner;
+ struct txp_context *ctx;
+ struct yy_buffer_state *buffer_state;
+
+ ctx = xcalloc(sizeof(*ctx));
+ ret = txp_yylex_init(&scanner);
+ assert(ret == 0);
+ buffer_state = txp_yy_scan_bytes(definition, nbytes, scanner);
+ txp_yyset_lineno(1, scanner);
+ NOTICE_LOG("creating abstract syntax tree from tag expression\n");
+ ret = txp_yyparse(ctx, &ctx->ast, scanner);
+ txp_yy_delete_buffer(buffer_state, scanner);
+ txp_yylex_destroy(scanner);
+ if (ctx->errmsg) { /* parse error */
+ if (errmsg)
+ *errmsg = ctx->errmsg;
+ else
+ free(ctx->errmsg);
+ free(ctx);
+ return -E_TXP;
+ }
+ if (errmsg)
+ *errmsg = NULL;
+ *result = ctx;
+ return 1;
+}
--- /dev/null
+#!/bin/sh
+
+# SPDX-License-Identifier: GPL-2.0
+
+autoheader && autoconf
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0
+
+prefix := @prefix@
+exec_prefix := @exec_prefix@
+
+# These two use prefix and exec_prefix
+bindir := @bindir@
+datarootdir := @datarootdir@
+
+PACKAGE_TARNAME := @PACKAGE_TARNAME@
+PACKAGE_VERSION := @PACKAGE_VERSION@
+
+FLEX := @FLEX@
+BISON := @BISON@
+M4 := @M4@
+LOPSUBGEN := @LOPSUBGEN@
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0
+
+AC_PREREQ([2.61])
+
+AC_INIT([tfortune], [m4_esyscmd_s(./version-gen.sh)],
+ [maan@tuebingen.mpg.de], [], [http://people.tuebingen.mpg.de/maan/tfortune/])
+AC_CONFIG_HEADERS([config.h])
+AC_CONFIG_FILES([config.mak])
+AC_USE_SYSTEM_EXTENSIONS
+AC_PROG_CC
+AC_PROG_CPP
+
+AC_DEFUN([REQUIRE_EXECUTABLE], [
+ AC_PATH_PROG(m4_toupper([$1]), [$1])
+ test -z "$m4_toupper([$1])" && AC_MSG_ERROR([$1 is required])
+])
+REQUIRE_EXECUTABLE([flex])
+REQUIRE_EXECUTABLE([bison])
+REQUIRE_EXECUTABLE([lopsubgen])
+
+HAVE_LOPSUB=yes
+AC_CHECK_HEADER(lopsub.h, [], [HAVE_LOPSUB=no])
+AC_CHECK_LIB([lopsub], [lls_merge], [], [HAVE_LOPSUB=no])
+if test $HAVE_LOPSUB = no; then AC_MSG_ERROR([
+ The lopsub library is required to build this software, but
+ the above checks indicate it is not installed on your system.
+ Run the following command to download a copy.
+ git clone git://git.tuebingen.mpg.de/lopsub.git
+ Install the library, then run this configure script again.
+])
+fi
+AC_OUTPUT
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#define TF_ERRORS \
+ TF_ERROR(SUCCESS, "success"), \
+ TF_ERROR(ATOI_OVERFLOW, "value too large"), \
+ TF_ERROR(ATOI_NO_DIGITS, "no digits found in string"), \
+ TF_ERROR(ATOI_JUNK_AT_END, "further characters after number"), \
+ TF_ERROR(TXP, "tag expression parse error"), \
+ TF_ERROR(REGEX, "regular expression error"), \
+ TF_ERROR(LOPSUB, "lopsub error"), \
+
+/*
+ * This is temporarily defined to expand to its first argument (prefixed by
+ * 'E_') and gets later redefined to expand to the error text only
+ */
+#define TF_ERROR(err, msg) E_ ## err
+enum tf_error_codes {TF_ERRORS};
+#undef TF_ERROR
+#define TF_ERROR(err, msg) msg
+#define DEFINE_TF_ERRLIST char *tf_errlist[] = {TF_ERRORS}
+
+extern char *tf_errlist[];
+
+/**
+ * This bit indicates whether a number is considered a system error number
+ * If yes, the system errno is just the result of clearing this bit from
+ * the given number.
+ */
+#define SYSTEM_ERROR_BIT 30
+
+/** Check whether the system error bit is set. */
+#define IS_SYSTEM_ERROR(num) (!!((num) & (1 << SYSTEM_ERROR_BIT)))
+
+/** Set the system error bit for the given number. */
+#define ERRNO_TO_TF_ERROR(num) ((num) | (1 << SYSTEM_ERROR_BIT))
+
+static inline char *tf_strerror(int num)
+{
+ assert(num > 0);
+ if (IS_SYSTEM_ERROR(num))
+ return strerror((num) & ((1 << SYSTEM_ERROR_BIT) - 1));
+ else
+ return tf_errlist[num];
+}
+
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Copied from the Linux kernel source tree, version 2.6.13.
+ *
+ * Licensed under the GPL v2 as per the whole kernel source tree.
+ *
+ */
+
+/** \file list.h doubly linked list implementation */
+
+#include <stddef.h> /* offsetof */
+
+/** get the struct this entry is embedded in */
+#define container_of(ptr, type, member) ({ \
+ const typeof( ((type *)0)->member ) *__mptr = (ptr); \
+ (type *)( (char *)__mptr - offsetof(type,member) );})
+
+/**
+ * Non-NULL pointers that will result in page faults under normal
+ * circumstances, used to verify that nobody uses non-initialized list entries.
+ * Used for poisoning the \a next pointer of struct list_head.
+ */
+#define LIST_POISON1 ((void *) 0x00100100)
+/** Non-null pointer, used for poisoning the \a prev pointer of struct
+ * list_head
+ */
+#define LIST_POISON2 ((void *) 0x00200200)
+
+/** Simple doubly linked list implementation. */
+struct list_head {
+ /** pointer to the next list entry */
+ struct list_head *next;
+ /** pointer to the previous list entry */
+ struct list_head *prev;
+};
+
+/** Define an initialized list head. */
+#define INITIALIZED_LIST_HEAD(name) struct list_head name = { &(name), &(name) }
+
+
+/** must be called before using any other list functions */
+#define INIT_LIST_HEAD(ptr) do { \
+ (ptr)->next = (ptr); (ptr)->prev = (ptr); \
+} while (0)
+
+
+/*
+ * Some of the internal functions ("__xxx") are useful when
+ * manipulating whole lists rather than single entries, as
+ * sometimes we already know the next/prev entries and we can
+ * generate better code by using them directly rather than
+ * using the generic single-entry routines.
+ */
+
+
+/*
+ * Insert a new entry between two known consecutive entries.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static inline void __list_add(struct list_head *new,
+ struct list_head *prev,
+ struct list_head *next)
+{
+ next->prev = new;
+ new->next = next;
+ new->prev = prev;
+ prev->next = new;
+}
+
+/**
+ * add a new entry
+ *
+ * \param new new entry to be added
+ * \param head list head to add it after
+ *
+ * Insert a new entry after the specified head.
+ * This is good for implementing stacks.
+ */
+static inline void list_add(struct list_head *new, struct list_head *head)
+{
+ __list_add(new, head, head->next);
+}
+
+/**
+ * add a new entry
+ *
+ * \param new new entry to be added
+ * \param head list head to add it before
+ *
+ * Insert a new entry before the specified head.
+ * This is useful for implementing queues.
+ */
+static inline void list_add_tail(struct list_head *new, struct list_head *head)
+{
+ __list_add(new, head->prev, head);
+}
+
+/*
+ * Delete a list entry by making the prev/next entries
+ * point to each other.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static inline void __list_del(struct list_head * prev, struct list_head * next)
+{
+ next->prev = prev;
+ prev->next = next;
+}
+
+/**
+ * Delete entry from list.
+ *
+ * \param entry the element to delete from the list.
+ *
+ * Note: list_empty on entry does not return true after this, the entry is
+ * in an undefined state.
+ */
+static inline void list_del(struct list_head *entry)
+{
+ __list_del(entry->prev, entry->next);
+ entry->next = LIST_POISON1;
+ entry->prev = LIST_POISON2;
+}
+
+/**
+ * delete from one list and add as another's head
+ *
+ * \param list: the entry to move
+ * \param head: the head that will precede our entry
+ */
+static inline void list_move(struct list_head *list, struct list_head *head)
+{
+ __list_del(list->prev, list->next);
+ _list_add(list, head);
+}
+
+/**
+ * test whether a list is empty
+ *
+ * \param head the list to test.
+ */
+static inline int list_empty(const struct list_head *head)
+{
+ return head->next == head;
+}
+
+/**
+ * get the struct for this entry
+ *
+ * \param ptr the &struct list_head pointer.
+ * \param type the type of the struct this is embedded in.
+ * \param member the name of the list_struct within the struct.
+ */
+#define list_entry(ptr, type, member) \
+ container_of(ptr, type, member)
+
+/**
+ * iterate over list of given type
+ *
+ * \param pos the type * to use as a loop counter.
+ * \param head the head for your list.
+ * \param member the name of the list_struct within the struct.
+ */
+#define list_for_each_entry(pos, head, member) \
+ for (pos = list_entry((head)->next, typeof(*pos), member); \
+ &pos->member != (head); \
+ pos = list_entry(pos->member.next, typeof(*pos), member))
+
+/**
+ * iterate over list of given type safe against removal of list entry
+ *
+ * \param pos the type * to use as a loop counter.
+ * \param n another type * to use as temporary storage
+ * \param head the head for your list.
+ * \param member the name of the list_struct within the struct.
+ */
+#define list_for_each_entry_safe(pos, n, head, member) \
+ for (pos = list_entry((head)->next, typeof(*pos), member), \
+ n = list_entry(pos->member.next, typeof(*pos), member); \
+ &pos->member != (head); \
+ pos = n, n = list_entry(n->member.next, typeof(*n), member))
+/**
+ * iterate backwards over list of given type safe against removal of list entry
+ * \param pos the type * to use as a loop counter.
+ * \param n another type * to use as temporary storage
+ * \param head the head for your list.
+ * \param member the name of the list_struct within the struct.
+ */
+#define list_for_each_entry_safe_reverse(pos, n, head, member) \
+ for (pos = list_entry((head)->prev, typeof(*pos), member), \
+ n = list_entry(pos->member.prev, typeof(*pos), member); \
+ &pos->member != (head); \
+ pos = n, n = list_entry(n->member.prev, typeof(*n), member))
+
+/**
+ * Get the first element from a list
+ * \param ptr the list head to take the element from.
+ * \param type The type of the struct this is embedded in.
+ * \param member The name of the list_struct within the struct.
+ *
+ * Note that list is expected to be not empty.
+ */
+#define list_first_entry(ptr, type, member) \
+ list_entry((ptr)->next, type, member)
+
+/**
+ * Test whether a list has just one entry.
+ *
+ * \param head The list to test.
+ */
+static inline int list_is_singular(const struct list_head *head)
+{
+ return !list_empty(head) && (head->next == head->prev);
+}
+
--- /dev/null
+<-- SPDX-License-Identifier: GPL-2.0 -->
+
+<svg xmlns="http://www.w3.org/2000/svg" height="70" width="100" >
+ <g fill="none">
+ <g stroke-width="4" linecap="round">
+ <path
+ d="
+ M 40,25 c 6,-20 20,-10 20,5
+ M 3,48 s 5,-30 41,-30
+ M 56,17 s 30,10 20,33
+ M 59,24 s 32,10 -5,35
+ M 56,58 s -6,1 -3,10
+ M 66,49 s 10,-2 20,10
+ m 0,-1 s -2,2 10,5
+ M 60,55 s 5,-5 25,15
+ M 85,70 s -2,-2 11,-7
+ M 72,58 s -2,10 -20,10
+ M 53,68 s -20,0 -5,-28
+ m 0,0 s 7,-10 3,-20
+ m 0,0 s 5,5 -7,15
+ M 45,45 s -10,-4 -30,20
+ m 0,0 s -3,-28 -12,-17
+ M 16,65 s -14,4 -13,-17
+ "
+ stroke="#000"
+ />
+ </g>
+ </g>
+</svg>
+
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <limits.h>
+#include <string.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <fnmatch.h>
+#include <regex.h>
+#include <assert.h>
+#include <lopsub.h>
+
+#include "err.h"
+
+/* Opaque, only known to ast.c. Passed to the generated txp_yyparse(). */
+struct txp_context;
+
+/*
+ * Since we use a reentrant lexer, all functions generated by flex(1)
+ * receive an additional argument of this type.
+ */
+typedef void *txp_yyscan_t;
+
+/* Parsed regex pattern. */
+struct txp_re_pattern {
+ regex_t preg; /* Pre-compiled regex. */
+ unsigned flags; /* Subset of the cflags described in regex(3). */
+};
+
+/*
+ * The possible values of a node in the abstract syntax tree (AST).
+ *
+ * Constant semantic values (string literals, numeric constants and regex
+ * patterns which are part of the tag expression) are determined during
+ * txp_init() while values which depend on the epigram (tags, number of lines,
+ * etc.) are determined during txp_eval_row().
+ *
+ * This union, and the txp_ast_node structure below are used extensively in
+ * txp.y. However, both need to be public because the lexer must be able to
+ * create AST nodes for the constant semantic values.
+ */
+union txp_semantic_value {
+ bool boolval; /* Comparators, =~ and =|. */
+ char *strval; /* String literals, tags, path. */
+ int64_t intval; /* Constants, bitrate, frequency, etc. */
+ struct txp_re_pattern re_pattern; /*< Right-hand side operand of =~. */
+};
+
+/*
+ * A node is either interior or a leaf node. Interior nodes have at least one
+ * child while leaf nodes have a semantic value and no children.
+ *
+ * Examples: (a) STRING_LITERAL has a semantic value (the unescaped string
+ * literal) and no children, (b) NEG (unary minus) has no semantic value but
+ * one child (the numeric expression that is to be negated), (c) LESS_OR_EQUAL
+ * has no semantic value and two children (the two numeric expressions being
+ * compared).
+ */
+struct txp_ast_node {
+ /* Corresponds to a token type, for example LESS_OR_EQUAL. */
+ int id;
+ union {
+ /* Pointers to the child nodes (interior nodes only). */
+ struct txp_ast_node **children;
+ /* Leaf nodes only. */
+ union txp_semantic_value sv;
+ };
+ /*
+ * The number of children is implicitly given by the id, but we include
+ * it here to avoid having to maintain a lookup table. The AST is
+ * usually small, so we can afford to waste a byte per node.
+ */
+ uint8_t num_children;
+};
+
+enum loglevels {LOGLEVELS, NUM_LOGLEVELS};
+#define DEBUG_LOG(f,...) txp_log(LL_DEBUG, "%s: " f, __FUNCTION__, ## __VA_ARGS__)
+#define INFO_LOG(f,...) txp_log(LL_INFO, "%s: " f, __FUNCTION__, ## __VA_ARGS__)
+#define NOTICE_LOG(f,...) txp_log(LL_NOTICE, "%s: " f, __FUNCTION__, ## __VA_ARGS__)
+#define WARNING_LOG(f,...) txp_log(LL_WARNING, "%s: " f, __FUNCTION__, ## __VA_ARGS__)
+#define ERROR_LOG(f,...) txp_log(LL_ERROR, "%s: " f, __FUNCTION__, ## __VA_ARGS__)
+#define CRIT_LOG(f,...) txp_log(LL_CRIT, "%s: " f, __FUNCTION__, ## __VA_ARGS__)
+#define EMERG_LOG(f,...) txp_log(LL_EMERG, "%s: " f, __FUNCTION__, ## __VA_ARGS__)
+
+/* tfortune.c */
+
+void txp_log(int ll, const char* fmt,...);
+
+/* Called from both the lexer and the parser. */
+__attribute__ ((format (printf, 3, 4)))
+void txp_parse_error(int line, struct txp_context *ctx, const char *fmt, ...);
+
+/* Helper functions for the lexer. */
+unsigned parse_quoted_string(const char *src, const char quote_chars[2],
+ char **result);
+int txp_parse_regex_pattern(const char *src, struct txp_re_pattern *result);
+
+/* ast.c */
+struct txp_ast_node *ast_node_new_unary(int id, struct txp_ast_node *child);
+struct txp_ast_node *ast_node_new_binary(int id, struct txp_ast_node *left,
+ struct txp_ast_node *right);
+
+/*
+ * Allocate a new leaf node for the abstract syntax tree.
+ *
+ * This returns a pointer to a node whose ->num_children field is initialized
+ * to zero. The ->id field is initialized with the given id. The caller is
+ * expected to initialize the ->sv field.
+ */
+struct txp_ast_node *txp_new_ast_leaf_node(int id);
+
+/*
+ * Evaluate an abstract syntax tree, starting at the root node.
+ *
+ * The root node argument should be the pointer that was returned from an
+ * earlier call to txp_init() via the context pointer. The context contains the
+ * information about the epigram.
+ *
+ * Returns true if the AST evaluates to true, a non-empty string, or a non-zero
+ * number, false otherwise.
+ */
+bool txp_eval_ast(struct txp_ast_node *root, struct txp_context *ctx);
+
+/*
+ * Deallocate an abstract syntax tree.
+ *
+ * This frees the memory occupied by the nodes of the AST, the child pointers
+ * of the internal nodes and the (constant) semantic values of the leaf nodes
+ * (string literals and pre-compiled regular expressions).
+ */
+void txp_free_ast(struct txp_ast_node *root);
+
+
+/* util.c */
+int atoi64(const char *str, int64_t *value);
+unsigned xvasprintf(char **result, const char *fmt, va_list ap);
+unsigned xasprintf(char **result, const char *fmt, ...);
+void *xmalloc(size_t size);
+void *xcalloc(size_t size);
+int xregcomp(regex_t *preg, const char *regex, int cflags);
+
+/* txp.c */
+
+/* txp.y. */
+
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#include <stdlib.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <assert.h>
+#include <sys/mman.h>
+#include <stdbool.h>
+#include <string.h>
+#include <time.h>
+#include <limits.h>
+#include <sys/time.h>
+#include <lopsub.h>
+
+#include "tf.h"
+#include "tfortune.lsg.h"
+
+#define TF_SEP "---- "
+
+struct tf_map {
+ void *map;
+ off_t len;
+};
+
+struct tf_cookie {
+ unsigned input_num;
+ off_t offset, len;
+};
+
+static void mmap_file(const char *path, struct tf_map *map)
+{
+ int fd, ret;
+
+ ret = open(path, 0);
+ if (ret < 0) {
+ perror("open");
+ exit(EXIT_FAILURE);
+ }
+ fd = ret;
+ map->len = lseek(fd, 0, SEEK_END);
+ if (map->len == (off_t)-1) {
+ perror("lseek");
+ exit(EXIT_FAILURE);
+ }
+ map->map = mmap(NULL, map->len, PROT_READ, MAP_PRIVATE, fd, 0);
+ if (map->map == MAP_FAILED) {
+ perror("mmap");
+ exit(EXIT_FAILURE);
+ }
+ ret = close(fd);
+ assert(ret >= 0);
+}
+
+static bool tag_given(const char *tag, const char *tags, size_t sz)
+{
+ bool found = false;
+ char *p, *str = strndup(tags, sz);
+
+ assert(str);
+ p = strtok(str, ",");
+ while (p) {
+ //fprintf(stderr, "sz: %zu, compare: %s <-> %.*s\n", sz, tag, (int) sz, p);
+ if (strcmp(p, tag) == 0) {
+ found = true;
+ break;
+ }
+ p = strtok(NULL, ",");
+ }
+ free(str);
+ return found;
+}
+
+static bool tags_ok(const char *tags, size_t sz, struct lls_parse_result *lpr)
+{
+ unsigned n, given;
+ const struct lls_opt_result *r;
+ const char *arg;
+
+ r = lls_opt_result(LSG_TFORTUNE_TFORTUNE_OPT_ACCEPT, lpr);
+ given = lls_opt_given(r);
+ if (given == 0)
+ goto check_reject;
+ /* check if any of the given accept tags is set */
+ for (n = 0; n < given; n++) {
+ arg = lls_string_val(n, r);
+ if (tag_given(arg, tags, sz))
+ goto check_reject;
+ }
+ return false; /* accept tag(s) given, but none was set */
+check_reject:
+ /* check if none of the given reject tags is set */
+ r = lls_opt_result(LSG_TFORTUNE_TFORTUNE_OPT_REJECT, lpr);
+ given = lls_opt_given(r);
+ for (n = 0; n < given; n++) {
+ arg = lls_string_val(n, r);
+ if (tag_given(arg, tags, sz))
+ return false;
+ }
+ return true;
+}
+
+static void print_tags(const char *tags, size_t sz, FILE *f)
+{
+ char *p, *str = strndup(tags, sz);
+
+ assert(str);
+ p = strtok(str, ",");
+ while (p) {
+ fprintf(f, "%s\n", p);
+ p = strtok(NULL, ",");
+ }
+ free(str);
+}
+
+static void print_random_cookie(const struct tf_cookie *cookies,
+ unsigned num_cookies, const struct tf_map *maps)
+{
+ long unsigned r;
+ const struct tf_cookie *cookie;
+ struct timeval tv;
+
+ if (num_cookies == 0) {
+ fprintf(stderr, "no matching cookie\n");
+ return;
+ }
+ gettimeofday(&tv, NULL);
+ srandom((unsigned)tv.tv_usec);
+ r = ((num_cookies + 0.0) * (random() / (RAND_MAX + 1.0)));
+ cookie = cookies + r;
+ assert(r < num_cookies);
+ printf("%.*s", (int)cookie->len,
+ (char *)maps[cookie->input_num].map + cookie->offset);
+}
+
+static void make_cookies(struct tf_map *maps, struct lls_parse_result *lpr)
+{
+ struct tf_cookie *cookies = NULL;
+ unsigned n, cookies_size = 0, num_cookies = 0, num_inputs;
+ size_t sep_len = strlen(TF_SEP);
+ const struct lls_opt_result *r_s;
+ FILE *f = NULL;
+
+ r_s = lls_opt_result(LSG_TFORTUNE_TFORTUNE_OPT_STATISTICS, lpr);
+ if (lls_opt_given(r_s)) {
+ f = popen("sort | uniq -c | sort -n", "w");
+ assert(f);
+ }
+ num_inputs = lls_num_inputs(lpr);
+ for (n = 0; n < num_inputs; n++) {
+ struct tf_map *m = maps + n;
+ const char *start = m->map, *end = m->map + m->len;
+ const char *buf = start, *cookie_start = start;
+
+ while (buf < end) {
+ struct tf_cookie *cookie;
+ const char *p, *cr, *tags;
+ size_t sz;
+
+ cr = memchr(buf, '\n', end - buf);
+ if (!cr)
+ break;
+ p = cr + 1;
+ if (!cookie_start)
+ cookie_start = p;
+ if (p + sep_len >= end)
+ break;
+ if (strncmp(p, TF_SEP, sep_len) != 0) {
+ buf = p;
+ continue;
+ }
+ tags = p + sep_len;
+ cr = memchr(tags, '\n', end - tags);
+ if (cr)
+ sz = cr - tags;
+ else
+ sz = end - tags;
+ if (!tags_ok(tags, sz, lpr)) {
+ if (!cr)
+ break;
+ buf = cr;
+ cookie_start = NULL;
+ continue;
+ }
+ num_cookies++;
+ if (lls_opt_given(r_s)) {
+ print_tags(tags, sz, f);
+ if (!cr)
+ break;
+ buf = cr;
+ continue;
+ }
+ if (num_cookies > cookies_size) {
+ cookies_size = 2 * cookies_size + 1;
+ cookies = realloc(cookies,
+ cookies_size * sizeof(*cookies));
+ assert(cookies);
+ }
+ cookie = cookies + num_cookies - 1;
+ cookie->input_num = n;
+ cookie->offset = cookie_start - start;
+ cookie->len = p - cookie_start;
+ buf = p + sep_len;
+ cookie_start = NULL;
+ }
+ }
+ if (f)
+ pclose(f);
+ if (!lls_opt_given(r_s))
+ print_random_cookie(cookies, num_cookies, maps);
+ else
+ printf("num cookies: %d\n", num_cookies);
+ free(cookies);
+}
+
+int main(int argc, char **argv)
+{
+ char *errctx;
+ struct lls_parse_result *lpr;
+ int ret;
+ struct tf_map *maps;
+ unsigned n, num_inputs;
+ const struct lls_command *cmd = lls_cmd(0, tfortune_suite);
+
+ ret = lls_parse(argc, argv, cmd, &lpr, &errctx);
+ if (ret < 0) {
+ fprintf(stderr, "%s: %s\n", errctx? errctx : "",
+ lls_strerror(-ret));
+ free(errctx);
+ exit(EXIT_FAILURE);
+ }
+ if (lls_num_inputs(lpr) == 0) {
+ char *help = lls_short_help(cmd);
+ fprintf(stderr, "%s\n", help);
+ free(help);
+ ret = 0;
+ goto free_lpr;
+ }
+ num_inputs = lls_num_inputs(lpr);
+ maps = xmalloc(num_inputs * sizeof(*maps));
+ for (n = 0; n < num_inputs; n++)
+ mmap_file(lls_input(n, lpr), maps + n);
+ make_cookies(maps, lpr);
+ free(maps);
+ ret = EXIT_SUCCESS;
+free_lpr:
+ lls_free_parse_result(lpr, cmd);
+ return ret;
+}
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0
+
+[suite tfortune]
+[supercommand tfortune]
+ purpose = fortune cookies with tags
+ [description]
+ Like fortune(1), tfortune prints out a random epigram. However,
+ the epigrams in the input files must contain additional information,
+ called "tags". The program picks only epigrams which are considered
+ "admissible" based on the tags and the command line options.
+ [/description]
+ non-opts-name = <file>...
+ [option accept]
+ short_opt = a
+ summary = epigrams which contain this tag are admissible
+ arg_info = required_arg
+ arg_type = string
+ typestr = tag
+ flag multiple
+ [help]
+ This option may be given multiple times.
+ [/help]
+ [option reject]
+ short_opt = r
+ summary = epigrams which contain this tag are inadmissible
+ arg_info = required_arg
+ arg_type = string
+ typestr = tag
+ flag multiple
+ [help]
+ Like --accept, this may be given multiple times. See the discussion
+ below how --accept and --reject interact.
+ [/help]
+ [option statistics]
+ short_opt = s
+ summary = print tags found in input files
+ [help]
+ If this option is specified, tfortune does not print any
+ epigrams. Instead, it prints all tags found in the given input file(s),
+ together with how many time each tag occurred. The list is sorted by
+ occurence count.
+ [/help]
+ [option accept-reject]
+ summary = Admissible epigrams
+ flag ignored
+ [help]
+ tfortune picks a random epigram from the set of admissible epigrams,
+ which is computed as follows. If neither --accept nor --reject are
+ specified, all epigrams are considered admissible. If only --accept
+ is specified, epigrams with at least one tag given as an argument to
+ --accept are admissible, all others are inadmissible. Similarly, if
+ only --reject is specified, epigrams with at least one tag given as an
+ argument to --reject are inadmissible, all others are admissible. If
+ both --accept and --reject are specified, an epigram is admissible
+ if and only if it has at least one tag which is given as an argument
+ to --accept but no tag which is given as an argument to --reject.
+ [/help]
+ [option format]
+ summary = Input file format
+ flag ignored
+ [help]
+ Input files may contain arbitrary many epigrams. The end of each
+ epigram must be marked with a "tag" line. The tag line consists of
+ four dashes, a space character, and a comma separated list of tags.
+ Tags may span multiple words, but no comma is allowed.
+ [/help]
+
+[section example]
+The following is an example input file for tfortune. It contains a single epigram
+with two tags.
+
+.RS
+.EX
+Anyone who attempts to generate random numbers by deterministic means
+is, of course, living in a state of sin. -- John von Neumann
+---- math,religion
+.EE
+.RE
+[/section]
+
+[section copyright]
+ Written by Andre Noll
+ .br
+ Copyright (C) 2016-present Andre Noll
+ .br
+ License: GNU GPL version 3
+ .br
+ This is free software: you are free to change and redistribute it.
+ .br
+ There is NO WARRANTY, to the extent permitted by law.
+ .br
+ Report bugs to
+ .MT <maan@tuebingen.mpg.de>
+ Andre Noll
+ .ME
+ .br
+ Homepage:
+ .UR http://people.tuebingen.mpg.de/~maan/tfortune/
+ .UE
+[/section]
+[section see also]
+ .BR fortune (6)
+[/section]
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0 */
+
+ /*
+ * Since we do not supply yywrap(), we use noyywrap to instruct the scanner to
+ * behave as though yywrap() returned 1.
+ */
+%option noyywrap
+
+ /*
+ * We don't want symbols to clash with those of other flex users, particularly
+ * lopsub.
+ */
+%option prefix="txp_yy"
+
+ /*
+ * Generate a scanner that maintains the number of the current line read from
+ * its input in the yylineno variable.
+ */
+%option yylineno
+
+ /* Generate a bison-compatible scanner. */
+%option bison-bridge bison-locations
+
+ /*
+ * Warn (in particular) if the default rule can be matched but no default rule
+ * has been given.
+ */
+%option warn
+
+ /*
+ * Generate a scanner which is portable and safe to use in one or more threads
+ * of control.
+ */
+%option reentrant
+
+ /*
+ * Generate a scanner which always looks one extra character ahead. This is a
+ * bit faster than an interactive scanner for which look ahead happens only
+ * when necessary.
+ */
+%option never-interactive
+
+%{
+#include "tf.h"
+
+#define YYSTYPE TXP_YYSTYPE
+#define YYLTYPE TXP_YYLTYPE
+#define YY_DECL int txp_yylex(TXP_YYSTYPE *yylval_param, TXP_YYLTYPE *yylloc_param, \
+ struct txp_context *ctx, struct txp_ast_node **ast, txp_yyscan_t yyscanner)
+#include "txp.bison.h"
+#define TXP_YY_USER_ACTION do {txp_yylloc->first_line = txp_yylineno;} while (0);
+%}
+DECIMAL_CONSTANT (0|([[:digit:]]{-}[0])[[:digit:]]*)
+STRING_LITERAL \"([^\"\\\n]|(\\[\"\\abfnrtv]))*\"
+REGEX_PATTERN \/([^\/\\\n]|(\\[\/\\abfnrtv]))*\/([in])*
+%%
+
+tag {return TAG;}
+num_lines {return NUM_LINES;}
+true {return TRUE;}
+false {return FALSE;}
+
+[[:space:]]+|#.*\n /* skip comments and whitespace */
+
+"("|")"|","|"+"|"-"|"*"|"/"|"<"|">" {return yytext[0];}
+
+"||" {return OR;}
+"&&" {return AND;}
+"!" {return NOT;}
+"==" {return EQUAL;}
+"!=" {return NOT_EQUAL;}
+"<=" {return LESS_OR_EQUAL;}
+">=" {return GREATER_OR_EQUAL;}
+"=~" {return REGEX_MATCH;}
+
+{DECIMAL_CONSTANT} {
+ int ret;
+ yylval->node = txp_new_ast_leaf_node(NUM);
+ ret = atoi64(yytext, &yylval->node->sv.intval);
+ if (ret < 0) {
+ free(yylval->node);
+ txp_parse_error(yylloc->first_line, ctx, "%s: %s", yytext,
+ tf_strerror(-ret));
+ return -E_TXP;
+ }
+ return NUM;
+}
+
+{STRING_LITERAL} {
+ yylval->node = txp_new_ast_leaf_node(STRING_LITERAL);
+ parse_quoted_string(yytext, "\"\"", &yylval->node->sv.strval);
+ return STRING_LITERAL;
+}
+
+{REGEX_PATTERN} {
+ int ret;
+ yylval->node = txp_new_ast_leaf_node(REGEX_PATTERN);
+ ret = txp_parse_regex_pattern(yytext, &yylval->node->sv.re_pattern);
+ if (ret < 0) {
+ txp_parse_error(yylloc->first_line, ctx, "%s: %s", yytext,
+ tf_strerror(-ret));
+ return -E_TXP;
+ }
+ return REGEX_PATTERN;
+}
+
+. {
+ txp_parse_error(yylloc->first_line, ctx, "unrecognized text: %s",
+ yytext);
+ return -E_TXP;
+}
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Provide more verbose and specific error messages instead of just "syntax
+ * error".
+ */
+%define parse.error verbose
+
+/*
+ * Verbose error messages may contain incorrect information if LAC (Lookahead
+ * Correction) is not enabled.
+ */
+%define parse.lac full
+
+/* Avoid symbol clashes (lopsub might also expose yy* symbols). */
+%define api.prefix {txp_yy}
+
+/*
+ * Although locations are automatically enabled as soon as the grammar uses the
+ * special @N tokens, specifying %locations explicitly allows for more accurate
+ * syntax error messages.
+ */
+%locations
+
+/*
+ * Generate a pure (reentrant) parser. With this option enabled, yylval and
+ * yylloc become local variables in yyparse(), and a different calling
+ * convention is used for yylex().
+ */
+%define api.pure full
+
+/* Additional arguments to yylex(), yyparse() and yyerror() */
+%param {struct txp_context *ctx}
+%param {struct txp_ast_node **ast}
+%param {txp_yyscan_t yyscanner} /* reentrant lexers */
+
+%{
+#include "tf.h"
+#include "txp.bison.h"
+
+int yylex(TXP_YYSTYPE *lvalp, TXP_YYLTYPE *llocp, struct txp_context *ctx,
+ struct txp_ast_node **ast, txp_yyscan_t yyscanner);
+static void yyerror(YYLTYPE *llocp, struct txp_context *ctx,
+ struct txp_ast_node **ast, txp_yyscan_t yyscanner, const char *msg);
+
+%}
+
+%union {
+ struct txp_ast_node *node;
+}
+
+/* terminals */
+%token <node> NUM
+%token <node> STRING_LITERAL
+%token <node> REGEX_PATTERN
+
+/* keywords with semantic value */
+%token <node> NUM_LINES
+%token <node> FALSE TRUE
+
+/* keywords without semantic value */
+%token TAG
+
+/* operators, ordered by precendence */
+%left OR
+%left AND
+%left EQUAL NOT_EQUAL
+%left LESS_THAN LESS_OR_EQUAL GREATER_OR_EQUAL REGEX_MATCH FILENAME_MATCH
+%left '-' '+'
+%left '*' '/'
+%right NOT NEG /* negation (unary minus) */
+
+/* nonterminals */
+%type <node> string
+%type <node> exp
+%type <node> boolexp
+
+%%
+
+program:
+ /* empty */ {*ast = NULL; return 0;}
+ | string {*ast = $1; return 0;}
+ | exp {*ast = $1; return 0;}
+ | boolexp {*ast = $1; return 0;}
+
+string: STRING_LITERAL {$$ = $1;}
+;
+
+exp: NUM {$$ = $1;}
+ | exp '+' exp {$$ = ast_node_new_binary('+', $1, $3);}
+ | exp '-' exp {$$ = ast_node_new_binary('-', $1, $3);}
+ | exp '*' exp {$$ = ast_node_new_binary('*', $1, $3);}
+ | exp '/' exp {$$ = ast_node_new_binary('/', $1, $3);}
+ | '-' exp %prec NEG {$$ = ast_node_new_unary(NEG, $2);}
+ | '(' exp ')' {$$ = $2;}
+ | NUM_LINES {$$ = txp_new_ast_leaf_node(NUM_LINES);}
+;
+
+boolexp: TRUE {$$ = txp_new_ast_leaf_node(TRUE);}
+ | FALSE {$$ = txp_new_ast_leaf_node(FALSE);}
+ | '(' boolexp ')' {$$ = $2;}
+ | boolexp OR boolexp {$$ = ast_node_new_binary(OR, $1, $3);}
+ | boolexp AND boolexp {$$ = ast_node_new_binary(AND, $1, $3);}
+ | NOT boolexp {$$ = ast_node_new_unary(NOT, $2);}
+ | exp EQUAL exp {$$ = ast_node_new_binary(EQUAL, $1, $3);}
+ | exp NOT_EQUAL exp {$$ = ast_node_new_binary(NOT_EQUAL, $1, $3);}
+ | exp '<' exp {$$ = ast_node_new_binary('<', $1, $3);}
+ | exp '>' exp {$$ = ast_node_new_binary('>', $1, $3);}
+ | exp LESS_OR_EQUAL exp {
+ $$ = ast_node_new_binary(LESS_OR_EQUAL, $1, $3);
+ }
+ | exp GREATER_OR_EQUAL exp {
+ $$ = ast_node_new_binary(GREATER_OR_EQUAL, $1, $3);
+ }
+ | string REGEX_MATCH REGEX_PATTERN {
+ $$ = ast_node_new_binary(REGEX_MATCH, $1, $3);
+ }
+ | string EQUAL string {$$ = ast_node_new_binary(EQUAL, $1, $3);}
+ | string NOT_EQUAL string {$$ = ast_node_new_binary(NOT_EQUAL, $1, $3);}
+;
+%%
+
+/* Called by yyparse() on error */
+static void yyerror(YYLTYPE *llocp, struct txp_context *ctx,
+ __attribute__ ((unused)) struct txp_ast_node **ast,
+ __attribute__ ((unused)) txp_yyscan_t yyscanner,
+ const char *msg)
+{
+ txp_parse_error(llocp->first_line, ctx, "%s", msg);
+}
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#include "tf.h"
+
+DEFINE_TF_ERRLIST;
+
+int atoi64(const char *str, int64_t *value)
+{
+ char *endptr;
+ long long tmp;
+
+ errno = 0; /* To distinguish success/failure after call */
+ tmp = strtoll(str, &endptr, 10);
+ if (errno == ERANGE && (tmp == LLONG_MAX || tmp == LLONG_MIN))
+ return -E_ATOI_OVERFLOW;
+ /*
+ * If there were no digits at all, strtoll() stores the original value
+ * of str in *endptr.
+ */
+ if (endptr == str)
+ return -E_ATOI_NO_DIGITS;
+ /*
+ * The implementation may also set errno and return 0 in case no
+ * conversion was performed.
+ */
+ if (errno != 0 && tmp == 0)
+ return -E_ATOI_NO_DIGITS;
+ if (*endptr != '\0') /* Further characters after number */
+ return -E_ATOI_JUNK_AT_END;
+ *value = tmp;
+ return 1;
+}
+
+__attribute__ ((warn_unused_result))
+void *xrealloc(void *p, size_t size)
+{
+ /*
+ * No need to check for NULL pointers: If p is NULL, the call
+ * to realloc is equivalent to malloc(size)
+ */
+ assert(size);
+ if (!(p = realloc(p, size))) {
+ EMERG_LOG("realloc failed (size = %zu), aborting\n", size);
+ exit(EXIT_FAILURE);
+ }
+ return p;
+}
+
+__attribute__ ((warn_unused_result))
+void *xmalloc(size_t size)
+{
+ return xrealloc(NULL, size);
+}
+
+__attribute__ ((warn_unused_result))
+void *xcalloc(size_t size)
+{
+ void *p = xmalloc(size);
+ memset(p, 0, size);
+ return p;
+}
+
+/*
+ * Print a formated message to a dynamically allocated string.
+ *
+ * This function is similar to vasprintf(), a GNU extension which is not in C
+ * or POSIX. It allocates a string large enough to hold the output including
+ * the terminating null byte. The allocated string is returned via the first
+ * argument and must be freed by the caller. However, unlike vasprintf(), this
+ * function calls exit() if insufficient memory is available, while vasprintf()
+ * returns -1 in this case.
+ *
+ * It returns the number of bytes written, not including the terminating \p
+ * NULL character.
+ */
+__attribute__ ((format (printf, 2, 0)))
+unsigned xvasprintf(char **result, const char *fmt, va_list ap)
+{
+ int ret;
+ size_t size = 150;
+ va_list aq;
+
+ *result = xmalloc(size + 1);
+ va_copy(aq, ap);
+ ret = vsnprintf(*result, size, fmt, aq);
+ va_end(aq);
+ assert(ret >= 0);
+ if ((size_t)ret < size)
+ return ret;
+ size = ret + 1;
+ *result = xrealloc(*result, size);
+ va_copy(aq, ap);
+ ret = vsnprintf(*result, size, fmt, aq);
+ va_end(aq);
+ assert(ret >= 0 && (size_t)ret < size);
+ return ret;
+}
+
+__attribute__ ((format (printf, 2, 3)))
+/* Print to a dynamically allocated string, variable number of arguments. */
+unsigned xasprintf(char **result, const char *fmt, ...)
+{
+ va_list ap;
+ unsigned ret;
+
+ va_start(ap, fmt);
+ ret = xvasprintf(result, fmt, ap);
+ va_end(ap);
+ return ret;
+}
+
+/*
+ * Compile a regular expression.
+ *
+ * This simple wrapper calls regcomp(3) and logs a message on errors.
+ */
+int xregcomp(regex_t *preg, const char *regex, int cflags)
+{
+ char *buf;
+ size_t size;
+ int ret = regcomp(preg, regex, cflags);
+
+ if (ret == 0)
+ return 1;
+ size = regerror(ret, preg, NULL, 0);
+ buf = xmalloc(size);
+ regerror(ret, preg, buf, size);
+ ERROR_LOG("%s\n", buf);
+ free(buf);
+ return -E_REGEX;
+}
+
+static int loglevel_arg_val;
+
+__attribute__ ((format (printf, 2, 3)))
+void txp_log(int ll, const char* fmt,...)
+{
+ va_list argp;
+ if (ll < loglevel_arg_val)
+ return;
+ va_start(argp, fmt);
+ vfprintf(stderr, fmt, argp);
+ va_end(argp);
+}
--- /dev/null
+#!/bin/sh
+
+# SPDX-License-Identifier: GPL-2.0
+
+version_file='version.c'
+ver='unnamed_version'
+# First try git, then gitweb, then default.
+if [ -e '.git' -o -e '../.git' ]; then
+ git_ver=$(git describe --abbrev=4 HEAD 2>/dev/null)
+ [ -z "$git_ver" ] && git_ver="$ver"
+ # update stat information in index to match working tree
+ git update-index -q --refresh > /dev/null
+ # if there are differences (exit code 1), the working tree is dirty
+ git diff-index --quiet HEAD || git_ver=$git_ver-dirty
+ ver=$git_ver
+elif [ "${PWD%%-*}" = 'tfortune-' ]; then
+ ver=${PWD##*/tfortune-}
+fi
+ver=${ver#v}
+
+echo "$ver"
+
+# update version file if necessary
+content="const char *lls_version(void) {return \"$ver\";};"
+[ -r "$version_file" ] && echo "$content" | cmp -s - $version_file && exit 0
+echo >&2 "new git version: $ver"
+echo "$content" > $version_file