resolver: initial import of an embrionic dependency resolver.
authorKrisztian Litkey <krisztian.litkey@intel.com>
Mon, 13 Aug 2012 07:09:03 +0000 (10:09 +0300)
committerKrisztian Litkey <krisztian.litkey@intel.com>
Fri, 26 Oct 2012 15:44:40 +0000 (18:44 +0300)
22 files changed:
configure.ac
src/Makefile.am
src/resolver/fact.c [new file with mode: 0644]
src/resolver/fact.h [new file with mode: 0644]
src/resolver/murphy-resolver.pc.in [new file with mode: 0644]
src/resolver/parser-api.h [new file with mode: 0644]
src/resolver/parser.y [new file with mode: 0644]
src/resolver/resolver-types.h [new file with mode: 0644]
src/resolver/resolver.c [new file with mode: 0644]
src/resolver/resolver.h [new file with mode: 0644]
src/resolver/scanner.l [new file with mode: 0644]
src/resolver/script.c [new file with mode: 0644]
src/resolver/script.h [new file with mode: 0644]
src/resolver/target-sorter.c [new file with mode: 0644]
src/resolver/target.c [new file with mode: 0644]
src/resolver/target.h [new file with mode: 0644]
src/resolver/test-input [new file with mode: 0644]
src/resolver/test-input-audio [new file with mode: 0644]
src/resolver/test-input-video [new file with mode: 0644]
src/resolver/tests/Makefile.am [new file with mode: 0644]
src/resolver/tests/parser-test.c [new file with mode: 0644]
src/resolver/tests/test [new file with mode: 0644]

index 08d6c4d..b3a27d9 100644 (file)
@@ -40,6 +40,8 @@ AM_PROG_CC_C_O
 AM_PROG_LIBTOOL
 AC_PROG_LEX
 AC_PROG_YACC
+AM_PROG_LEX
+AC_SUBST(LEXLIB)
 
 # Don't require ctags (we keep linker scripts and debug files in the repo now).
 #AC_CHECK_PROG(CTAGS, ctags, "ctags")
@@ -378,19 +380,21 @@ AC_CONFIG_FILES([build-aux/shave
                 build-aux/shave-libtool
                 Makefile
                 src/Makefile
-                src/common/tests/Makefile
-                src/core/tests/Makefile
-                src/daemon/tests/Makefile
                 src/common/murphy-common.pc
                 src/common/murphy-dbus.pc
                 src/common/murphy-pulse.pc
+                src/common/tests/Makefile
                 src/core/murphy-core.pc
+                src/core/tests/Makefile
                  src/murphy-db/Makefile
                  src/murphy-db/mdb/Makefile
                  src/murphy-db/mqi/Makefile
                  src/murphy-db/mql/Makefile
                  src/murphy-db/include/Makefile
                  src/murphy-db/tests/Makefile
+                src/daemon/tests/Makefile
+                src/resolver/murphy-resolver.pc
+                src/resolver/tests/Makefile
                 doc/Makefile
                  doc/plugin-developer-guide/Makefile
                  doc/plugin-developer-guide/db/Makefile
index 4854fd6..187e989 100644 (file)
@@ -1,4 +1,5 @@
-SUBDIRS         = . murphy-db common/tests core/tests daemon/tests
+SUBDIRS         = . murphy-db \
+                 common/tests core/tests daemon/tests resolver/tests
 AM_CFLAGS       = $(WARNING_CFLAGS) -I$(top_builddir) -DLIBDIR=\"@LIBDIR@\"
 MURPHY_CFLAGS   = 
 pkgconfigdir    = ${libdir}/pkgconfig
@@ -102,7 +103,7 @@ libmurphy_coreh_ladir      =                \
 libmurphy_coreh_la_HEADERS =           \
                core.h
 
-libmurphy_core_ladir      =            \
+libmurphy_core_ladir =                 \
                $(includedir)/murphy/core
 
 libmurphy_core_la_HEADERS =            \
@@ -244,6 +245,75 @@ clean-linker-script::
 endif
 
 ###################################
+# murphy resolver library
+#
+
+PARSER_PREFIX    = yy_res_
+AM_YFLAGS        = -p $(PARSER_PREFIX)
+AM_LFLAGS        = -P $(PARSER_PREFIX)
+LEXCOMPILE       = $(LEX) $(LFLAGS) $(AM_LFLAGS)
+YACCCOMPILE      = $(YACC) $(YFLAGS) $(AM_YFLAGS)
+
+lib_LTLIBRARIES += libmurphy-resolver.la
+EXTRA_DIST      += resolver/murphy-resolver.pc
+pkgconfig_DATA  += resolver/murphy-resolver.pc
+
+libmurphy_resolver_ladir =                     \
+               $(includedir)/murphy/resolver
+
+libmurphy_resolver_la_HEADERS =                        \
+               resolver/resolver.h
+
+libmurphy_resolver_la_REGULAR_SOURCES =                \
+               resolver/resolver.c             \
+               resolver/parser.c               \
+               resolver/scanner.c              \
+               resolver/target.c               \
+               resolver/target-sorter.c        \
+               resolver/fact.c                 \
+               resolver/script.c
+
+libmurphy_resolver_la_SOURCES =                        \
+               $(libmurphy_resolver_la_REGULAR_SOURCES)        \
+               resolver-func-info.c
+
+libmurphy_resolver_la_CFLAGS =                 \
+               $(AM_CFLAGS)
+
+libmurphy_resolver_la_LDFLAGS =                        \
+               -Wl,-version-script=linker-script.resolver \
+               -version-info @MURPHY_VERSION_INFO@
+
+libmurphy_resolver_la_LIBADD = \
+               libmurphy-common.la
+
+libmurphy_resolver_la_DEPENDENCIES = linker-script.resolver libmurphy-common.la
+
+# lexical analyser generation
+resolver/scanner.c: resolver/scanner.l resolver/parser.h
+       $(LEXCOMPILE) $<
+       mv lex.$(PARSER_PREFIX).c $@
+
+resolver/parser.h resolver/parser.c: resolver/parser.y
+       $(YACCCOMPILE) $<
+       mv -f y.tab.h resolver/parser.h
+       mv -f y.tab.c resolver/parser.c
+
+# resolver linker script generation
+linker-script.resolver: $(libmurphy_resolver_la_HEADERS)
+       $(QUIET_GEN)$(top_builddir)/build-aux/gen-linker-script -q -o $@ $^
+
+clean-linker-script::
+       -rm -f linker-script.resolver
+
+# debug file:line-function mapping generation
+resolver-func-info.c: $(libmurphy_resolver_la_REGULAR_SOURCES)
+       $(QUIET_GEN)$(top_builddir)/build-aux/gen-debug-table -o $@ $^
+
+clean-func-infos::
+       -rm resolver-func-info.c
+
+###################################
 # murphy plugins
 #
 
diff --git a/src/resolver/fact.c b/src/resolver/fact.c
new file mode 100644 (file)
index 0000000..82806b7
--- /dev/null
@@ -0,0 +1,55 @@
+#include <murphy/common/mm.h>
+
+#include "resolver-types.h"
+#include "resolver.h"
+#include "fact.h"
+
+
+int create_facts(mrp_resolver_t *r)
+{
+    target_t *t;
+    int       i, j;
+
+    for (i = 0, t = r->targets; i < r->ntarget; i++, t++) {
+        for (j = 0; i < t->ndepend; j++) {
+            if (*t->depends[j] == '$')
+                if (!create_fact(r, t->depends[j]))
+                    return FALSE;
+        }
+    }
+
+    return TRUE;
+}
+
+
+int create_fact(mrp_resolver_t *r, char *fact)
+{
+    int     i;
+    fact_t *f;
+
+    for (i = 0; i < r->nfact; i++) {
+        if (!strcmp(r->facts[i].name, fact))
+            return TRUE;
+    }
+
+    if (!mrp_reallocz(r->facts, r->nfact * sizeof(*r->facts),
+                      (r->nfact + 1) * sizeof(*r->facts)))
+        return FALSE;
+
+    f = r->facts + r->nfact++;
+    f->name = mrp_strdup(fact);
+
+    if (f->name != NULL)
+        return TRUE;
+    else
+        return FALSE;
+}
+
+
+int fact_changed(mrp_resolver_t *r, int id)
+{
+    MRP_UNUSED(r);
+    MRP_UNUSED(id);
+
+    return TRUE;
+}
diff --git a/src/resolver/fact.h b/src/resolver/fact.h
new file mode 100644 (file)
index 0000000..b1f396f
--- /dev/null
@@ -0,0 +1,10 @@
+#ifndef __MURPHY_RESOLVER_FACT_H__
+#define __MURPHY_RESOLVER_FACT_H__
+
+#include "resolver.h"
+
+int create_facts(mrp_resolver_t *r);
+int create_fact(mrp_resolver_t *r, char *name);
+int fact_changed(mrp_resolver_t *r, int id);
+
+#endif /* __MURPHY_RESOLVER_FACT_H__ */
diff --git a/src/resolver/murphy-resolver.pc.in b/src/resolver/murphy-resolver.pc.in
new file mode 100644 (file)
index 0000000..dbc83bc
--- /dev/null
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: murphy-resolver
+Description: Murphy policy framework, resolver library.
+Requires: murphy-common @PACKAGE_VERSION@
+Version: @PACKAGE_VERSION@
+Libs: -L${libdir} -lmurphy-common
+Cflags: -I${includedir}
diff --git a/src/resolver/parser-api.h b/src/resolver/parser-api.h
new file mode 100644 (file)
index 0000000..419c818
--- /dev/null
@@ -0,0 +1,47 @@
+#ifndef __MURPHY_RESOLVER_PARSER_TYPES_H__
+#define __MURPHY_RESOLVER_PARSER_TYPES_H__
+
+#include <stdio.h>
+
+#include <murphy/common/list.h>
+
+#define YY_RES_RINGBUF_SIZE (8 * 1024)            /* token buffer size */
+
+/*
+ * a parsed target definition
+ */
+
+typedef struct {
+    mrp_list_hook_t  hook;                        /* to list of targets */
+    char            *name;                        /* target name */
+    char           **depends;                     /* target dependencies */
+    int              ndepend;                     /* number of dependencies */
+    char            *script;                      /* update script */
+} yy_res_target_t;
+
+
+typedef struct yy_res_input_s yy_res_input_t;
+
+struct yy_res_input_s {
+    yy_res_input_t *prev;                         /* previous input */
+    void           *yybuf;                        /* scanner buffer */
+    char           *name;                         /* name of this input */
+    int             line;                         /* line number in input */
+    FILE           *fp;                           /* input stream */
+};
+
+
+typedef struct {
+    mrp_list_hook_t targets;                      /* list of targets */
+    char            ringbuf[YY_RES_RINGBUF_SIZE]; /* token ringbuffer */
+    int             offs;                         /* buffer insert offset */
+    yy_res_input_t *in;                           /* current input */
+    yy_res_input_t *done;                         /* processed inputs */
+} yy_res_parser_t;
+
+
+int parser_setup(yy_res_parser_t *parser, const char *path);
+void parser_cleanup(yy_res_parser_t *parser);
+int parser_parse_file(yy_res_parser_t *parser, const char *path);
+
+#endif /* __MURPHY_RESOLVER_PARSER_TYPES_H__ */
diff --git a/src/resolver/parser.y b/src/resolver/parser.y
new file mode 100644 (file)
index 0000000..6a021f1
--- /dev/null
@@ -0,0 +1,256 @@
+%{ /* -*- c -*- */
+
+#include <murphy/common/mm.h>
+#include <murphy/common/log.h>
+#include <murphy/common/list.h>
+
+#include "murphy/resolver/resolver.h"
+#include "murphy/resolver/parser-api.h"
+#include "murphy/resolver/token.h"
+#include "murphy/resolver/scanner.h"
+
+void yy_res_error(yy_res_parser_t *parser, const char *msg);
+
+static tkn_strarr_t *strarr_append(tkn_strarr_t *arr, char *str);
+
+%}
+
+%union {
+    tkn_any_t        any;
+    tkn_string_t     string;
+    tkn_s16_t        s16;
+    tkn_u16_t        u16;
+    tkn_s32_t        s32;
+    tkn_u32_t        u32;
+    yy_res_target_t *target;
+    tkn_strarr_t    *strarr;
+}
+
+%defines
+%parse-param { yy_res_parser_t *parser }
+%lex-param   { yy_res_parser_t *parser }
+
+%token          KEY_TARGET
+%token          KEY_DEPENDS_ON
+%token          KEY_UPDATE_SCRIPT
+%token          KEY_END_SCRIPT
+%token <string> TKN_IDENT
+%token <string> TKN_FACT
+%token <string> TKN_SCRIPT_LINE
+%token <string> TKN_LEX_ERROR
+
+%type  <target> targets
+%type  <target> target
+%type  <strarr> optional_dependencies
+%type  <strarr> dependencies
+%type  <string> optional_script
+%type  <string> script
+
+
+%%
+
+input: targets
+    ;
+
+targets:
+  target         { mrp_list_append(&parser->targets, &$1->hook); }
+| targets target { mrp_list_append(&parser->targets, &$2->hook); }
+| targets error  { YYABORT; }
+;
+
+target: KEY_TARGET TKN_IDENT optional_dependencies optional_script {
+    yy_res_target_t *t;
+    int i;
+
+    t = mrp_allocz(sizeof(*t));
+
+    if (t != NULL) {
+        mrp_list_init(&t->hook);
+
+        t->name = mrp_strdup($2.value);
+        if ($3 != NULL) {
+            t->depends = $3->strs;
+            t->ndepend = $3->nstr;
+        }
+        t->script = $4.value;
+
+        if (t->name != NULL) {
+            mrp_list_append(&parser->targets, &t->hook);
+            $$ = t;
+        }
+        else
+            YYABORT;
+    }
+
+    mrp_log_info("target '%s':", $2.value);
+
+    if ($3 != NULL) {
+        for (i = 0; i < $3->nstr; i++)
+            mrp_log_info("    depends on '%s'", $3->strs[i]);
+    }
+    else
+        mrp_log_info("    no dependencies");
+
+    mrp_log_info("    update script: %s%s",
+                 $4.value ? "\n" : "", $4.value ? $4.value : "none");
+  }
+;
+
+optional_dependencies:
+  /* no dependencies */       { $$ = NULL; }
+| KEY_DEPENDS_ON dependencies { $$ = $2;   }
+;
+
+dependencies:
+  TKN_IDENT                   { $$ = strarr_append(NULL, $1.value); }
+| TKN_FACT                    { $$ = strarr_append(NULL, $1.value); }
+| dependencies TKN_IDENT {
+    if (!strarr_append($1, $2.value))
+        YYABORT;
+    else
+        $$ = $1;
+  }
+| dependencies TKN_FACT {
+    if (!strarr_append($1, $2.value))
+        YYABORT;
+    else
+        $$ = $1;
+  }
+;
+
+optional_script:
+  /* no script */                         { $$.value = NULL;     }
+| KEY_UPDATE_SCRIPT script KEY_END_SCRIPT { $$.value = $2.value; }
+;
+
+script:
+  TKN_SCRIPT_LINE {
+      int n;
+
+      n = strlen($1.value) + 2;
+      $$.value = mrp_allocz(n);
+
+      if ($$.value != NULL) {
+          strcpy($$.value, $1.value);
+          $$.value[n - 2] = '\n';
+          $$.value[n - 1] = '\0';
+      }
+      else
+          YYABORT;
+  }
+| script TKN_SCRIPT_LINE {
+    int o, n;
+
+    o = strlen($1.value);
+    n = o + strlen($2.value) + 2;
+    $$.value = mrp_reallocz($1.value, o, n);
+
+    if ($$.value == NULL)
+        YYABORT;
+
+    strcat($$.value, $2.value);
+    $$.value[n - 2] = '\n';
+    $$.value[n - 1] = '\0';
+  }
+;
+
+%%
+
+void yy_res_error(yy_res_parser_t *parser, const char *msg)
+{
+    MRP_UNUSED(parser);
+
+    mrp_log_error("parse error at %s:%d near token '%s': %s",
+                  yy_res_lval.any.source, yy_res_lval.any.line,
+                  yy_res_lval.any.token, msg);
+}
+
+
+static tkn_strarr_t *strarr_append(tkn_strarr_t *arr, char *str)
+{
+    int n;
+
+    if (arr == NULL) {
+        arr = mrp_allocz(sizeof(*arr));
+        if (arr == NULL)
+            return NULL;
+    }
+
+    if (!mrp_reallocz(arr->strs, arr->nstr, arr->nstr + 1))
+        return NULL;
+
+    n = arr->nstr++;
+    arr->strs[n] = mrp_strdup(str);
+
+    if (arr->strs[n] != NULL)
+        return arr;
+    else {
+        if (n == 0) {
+            mrp_free(arr->strs);
+            mrp_free(arr);
+        }
+
+        return NULL;
+    }
+}
+
+
+int parser_setup(yy_res_parser_t *parser, const char *path)
+{
+    mrp_clear(parser);
+    mrp_list_init(&parser->targets);
+
+    if (path != NULL)
+        return scanner_push_file(parser, path);
+    else
+        return TRUE;
+}
+
+
+void parser_cleanup(yy_res_parser_t *parser)
+{
+    mrp_list_hook_t  *tp, *tn;
+    yy_res_target_t  *t;
+    yy_res_input_t   *ip, *in;
+    char            **dep;
+    int               i;
+
+    mrp_list_foreach(&parser->targets, tp, tn) {
+        t = mrp_list_entry(tp, typeof(*t), hook);
+
+        mrp_free(t->name);
+        mrp_free(t->script);
+
+        if (t->depends != NULL) {
+            for (i = 0, dep = t->depends; i < t->ndepend; i++, dep++)
+                mrp_free(*dep);
+
+            mrp_free(t->depends);
+        }
+
+        mrp_free(t);
+    }
+
+    ip = parser->in;
+    while (ip != NULL) {
+        in = ip->prev;
+        scanner_free_input(ip);
+        ip = in;
+    }
+
+    ip = parser->done;
+    while (ip != NULL) {
+        in = ip->prev;
+        scanner_free_input(ip);
+        ip = in;
+    }
+}
+
+
+int parser_parse_file(yy_res_parser_t *parser, const char *path)
+{
+    if (parser_setup(parser, path))
+        return yy_res_parse(parser) == 0;
+    else
+        return FALSE;
+}
diff --git a/src/resolver/resolver-types.h b/src/resolver/resolver-types.h
new file mode 100644 (file)
index 0000000..5bd6e2e
--- /dev/null
@@ -0,0 +1,48 @@
+#ifndef __MURPHY_RESOLVER_TYPES_H__
+#define __MURPHY_RESOLVER_TYPES_H__
+
+#include <stdint.h>
+
+
+typedef struct target_s target_t;
+typedef uint32_t        tstamp_t;
+typedef struct fact_s   fact_t;
+
+typedef enum {
+    PREREQ_UNKNOWN = 0,
+    PREREQ_FACT,
+    PREREQ_TARGET
+} prereq_type_t;
+
+typedef struct {
+    prereq_type_t type;                  /* PREREQ_FACT or PREREQ_TARGET */
+    union {
+        char     *name;                  /* fact or target name */
+        target_t *target;                /* pointer to prerequisit target */
+    };
+} prereq_t;
+
+struct target_s {
+    tstamp_t  stamp;                     /* touch-stamp */
+    char     *name;                      /* target name */
+    char    **depends;                   /* dependencies stated in the input */
+    int       ndepend;                   /* number of dependencies */
+    int      *update_facts;              /* facts to check when updating */
+    int      *update_targets;            /* targets to update when updating */
+    char     *script;                    /* update script if any, or NULL */
+};
+
+struct fact_s {
+    char     *name;
+};
+
+
+struct mrp_resolver_s {
+    target_t *targets;
+    int       ntarget;
+    fact_t   *facts;
+    int       nfact;
+};
+
+
+#endif /* __MURPHY_RESOLVER_TYPES_H__ */
diff --git a/src/resolver/resolver.c b/src/resolver/resolver.c
new file mode 100644 (file)
index 0000000..e788297
--- /dev/null
@@ -0,0 +1,101 @@
+#include <stdarg.h>
+
+#include <murphy/common/mm.h>
+#include <murphy/common/debug.h>
+
+#include "scanner.h"
+#include "resolver-types.h"
+#include "target.h"
+#include "target-sorter.h"
+#include "resolver.h"
+
+mrp_resolver_t *mrp_resolver_parse(const char *path)
+{
+    yy_res_parser_t  parser;
+    mrp_resolver_t  *r;
+
+    mrp_clear(&parser);
+    r = mrp_allocz(sizeof(*r));
+
+    if (r != NULL) {
+        if (parser_parse_file(&parser, path)) {
+            if (create_targets(r, &parser) && sort_targets(r)) {
+                parser_cleanup(&parser);
+
+                return r;
+            }
+        }
+    }
+
+    mrp_resolver_cleanup(r);
+    parser_cleanup(&parser);
+
+    return NULL;
+}
+
+
+void mrp_resolver_cleanup(mrp_resolver_t *r)
+{
+    int       i, j;
+    target_t *t;
+    fact_t   *f;
+
+    if (r != NULL) {
+        for (i = 0, t = r->targets; i < r->ntarget; i++, t++) {
+            mrp_free(t->name);
+
+            for (j = 0; j < t->ndepend; j++)
+                mrp_free(t->depends[j]);
+            mrp_free(t->depends);
+
+            mrp_free(t->update_facts);
+            mrp_free(t->update_targets);
+
+            mrp_free(t->script);
+        }
+
+        mrp_free(r->targets);
+
+        for (i = 0, f = r->facts; i < r->nfact; i++, f++) {
+            mrp_free(f->name);
+        }
+
+        mrp_free(r->facts);
+    }
+
+    mrp_free(r);
+}
+
+
+int mrp_resolver_update(mrp_resolver_t *r, const char *target, ...)
+{
+    va_list ap;
+    int     status;
+
+    MRP_UNUSED(r);
+
+    va_start(ap, target);
+    status = update_target_by_name(r, target, ap);
+    va_end(ap);
+
+    return status;
+}
+
+
+void mrp_resolver_dump_targets(mrp_resolver_t *r, FILE *fp)
+{
+    dump_targets(r, fp);
+}
+
+
+void mrp_resolver_dump_facts(mrp_resolver_t *r, FILE *fp)
+{
+    int     i;
+    fact_t *f;
+
+    fprintf(fp, "%d facts\n", r->nfact);
+    for (i = 0; i < r->nfact; i++) {
+        f = r->facts + i;
+        fprintf(fp, "  #%d: %s\n", i, f->name);
+    }
+}
diff --git a/src/resolver/resolver.h b/src/resolver/resolver.h
new file mode 100644 (file)
index 0000000..de3aed8
--- /dev/null
@@ -0,0 +1,14 @@
+#ifndef __MURPHY_RESOLVER_H__
+#define __MURPHY_RESOLVER_H__
+
+#include <stdio.h>
+
+typedef struct mrp_resolver_s mrp_resolver_t;
+
+mrp_resolver_t *mrp_resolver_parse(const char *path);
+void mrp_resolver_cleanup(mrp_resolver_t *r);
+int mrp_resolver_update(mrp_resolver_t *r, const char *target, ...);
+void mrp_resolver_dump_targets(mrp_resolver_t *r, FILE *fp);
+void mrp_resolver_dump_facts(mrp_resolver_t *r, FILE *fp);
+
+#endif /* __MURPHY_RESOLVER_H__ */
diff --git a/src/resolver/scanner.l b/src/resolver/scanner.l
new file mode 100644 (file)
index 0000000..b8ba948
--- /dev/null
@@ -0,0 +1,265 @@
+%{ /* -*- c -*- */
+
+#define YY_DECL int yy_res_lex(yy_res_parser_t *parser)
+
+#include <stdio.h>
+
+#include <murphy/common/mm.h>
+#include <murphy/common/log.h>
+
+#include "murphy/resolver/resolver.h"
+#include "murphy/resolver/scanner.h"
+#include "murphy/resolver/token.h"
+#include "murphy/resolver/parser-api.h"
+#include "murphy/resolver/parser.h"
+
+#define YY_NO_INPUT
+
+#define yy_res_create_buffer    yy_res__create_buffer
+#define yy_res_delete_buffer    yy_res__delete_buffer
+#define yy_res_switch_to_buffer yy_res__switch_to_buffer
+#define yy_res_scan_buffer      yy_res__scan_buffer
+
+
+/*
+ * lexical analyser input sources
+ *
+ * We support an include mechanism similar to the #include directive
+ * of the C-preprocessor. When one input file includes another, this
+ * is treated as if the latter file was verbatim copied in place of
+ * the include directive in the former file.
+ *
+ * The include mechanism is (almost) entirely implemented in the lexical
+ * analyser and is transparet to/hidden from the parser. The functions
+ * below and the macro CHECK_EOF take care of inclusion.
+ *
+ * Note that currently there is no any attempt to check for and prevent
+ * circular inclusion loops...
+ */
+
+int scanner_push_file(yy_res_parser_t *parser, const char *path)
+{
+    yy_res_input_t *in;
+    FILE           *fp;
+
+    fp = fopen(path, "r");
+
+    if (fp != NULL) {
+        in = mrp_allocz(sizeof(*in));
+
+        if (in != NULL) {
+            in->fp    = fp;
+            in->name  = mrp_strdup(path);
+            in->line  = 1;
+            in->yybuf = yy_res_create_buffer(in->fp, YY_BUF_SIZE);
+            in->prev  = parser->in;
+
+            yy_res_switch_to_buffer(in->yybuf);
+
+            parser->in = in;
+
+            return TRUE;
+        }
+        else
+            fclose(fp);
+    }
+
+    return FALSE;
+}
+
+
+void scanner_free_input(yy_res_input_t *in)
+{
+    if (in != NULL) {
+        if (in->fp != NULL)
+            fclose(in->fp);
+        mrp_free(in->name);
+        yy_res_delete_buffer(in->yybuf);
+
+        mrp_free(in);
+    }
+}
+
+
+int scanner_pop_input(yy_res_parser_t *parser)
+{
+    yy_res_input_t *in, *prev;
+
+    if (parser->in != NULL) {
+        in   = parser->in;
+        prev = in->prev;
+
+        in->prev     = parser->done;
+        parser->done = in;
+
+        parser->in = prev;
+        if (prev != NULL) {
+            yy_res_switch_to_buffer(prev->yybuf);
+
+            return TRUE;                 /* more input to process */
+        }
+    }
+
+    return FALSE;                        /* no more input */
+}
+
+
+/*
+ * ringbuffer of tokens
+ *
+ * To simplify the lifecycle management of tokens passed between the
+ * lexical analyser and the parser we collect them into a ring buffer
+ * instead of dynamic allocation. This simplifies both the lexical
+ * analyser and the parser and allows us to have sane owner allocates /
+ * owner frees allocation semantics. The price we pay for this is that
+ * the ring buffer must be big enough to accomodate all the unprocessed
+ * tokens between bison rule reductions.
+ */
+
+static char *save_token(yy_res_parser_t *parser, char *str, size_t size)
+{
+    char *token;
+
+    if (!size)
+        size = strlen(str);
+
+    if (parser->offs + size + 1 >= YY_RES_RINGBUF_SIZE)
+        parser->offs = 0;
+
+    token = parser->ringbuf + parser->offs;
+    parser->offs += size + 1;
+
+#ifdef __MURPHY_RESOLVER_CHECK_RINGBUF__
+    if (*token != '\0') {
+        mrp_log_error("Token ring buffer overflow in resolver lexical "
+                      "analyser.");
+        exit(1);
+    }
+#endif
+
+    strncpy(token, str, size);
+    token[size] = '\0';
+
+    yy_res_lval.any.token  = token;
+    yy_res_lval.any.source = parser->in->name;
+    yy_res_lval.any.line   = parser->in->line;
+    yy_res_lval.any.size   = size;
+
+    return token;
+}
+
+
+/*
+ * string token types (must include all token types passed via STRING_TOKEN)
+ */
+
+typedef enum {
+    STRING_TYPE_IDENT,
+    STRING_TYPE_FACT,
+    STRING_TYPE_SCRIPT_LINE,
+} string_type_t;
+
+
+#define KEYWORD_TOKEN(tkn) do {                         \
+        save_token(parser, yy_res_text, yy_res_leng);   \
+                                                        \
+        mrp_debug("KEY_%s", #tkn);                      \
+                                                        \
+        return KEY_##tkn;                               \
+    } while (0)
+
+
+#define STRING_TOKEN(tkn) do {                          \
+        char *_t, *_v;                                  \
+        int   _l;                                       \
+                                                        \
+        switch (STRING_TYPE_##tkn) {                    \
+        case STRING_TYPE_FACT:                          \
+            _v = yy_res_text;                           \
+            _l = yy_res_leng;                           \
+            break;                                      \
+        default:                                        \
+            _v = yy_res_text;                           \
+            _l = yy_res_leng;                           \
+        }                                               \
+                                                        \
+        _t = save_token(parser, _v, _l);                \
+        yy_res_lval.string.value = _t;                  \
+                                                        \
+        mrp_debug("TKN_%s: '%s'", #tkn, _t);            \
+                                                        \
+        return TKN_##tkn;                               \
+    } while (0)
+
+
+#define IGNORE_TOKEN(tkn) do {                          \
+        mrp_debug("ignore %s ('%s')", #tkn,             \
+                  yy_res_text);                         \
+    } while (0)
+
+
+#define INCLUDE_FILE() do {                             \
+        char *_p;                                       \
+        int   _l;                                       \
+                                                        \
+        _p = strchr(yy_res_text, '"');                  \
+        if (_p != NULL) {                               \
+            _p++;                                       \
+            _l = yy_res_leng - (_p - yy_res_text);      \
+            _p = save_token(parser, _p, _l - 1);        \
+                                                        \
+            mrp_debug("including file '%s'...", _p);    \
+                                                        \
+            if (!scanner_push_file(parser, _p))         \
+                return TKN_LEX_ERROR;                   \
+        }                                               \
+    } while (0)
+
+#define CHECK_EOF() do {                                \
+        if (!scanner_pop_input(parser))                 \
+            yyterminate();                              \
+    } while (0)
+
+%}
+
+%option warn
+%option batch
+%option noyywrap
+%option nounput
+
+WS                    [ \t]+
+ESCAPED_EOL           \\\n
+EOL                   \n
+
+INCLUDE               ^include{WS}\"[^\"]*\"
+
+TARGET                ^target
+DEPENDS_ON            depends\ on
+IDENT                 [a-zA-Z_][a-zA-Z0-9_]+
+FACT                  \${IDENT}(\.{IDENT})*
+UPDATE_SCRIPT         ^{WS}update\ script
+END_SCRIPT            ^{WS}end\ script
+SCRIPT_LINE           ^{WS}.*
+
+%x SCRIPT
+
+%%
+
+{TARGET}              { KEYWORD_TOKEN(TARGET);                        }
+{DEPENDS_ON}          { KEYWORD_TOKEN(DEPENDS_ON);                    }
+{IDENT}               { STRING_TOKEN(IDENT);                          }
+{FACT}                { STRING_TOKEN(FACT);                           }
+
+{UPDATE_SCRIPT}       { BEGIN(SCRIPT);  KEYWORD_TOKEN(UPDATE_SCRIPT); }
+<SCRIPT>{END_SCRIPT}  { BEGIN(INITIAL); KEYWORD_TOKEN(END_SCRIPT);    }
+<SCRIPT>{SCRIPT_LINE} { STRING_TOKEN(SCRIPT_LINE);                    }
+
+<*>{WS}               { /*IGNORE_TOKEN(WS);*/                         }
+<*>{EOL}              { parser->in->line++; /*IGNORE_TOKEN(EOL);*/    }
+{ESCAPED_EOL}         { parser->in->line++;                           }
+
+{INCLUDE}             { INCLUDE_FILE();                               }
+<<EOF>>               { CHECK_EOF();                                  }
+
+.                     { mrp_log_error("Unhandled token '%s'",
+                                      yy_res_text);                   }
diff --git a/src/resolver/script.c b/src/resolver/script.c
new file mode 100644 (file)
index 0000000..a9cc0ff
--- /dev/null
@@ -0,0 +1,24 @@
+#include <stdio.h>
+#include <stdarg.h>
+#include <stddef.h>
+
+#include <murphy/common/macros.h>
+
+#include "resolver.h"
+
+
+int eval_script(mrp_resolver_t *r, char *script, va_list ap)
+{
+    MRP_UNUSED(r);
+    MRP_UNUSED(ap);
+
+    if (script == NULL)
+        return TRUE;
+    else {
+        printf("----- running update script -----\n");
+        printf("%s", script);
+        printf("---------------------------------\n");
+
+        return TRUE;
+    }
+}
diff --git a/src/resolver/script.h b/src/resolver/script.h
new file mode 100644 (file)
index 0000000..d8260ef
--- /dev/null
@@ -0,0 +1,10 @@
+#ifndef __MURPHY_RESOLVER_SCRIPT_H__
+#define __MURPHY_RESOLVER_SCRIPT_H__
+
+#include <stdarg.h>
+
+#include "resolver.h"
+
+int eval_script(mrp_resolver_t *r, char *script, va_list ap);
+
+#endif /* __MURPHY_RESOLVER_SCRIPT_H__ */
diff --git a/src/resolver/target-sorter.c b/src/resolver/target-sorter.c
new file mode 100644 (file)
index 0000000..004a39f
--- /dev/null
@@ -0,0 +1,447 @@
+#include <murphy/common/mm.h>
+#include <murphy/common/debug.h>
+
+#include "scanner.h"
+#include "resolver.h"
+#include "resolver-types.h"
+#include "target-sorter.h"
+
+
+typedef struct {
+    mrp_resolver_t *r;                   /* resolver context */
+    int            *edges;               /* edges between nodes */
+    int             nnode;               /* number of graph nodes */
+} graph_t;
+
+
+static graph_t *build_graph(mrp_resolver_t *r);
+static int sort_graph(graph_t *g, int target_idx);
+static void free_graph(graph_t *g);
+static void dump_graph(graph_t *g, FILE *fp);
+
+
+int sort_targets(mrp_resolver_t *r)
+{
+    graph_t *g;
+    int      i, success;
+
+    g = build_graph(r);
+
+    if (g != NULL) {
+        dump_graph(g, stdout);
+
+        success = TRUE;
+        for (i = 0; i < r->ntarget && success; i++) {
+            if (!sort_graph(g, i))
+                success = FALSE;
+        }
+
+        free_graph(g);
+    }
+
+    return success;
+}
+
+
+
+static inline int fact_id(graph_t *g, char *fact)
+{
+    int i;
+
+    for (i = 0; i < g->r->nfact; i++)
+        if (!strcmp(fact, g->r->facts[i].name))
+            return i;
+
+    return -1;
+}
+
+
+static inline int target_id(graph_t *g, char *target)
+{
+    int i;
+
+    for (i = 0; i < g->r->ntarget; i++)
+        if (!strcmp(target, g->r->targets[i].name))
+            return g->r->nfact + i;
+
+    return -1;
+}
+
+
+static inline char *node_name(graph_t *g, int id)
+{
+    if (id < g->r->nfact)
+        return g->r->facts[id].name;
+    else
+        return g->r->targets[id - g->r->nfact].name;
+}
+
+
+static inline int node_id(graph_t *g, char *name)
+{
+    if (name[0] == '$')
+        return fact_id(g, name);
+    else
+        return target_id(g, name);
+}
+
+
+static inline int *edge_markp(graph_t *g, int n1, int n2)
+{
+    if (n1 >= 0 && n2 >= 0)
+        return g->edges + (n1 * g->nnode) + n2;
+    else
+        return NULL;
+}
+
+
+static inline void undelete_node(graph_t *g, int node)
+{
+    *edge_markp(g, node, node) = 1;
+}
+
+
+static inline void delete_node(graph_t *g, int node)
+{
+    *edge_markp(g, node, node) = 0;
+}
+
+
+static inline int node_present(graph_t *g, int node)
+{
+    return *edge_markp(g, node, node) == 1;
+}
+
+
+static graph_t *build_graph(mrp_resolver_t *r)
+{
+    graph_t  *g;
+    int       tid, did, i, j;
+    target_t *t;
+
+    g = mrp_allocz(sizeof(*g));
+
+    if (g != NULL) {
+        g->r     = r;
+        g->nnode = r->nfact + r->ntarget;
+        g->edges = mrp_allocz(g->nnode * g->nnode * sizeof(*g->edges));
+
+        if (g->edges == NULL)
+            goto fail;
+
+        for (i = 0; i < r->ntarget; i++) {
+            t   = r->targets + i;
+            tid = r->nfact + i;
+
+            for (j = 0; j < t->ndepend; j++) {
+                mrp_debug("adding edge: %s <- %s", t->depends[j], t->name);
+                did = node_id(g, t->depends[j]);
+
+                if (did < 0)
+                    goto fail;
+
+                *edge_markp(g, did, tid) = 1;
+            }
+        }
+    }
+
+    return g;
+
+ fail:
+    if (g != NULL) {
+        mrp_free(g->edges);
+        mrp_free(g);
+    }
+    return NULL;
+}
+
+
+static inline void delete_nodes(graph_t *g)
+{
+    int i;
+
+    for (i = 0; i < g->nnode; i++)
+        g->edges[i * g->nnode + i] = 0;
+}
+
+
+static int undelete_present_nodes(graph_t *g, int target_idx)
+{
+    int       tid, did, i;
+    target_t *t;
+
+    tid = g->r->nfact + target_idx;
+    t   = g->r->targets + target_idx;
+
+    if (node_present(g, tid))
+        return TRUE;
+
+    undelete_node(g, tid);
+
+    for (i = 0; i < t->ndepend; i++) {
+        did = node_id(g, t->depends[i]);
+
+        if (did < 0)
+            return FALSE;
+
+        if (*t->depends[i] == '$')
+            undelete_node(g, did);
+        else {
+            if (!undelete_present_nodes(g, did - g->r->nfact))
+                return FALSE;
+        }
+    }
+
+    return TRUE;
+}
+
+
+typedef struct {
+    int  size;
+    int *items;
+    int  head;
+    int  tail;
+} que_t;
+
+
+static int que_init(que_t *q, int size)
+{
+    mrp_free(q->items);
+
+    q->items = mrp_alloc(size * sizeof(*q->items));
+
+    if (q->items != NULL) {
+        q->size = size;
+        q->head = 0;
+        q->tail = 0;
+
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+#if 0
+static que_t *que_alloc(int size)
+{
+    que_t *q;
+
+    q = mrp_allocz(sizeof(*q));
+
+    if (q != NULL) {
+        if (que_init(q, size))
+            return q;
+        else
+            mrp_free(q);
+    }
+
+    return NULL;
+}
+#endif
+
+static void que_cleanup(que_t *q)
+{
+    mrp_free(q->items);
+    q->items = NULL;
+}
+
+#if 0
+static void que_free(que_t *q)
+{
+    que_cleanup(q);
+    mrp_free(q);
+}
+#endif
+
+static int que_push(que_t *q, int item)
+{
+    q->items[q->tail++] = item;
+    q->tail            %= q->size;
+
+    return TRUE;
+}
+
+
+static int que_pop(que_t *q, int *itemp)
+{
+    if (q->head != q->tail) {
+        *itemp   = q->items[q->head++];
+        q->head %= q->size;
+
+        return TRUE;
+    }
+    else {
+        *itemp = -1;
+        return FALSE;
+    }
+}
+
+
+static int sort_graph(graph_t *g, int target_idx)
+{
+    target_t *target;
+    int       edges[g->nnode * g->nnode];
+    que_t     L = { .items = NULL}, Q = { .items = NULL };
+    int       i, j, m, id, node, nedge, nfact, ntarget;
+
+    target = g->r->targets + target_idx;
+
+    memcpy(edges, g->edges, sizeof(edges));
+
+    if (!que_init(&L, g->nnode + 1) || !que_init(&Q, g->nnode))
+        goto fail;
+
+    undelete_present_nodes(g, target_idx);
+
+    mrp_debug("-- target %s --", target->name);
+    /*dump_graph(g, stdout);*/
+
+    /* push all present facts, they don't depend on anything */
+    for (i = 0; i < g->r->nfact; i++) {
+        id = i;
+        if (node_present(g, id)) {
+            que_push(&Q, id);
+            delete_node(g, id);
+        }
+    }
+
+    /* push all present targets that have no dependencies */
+    for (i = 0; i < g->r->ntarget; i++) {
+        id = g->r->nfact + i;
+        if (g->r->targets[i].depends == NULL && node_present(g, id)) {
+            que_push(&Q, id);
+            delete_node(g, id);
+        }
+    }
+
+    /* try a topological sort of the nodes present in the graph */
+    while (que_pop(&Q, &node)) {
+        que_push(&L, node);
+
+        mrp_debug("popped node %s", node_name(g, node));
+
+        /*
+         * for each node m with an edge e from node to m do
+         *     remove edge e from the graph
+         *     if m has no other incoming edges then
+         *         insert m into Q
+         */
+        for (m = 0; m < g->nnode; m++) {
+            if (m == node || !node_present(g, m))
+                continue;
+            *edge_markp(g, node, m) = 0;
+            nedge = 0;
+            for (j = 0; j < g->nnode; j++) {
+                if (j == m)
+                    continue;
+                if (node_present(g, j) && *edge_markp(g, j, m))
+                    nedge++;
+            }
+            if (nedge == 0) {
+                mrp_debug("node %s empty, pushing it", node_name(g, m));
+                que_push(&Q, m);
+                delete_node(g, m);
+            }
+            else
+                mrp_debug("node %s not empty yet", node_name(g, m));
+        }
+    }
+
+    /* check if graph has still edges */
+    nedge = 0;
+    for (node = 0; node < g->nnode; node++) {
+        if (!node_present(g, node))
+            continue;
+        for (m = 0; m < g->nnode; m++) {
+            if (m == node || !node_present(g, m))
+                continue;
+            if (*edge_markp(g, node, m) == 1) {
+                printf("error: graph has cycles\n");
+                printf("error: edge %s <- %s still in graph\n",
+                       node_name(g, m), node_name(g, node));
+                goto fail;
+            }
+        }
+    }
+
+    mrp_debug("----- %s: graph sorted successfully -----", target->name);
+
+    for (i = 0; i < L.tail; i++)
+        mrp_debug(" %s", node_name(g, L.items[i]));
+    mrp_debug("-----");
+
+    if (L.tail > 0) {
+        nfact   = 0;
+        ntarget = 0;
+        for (i = 0; i < L.tail; i++) {
+            if (L.items[i] < g->r->nfact)
+                nfact++;
+            else
+                ntarget++;
+        }
+
+        if (nfact > 0) {
+            target->update_facts = mrp_alloc_array(int, nfact + 1);
+            if (target->update_facts != NULL) {
+                for (i = 0; i < nfact; i++)
+                    target->update_facts[i] = L.items[i];
+                target->update_facts[i] = -1;
+            }
+            else
+                goto fail;
+        }
+
+        if (ntarget > 0) {
+            target->update_targets = mrp_alloc_array(int, ntarget + 1);
+            if (target->update_targets != NULL) {
+                for (i = 0; i < ntarget; i++)
+                    target->update_targets[i] = L.items[nfact+i] - g->r->nfact;
+                target->update_targets[i] = -1;
+            }
+            else
+                goto fail;
+        }
+    }
+
+    memcpy(g->edges, edges, sizeof(edges));
+    que_cleanup(&L);
+    que_cleanup(&Q);
+
+    return TRUE;
+
+ fail:
+    memcpy(g->edges, edges, sizeof(edges));
+    que_cleanup(&L);
+    que_cleanup(&Q);
+
+    return FALSE;
+}
+
+
+static void free_graph(graph_t *g)
+{
+    if (g != NULL) {
+        mrp_free(g->edges);
+        mrp_free(g);
+    }
+}
+
+
+static void dump_graph(graph_t *g, FILE *fp)
+{
+    int i, j;
+
+    fprintf(fp, "Graph edges:\n");
+
+    fprintf(fp, "  %20.20s ", "");
+    for (i = 0; i < g->nnode; i++)
+        fprintf(fp, " %d", i % 10);
+    fprintf(fp, "\n");
+
+    for (i = 0; i < g->nnode; i++) {
+        fprintf(fp, "  %20.20s: ", node_name(g, i));
+        for (j = 0; j < g->nnode; j++)
+            fprintf(fp, "%d ", *edge_markp(g, i, j));
+        fprintf(fp, "\n");
+    }
+}
diff --git a/src/resolver/target.c b/src/resolver/target.c
new file mode 100644 (file)
index 0000000..020658a
--- /dev/null
@@ -0,0 +1,185 @@
+#include <stdarg.h>
+
+#include <murphy/common/mm.h>
+#include <murphy/common/list.h>
+
+#include "resolver-types.h"
+#include "resolver.h"
+#include "fact.h"
+#include "target.h"
+#include "script.h"
+
+
+int create_targets(mrp_resolver_t *r, yy_res_parser_t *parser)
+{
+    mrp_list_hook_t *lp, *ln;
+    yy_res_target_t *pt;
+    target_t        *t;
+    int              i;
+
+    r->ntarget = 0;
+    r->targets = NULL;
+    mrp_list_foreach(&parser->targets, lp, ln) {
+        if (!mrp_reallocz(r->targets, r->ntarget * sizeof(*r->targets),
+                          (r->ntarget + 1) * sizeof(*r->targets)))
+            return FALSE;
+
+        pt = mrp_list_entry(lp, typeof(*pt), hook);
+        t  = r->targets + r->ntarget;
+        r->ntarget++;
+
+        t->name     = pt->name;
+        pt->name    = NULL;
+
+        t->depends  = pt->depends;
+        t->ndepend  = pt->ndepend;
+        pt->depends = NULL;
+        pt->ndepend = 0;
+
+        t->script   = pt->script;
+        pt->script  = NULL;
+
+        for (i = 0; i < t->ndepend; i++) {
+            if (*t->depends[i] == '$')
+                if (!create_fact(r, t->depends[i]))
+                    return FALSE;
+        }
+    }
+
+    return TRUE;
+}
+
+
+void dump_targets(mrp_resolver_t *r, FILE *fp)
+{
+    int       i, j, idx;
+    target_t *t;
+
+    fprintf(fp, "%d targets\n", r->ntarget);
+    for (i = 0; i < r->ntarget; i++) {
+        t = r->targets + i;
+        fprintf(fp, "#%d: %s\n", i, t->name);
+
+        fprintf(fp, "  dependencies:");
+        if (t->depends != NULL) {
+            for (j = 0; j < t->ndepend; j++)
+                fprintf(fp, " %s", t->depends[j]);
+            fprintf(fp, "\n");
+
+            fprintf(fp, "  facts to check:");
+            if (t->update_facts != NULL) {
+                for (j = 0; (idx = t->update_facts[j]) >= 0; j++)
+                    fprintf(fp, " %s", r->facts[idx].name);
+                fprintf(fp, "\n");
+            }
+            else
+                fprintf(fp, "<none>\n");
+
+            fprintf(fp, "  target update order:");
+            if (t->update_targets != NULL) {
+                for (j = 0; (idx = t->update_targets[j]) >= 0; j++)
+                    fprintf(fp, " %s", r->targets[idx].name);
+                fprintf(fp, "\n");
+            }
+            else
+                fprintf(fp, "<none>\n");
+        }
+        else
+            fprintf(fp, " <none>\n");
+
+        if (t->script != NULL) {
+            fprintf(fp, "  update script:\n");
+            fprintf(fp, "%s", t->script);
+            fprintf(fp, "  end script\n");
+        }
+        else
+            fprintf(fp, "  no update script\n");
+    }
+}
+
+
+static int update_target(mrp_resolver_t *r, target_t *t, va_list ap)
+{
+    target_t *dep;
+    int       needs_update, status;
+    int       i, j, fid, tid;
+
+    if (t->update_facts == NULL)
+        needs_update = TRUE;
+    else {
+        needs_update = FALSE;
+        for (i = 0; (fid = t->update_facts[i]) >= 0; i++) {
+            if (fact_changed(r, fid)) {
+                needs_update = TRUE;
+                break;
+            }
+        }
+    }
+
+    if (!needs_update)
+        return TRUE;
+
+    status = TRUE;
+
+    for (i = 0; (tid = t->update_targets[i]) >= 0; i++) {
+        /* XXX TODO
+         *     Yuck, need to change how update_{target,fact}s are administered.
+         *     Add an nupdate_{target,fact} field as well...
+         */
+        if (tid == t - r->targets)
+            break;
+
+        dep = r->targets + tid;
+
+        if (dep->update_facts == NULL)
+            needs_update = TRUE;
+        else {
+            needs_update = FALSE;
+            for (j = 0; (fid = dep->update_facts[j]) >= 0; j++) {
+                if (fact_changed(r, dep->update_facts[j])) {
+                    needs_update = TRUE;
+                    break;
+                }
+            }
+        }
+
+        if (needs_update) {
+            status = eval_script(r, dep->script, ap);
+
+            if (status <= 0)
+                break;
+        }
+    }
+
+    if (status > 0)
+        status = eval_script(r, t->script, ap);
+
+    return status;
+}
+
+
+int update_target_by_name(mrp_resolver_t *r, const char *name, va_list ap)
+{
+    target_t *t;
+    int       i, status;
+
+    status = FALSE;
+
+    for (i = 0, t = r->targets; i < r->ntarget; i++, t++) {
+        if (!strcmp(t->name, name)) {
+            status = update_target(r, t, ap);
+            break;
+        }
+    }
+
+    return status;
+}
+
+
+int update_target_by_id(mrp_resolver_t *r, int id, va_list ap)
+{
+    if (id < r->ntarget)
+        return update_target(r, r->targets + id, ap);
+    else
+        return FALSE;
+}
diff --git a/src/resolver/target.h b/src/resolver/target.h
new file mode 100644 (file)
index 0000000..d276648
--- /dev/null
@@ -0,0 +1,15 @@
+#ifndef __MURPHY_RESOLVER_TARGET_H__
+#define __MURPHY_RESOLVER_TARGET_H__
+
+#include <stdarg.h>
+
+#include "resolver-types.h"
+#include "resolver.h"
+#include "parser-api.h"
+
+int create_targets(mrp_resolver_t *r, yy_res_parser_t *parser);
+void dump_targets(mrp_resolver_t *r, FILE *fp);
+int update_target_by_name(mrp_resolver_t *r, const char *name, va_list ap);
+int update_target_by_id(mrp_resolver_t *r, int id, va_list ap);
+
+#endif /* __MURPHY_RESOLVER_TARGET_H__ */
diff --git a/src/resolver/test-input b/src/resolver/test-input
new file mode 100644 (file)
index 0000000..1d39936
--- /dev/null
@@ -0,0 +1,20 @@
+target all
+    depends on video_route audio_route \
+               audio_volume audio_cork audio_mute \
+               $vibra $backlight
+    update script
+
+
+        This is the update script
+        for target all. As you can see, the main parser
+        does not understand jack squat about it. Instead
+        it is parsed by a language-specific script parser
+        after all the target definitions have been parsed.
+        This makes it also possible in theory to use different
+        scripting languages and to make them dynamically registrable.
+
+
+    end script
+
+include "/u/src/work/murphy/src/resolver/test-input-video"
+include "/u/src/work/murphy/src/resolver/test-input-audio"
diff --git a/src/resolver/test-input-audio b/src/resolver/test-input-audio
new file mode 100644 (file)
index 0000000..a74c201
--- /dev/null
@@ -0,0 +1,24 @@
+target audio_route
+    depends on $audio_device_selectable $audio_output_configuration \
+               $resource_owner
+    update script
+        Script for updating audio routing...
+    end script
+
+target audio_volume
+    depends on $resource_owner $resource_set
+    update script
+        Script for updating audio_volume
+    end script
+
+target audio_cork
+    depends on $resource_owner
+    update script
+        $audio_cork[group] |= set_volume_limits()
+    end script
+
+target audio_mute
+    depends on $mute
+    update script
+        $audio_mute[device] |= set_mute()
+    end script
diff --git a/src/resolver/test-input-video b/src/resolver/test-input-video
new file mode 100644 (file)
index 0000000..c63f467
--- /dev/null
@@ -0,0 +1,5 @@
+target video_route
+    depends on $video_device_selectable $gconf
+    update script
+        $video_route = prolog(set_video_routes)
+    end script
diff --git a/src/resolver/tests/Makefile.am b/src/resolver/tests/Makefile.am
new file mode 100644 (file)
index 0000000..415cad4
--- /dev/null
@@ -0,0 +1,7 @@
+noinst_PROGRAMS = parser-test
+AM_CFLAGS       = $(WARNING_CFLAGS) -I$(top_builddir)
+
+# parser test
+parser_test_SOURCES = parser-test.c
+parser_test_CFLAGS  = $(AM_CFLAGS)
+parser_test_LDADD   = ../../libmurphy-resolver.la
diff --git a/src/resolver/tests/parser-test.c b/src/resolver/tests/parser-test.c
new file mode 100644 (file)
index 0000000..ff7fbe1
--- /dev/null
@@ -0,0 +1,163 @@
+#include <stdio.h>
+#include <errno.h>
+
+#define _GNU_SOURCE
+#include <getopt.h>
+
+#include <murphy/common.h>
+#include <murphy/resolver/resolver.h>
+
+
+typedef struct {
+    const char     *file;
+    mrp_resolver_t *r;
+    int             log_mask;
+    const char     *log_target;
+    int             debug;
+} context_t;
+
+
+static void print_usage(const char *argv0, int exit_code, const char *fmt, ...)
+{
+    va_list ap;
+
+    if (fmt && *fmt) {
+        va_start(ap, fmt);
+        vprintf(fmt, ap);
+        va_end(ap);
+    }
+
+    printf("usage: %s [options] [transport-address]\n\n"
+           "The possible options are:\n"
+           "  -f, --file                     input file to user\n"
+           "  -t, --log-target=TARGET        log target to use\n"
+           "      TARGET is one of stderr,stdout,syslog, or a logfile path\n"
+           "  -l, --log-level=LEVELS         logging level to use\n"
+           "      LEVELS is a comma separated list of info, error and warning\n"
+           "  -v, --verbose                  increase logging verbosity\n"
+           "  -d, --debug                    enable given debug confguration\n"
+           "  -D, --list-debug               list known debug sites\n"
+           "  -h, --help                     show help on usage\n",
+           argv0);
+
+    if (exit_code < 0)
+        return;
+    else
+        exit(exit_code);
+}
+
+
+static void config_set_defaults(context_t *ctx)
+{
+    mrp_clear(ctx);
+    ctx->file = "test-input";
+    ctx->log_mask   = MRP_LOG_UPTO(MRP_LOG_DEBUG);
+    ctx->log_target = MRP_LOG_TO_STDERR;
+}
+
+
+int parse_cmdline(context_t *ctx, int argc, char **argv)
+{
+#   define OPTIONS "f:l:t:d:vDh"
+    struct option options[] = {
+        { "file"      , required_argument, NULL, 'f' },
+        { "log-level" , required_argument, NULL, 'l' },
+        { "log-target", required_argument, NULL, 't' },
+        { "verbose"   , optional_argument, NULL, 'v' },
+        { "debug"     , required_argument, NULL, 'd' },
+        { "list-debug", no_argument      , NULL, 'D' },
+        { "help"      , no_argument      , NULL, 'h' },
+        { NULL, 0, NULL, 0 }
+    };
+
+    int  opt;
+
+    config_set_defaults(ctx);
+
+    while ((opt = getopt_long(argc, argv, OPTIONS, options, NULL)) != -1) {
+        switch (opt) {
+        case 'f':
+            ctx->file = optarg;
+            break;
+
+        case 'v':
+            ctx->log_mask <<= 1;
+            ctx->log_mask  |= 1;
+            break;
+
+        case 'l':
+            ctx->log_mask = mrp_log_parse_levels(optarg);
+            if (ctx->log_mask < 0)
+                print_usage(argv[0], EINVAL, "invalid log level '%s'", optarg);
+            break;
+
+        case 't':
+            ctx->log_target = mrp_log_parse_target(optarg);
+            if (!ctx->log_target)
+                print_usage(argv[0], EINVAL, "invalid log target '%s'", optarg);
+            break;
+
+        case 'd':
+            ctx->debug = TRUE;
+            mrp_debug_set_config(optarg);
+            break;
+
+        case 'D':
+            printf("Known debug sites:\n");
+            mrp_debug_dump_sites(stdout, 4);
+            exit(0);
+            break;
+
+        case 'h':
+            print_usage(argv[0], -1, "");
+            exit(0);
+            break;
+
+        default:
+            print_usage(argv[0], EINVAL, "invalid option '%c'", opt);
+        }
+    }
+
+    return TRUE;
+}
+
+
+int main(int argc, char *argv[])
+{
+    context_t  c;
+    int        i;
+    char      *target;
+
+    if (!parse_cmdline(&c, argc, argv))
+        exit(1);
+
+    mrp_log_set_mask(c.log_mask);
+    mrp_log_set_target(c.log_target);
+
+    if (c.debug)
+        mrp_debug_enable(TRUE);
+
+    c.r = mrp_resolver_parse(c.file);
+
+    if (c.r == NULL)
+        mrp_log_error("Failed to parse input file '%s'.", c.file);
+    else {
+        mrp_log_info("Input file '%s' parsed successfully.", c.file);
+        mrp_resolver_dump_targets(c.r, stdout);
+        mrp_resolver_dump_facts(c.r, stdout);
+    }
+
+    for (i = optind; i < argc; i++) {
+        target = argv[i];
+        printf("========== Target %s ==========\n", target);
+        if (mrp_resolver_update(c.r, argv[i], NULL))
+            printf("Resolved OK.\n");
+        else
+            printf("Resolving FAILED.\n");
+    }
+
+    mrp_resolver_cleanup(c.r);
+    c.r = NULL;
+
+    return 0;
+}
diff --git a/src/resolver/tests/test b/src/resolver/tests/test
new file mode 100644 (file)
index 0000000..8e7da32
--- /dev/null
@@ -0,0 +1,19 @@
+target all
+    depends on target1 target2
+
+target target1
+    depends on target3
+
+target target2
+    depends on target4 target5
+
+target target3
+    depends on $fact1
+
+target target4
+    depends on $fact2 $fact3
+
+target target5
+    depends on target6
+
+target target6