add validation code
authorJohn Crispin <blogic@openwrt.org>
Thu, 14 Nov 2013 12:43:19 +0000 (13:43 +0100)
committerJohn Crispin <blogic@openwrt.org>
Fri, 15 Nov 2013 16:10:20 +0000 (17:10 +0100)
Signed-off-by: John Crispin <blogic@openwrt.org>
CMakeLists.txt
validate/cli.c [new file with mode: 0644]
validate/libvalidate.h [new file with mode: 0644]
validate/validate.c [new file with mode: 0644]

index 54434d15c1e5f82afed423722bdef78280906616..8cfbbc8b5af5df18655c501f35e50d7e43d499ca 100644 (file)
@@ -59,3 +59,14 @@ TARGET_LINK_LIBRARIES(lsbloader ${LIBS})
 INSTALL(TARGETS lsbloader
        RUNTIME DESTINATION sbin
 )
+
+ADD_LIBRARY(validate SHARED validate/validate.c)
+INSTALL(TARGETS validate
+       LIBRARY DESTINATION lib
+)
+
+ADD_EXECUTABLE(validate_data validate/cli.c)
+TARGET_LINK_LIBRARIES(validate_data ${LIBS} validate)
+INSTALL(TARGETS validate_data
+       RUNTIME DESTINATION sbin
+)
diff --git a/validate/cli.c b/validate/cli.c
new file mode 100644 (file)
index 0000000..dc5e96b
--- /dev/null
@@ -0,0 +1,28 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include <ctype.h>
+
+#include <arpa/inet.h>
+#include <netinet/ether.h>
+#include <sys/stat.h>
+
+#include "libvalidate.h"
+
+int main(int argc, char **argv)
+{
+       bool rv;
+
+       if (argc == 3) {
+               rv = dt_parse(argv[1], argv[2]);
+
+               printf("%s - %s = %s\n", argv[1], argv[2], rv ? "true" : "false");
+
+               return rv ? 0 : 1;
+       } else if (argc > 3) {
+
+       }
+
+       return 0;
+}
diff --git a/validate/libvalidate.h b/validate/libvalidate.h
new file mode 100644 (file)
index 0000000..d3b8e05
--- /dev/null
@@ -0,0 +1,6 @@
+#ifndef _VALIDATE_H__
+#define _VALIDATE_H__
+
+bool dt_parse(const char *code, const char *value);
+
+#endif
diff --git a/validate/validate.c b/validate/validate.c
new file mode 100644 (file)
index 0000000..c3ae56e
--- /dev/null
@@ -0,0 +1,1015 @@
+/*
+ * Copyright (C) 2013 Jo-Philipp Wich <jow@openwrt.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1
+ * as published by the Free Software Foundation
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include <ctype.h>
+
+#include <arpa/inet.h>
+#include <netinet/ether.h>
+#include <sys/stat.h>
+
+#include "libvalidate.h"
+
+enum dt_optype {
+       OP_UNKNOWN,
+       OP_BOOL,
+       OP_NUMBER,
+       OP_STRING,
+       OP_FUNCTION
+};
+
+struct dt_fun;
+
+struct dt_op {
+       enum dt_optype type;
+       const char *next;
+       int length;
+       int nextop;
+       union {
+               bool boolean;
+               double number;
+               const char *string;
+               struct dt_fun *function;
+       } value;
+};
+
+struct dt_state {
+       int pos;
+       int depth;
+       const char *value;
+       struct dt_op stack[32];
+};
+
+struct dt_fun {
+       const char *name;
+       bool (*call)(struct dt_state *s, int nargs);
+};
+
+static bool
+dt_test_number(double number, const char *value)
+{
+       char *e;
+       double n;
+
+       n = strtod(value, &e);
+
+       return (e > value && *e == 0 && n == number);
+}
+
+static bool
+dt_test_string(const char *s, const char *end, const char *value)
+{
+       bool esc = false;
+
+       while (*value)
+       {
+               if (s > end)
+                       return false;
+
+               if (!esc && *s == '\\')
+               {
+                       s++;
+
+                       if (s >= end)
+                               break;
+
+                       esc = true;
+                       continue;
+               }
+
+               if (*s != *value)
+                       return false;
+
+               esc = false;
+               value++;
+               s++;
+       }
+
+       return (*s == *value || (s > end && *value == 0));
+}
+
+static bool
+dt_step(struct dt_state *s);
+
+static bool
+dt_call(struct dt_state *s);
+
+static bool
+dt_type_or(struct dt_state *s, int nargs)
+{
+       while (nargs--)
+               if (dt_step(s))
+                       return true;
+
+       return false;
+}
+
+static bool
+dt_type_and(struct dt_state *s, int nargs)
+{
+       while (nargs--)
+               if (!dt_step(s))
+                       return false;
+
+       return true;
+}
+
+static bool
+dt_type_not(struct dt_state *s, int nargs)
+{
+       if (!nargs)
+               return false;
+
+       return !dt_step(s);
+}
+
+static bool
+dt_type_neg(struct dt_state *s, int nargs)
+{
+       bool rv;
+       const char *value = s->value;
+
+       if (!nargs)
+               return false;
+
+       if (*s->value == '!')
+               while (isspace(*++s->value));
+
+       rv = dt_step(s);
+       s->value = value;
+
+       return rv;
+}
+
+static bool
+dt_type_list(struct dt_state *s, int nargs)
+{
+       bool rv = true;
+       int pos = s->pos;
+       char *p, *str = strdup(s->value);
+       const char *value = s->value;
+
+       if (!str || !nargs)
+               return false;
+
+       for (p = strtok(str, " \t"); p; p = strtok(NULL, " \t"))
+       {
+               s->value = p;
+
+               if (!dt_step(s))
+               {
+                       rv = false;
+                       break;
+               }
+
+               s->pos = pos;
+       }
+
+       s->value = value;
+       free(str);
+
+       return rv;
+}
+
+static bool
+dt_type_min(struct dt_state *s, int nargs)
+{
+       int n;
+       char *e;
+
+       if (nargs >= 1 && s->stack[s->pos].type == OP_NUMBER)
+       {
+               n = strtol(s->value, &e, 0);
+
+               return (e > s->value && *e == 0 &&
+                       n >= s->stack[s->pos].value.number);
+       }
+
+       return false;
+}
+
+static bool
+dt_type_max(struct dt_state *s, int nargs)
+{
+       int n;
+       char *e;
+
+       if (nargs >= 1 && s->stack[s->pos].type == OP_NUMBER)
+       {
+               n = strtol(s->value, &e, 0);
+
+               return (e > s->value && *e == 0 &&
+                       n <= s->stack[s->pos].value.number);
+       }
+
+       return false;
+}
+
+static bool
+dt_type_range(struct dt_state *s, int nargs)
+{
+       int n;
+       char *e;
+
+       if (nargs >= 2 &&
+           s->stack[s->pos].type == OP_NUMBER &&
+           s->stack[s->pos + 1].type == OP_NUMBER)
+       {
+               n = strtol(s->value, &e, 0);
+
+               return (e > s->value && *e == 0 &&
+                       n >= s->stack[s->pos].value.number &&
+                       n <= s->stack[s->pos + 1].value.number);
+       }
+
+       return false;
+}
+
+static bool
+dt_type_minlen(struct dt_state *s, int nargs)
+{
+       if (nargs >= 1 && s->stack[s->pos].type == OP_NUMBER)
+               return (strlen(s->value) >= s->stack[s->pos].value.number);
+
+       return false;
+}
+
+static bool
+dt_type_maxlen(struct dt_state *s, int nargs)
+{
+       if (nargs >= 1 && s->stack[s->pos].type == OP_NUMBER)
+               return (strlen(s->value) <= s->stack[s->pos].value.number);
+
+       return false;
+}
+
+static bool
+dt_type_rangelen(struct dt_state *s, int nargs)
+{
+       if (nargs >= 2 &&
+           s->stack[s->pos].type == OP_NUMBER &&
+           s->stack[s->pos + 1].type == OP_NUMBER)
+               return (strlen(s->value) >= s->stack[s->pos].value.number &&
+                               strlen(s->value) <= s->stack[s->pos + 1].value.number);
+
+       return false;
+}
+
+static bool
+dt_type_int(struct dt_state *s, int nargs)
+{
+       char *e;
+
+       strtol(s->value, &e, 0);
+
+       return (e > s->value && *e == 0);
+}
+
+static bool
+dt_type_uint(struct dt_state *s, int nargs)
+{
+       int n;
+       char *e;
+
+       n = strtol(s->value, &e, 0);
+
+       return (e > s->value && *e == 0 && n >= 0);
+}
+
+static bool
+dt_type_float(struct dt_state *s, int nargs)
+{
+       char *e;
+
+       strtod(s->value, &e);
+
+       return (e > s->value && *e == 0);
+}
+
+static bool
+dt_type_ufloat(struct dt_state *s, int nargs)
+{
+       int n;
+       char *e;
+
+       n = strtod(s->value, &e);
+
+       return (e > s->value && *e == 0 && n >= 0.0);
+}
+
+static bool
+dt_type_bool(struct dt_state *s, int nargs)
+{
+       int i;
+       const char *values[] = {
+               "0", "off", "false", "no",
+               "1", "on", "true", "yes"
+       };
+
+       for (i = 0; i < sizeof(values) / sizeof(values[0]); i++)
+               if (!strcasecmp(values[i], s->value))
+                       return true;
+
+       return false;
+}
+
+static bool
+dt_type_string(struct dt_state *s, int nargs)
+{
+       return true;
+}
+
+static bool
+dt_type_ip4addr(struct dt_state *s, int nargs)
+{
+       struct in6_addr a;
+       return inet_pton(AF_INET, s->value, &a);
+}
+
+static bool
+dt_type_ip6addr(struct dt_state *s, int nargs)
+{
+       struct in6_addr a;
+       return inet_pton(AF_INET6, s->value, &a);
+}
+
+static bool
+dt_type_ipaddr(struct dt_state *s, int nargs)
+{
+       return (dt_type_ip4addr(s, 0) || dt_type_ip6addr(s, 0));
+}
+
+static bool
+dt_type_netmask4(struct dt_state *s, int nargs)
+{
+       int i;
+       struct in_addr a;
+
+       if (!inet_pton(AF_INET, s->value, &a))
+               return false;
+
+       if (a.s_addr == 0)
+               return true;
+
+       a.s_addr = ntohl(a.s_addr);
+
+       for (i = 0; (i < 32) && !(a.s_addr & (1 << i)); i++);
+
+       return ((uint32_t)(~((1 << i) - 1)) == a.s_addr);
+}
+
+static bool
+dt_type_netmask6(struct dt_state *s, int nargs)
+{
+       int i;
+       struct in6_addr a;
+
+       if (!inet_pton(AF_INET6, s->value, &a))
+               return false;
+
+       for (i = 0; (i < 16) && (a.s6_addr[i] == 0xFF); i++);
+
+       if (i == 16)
+               return true;
+
+       if ((a.s6_addr[i] != 255) && (a.s6_addr[i] != 254) &&
+               (a.s6_addr[i] != 252) && (a.s6_addr[i] != 248) &&
+               (a.s6_addr[i] != 240) && (a.s6_addr[i] != 224) &&
+               (a.s6_addr[i] != 192) && (a.s6_addr[i] != 128) &&
+               (a.s6_addr[i] != 0))
+               return false;
+
+       for (; (i < 16) && (a.s6_addr[i] == 0); i++);
+
+       return (i == 16);
+}
+
+static bool
+dt_type_cidr4(struct dt_state *s, int nargs)
+{
+       int n;
+       struct in_addr a;
+       char *p, buf[sizeof("255.255.255.255/32\0")];
+
+       if (strlen(s->value) >= sizeof(buf))
+               return false;
+
+       strcpy(buf, s->value);
+       p = strchr(buf, '/');
+
+       if (p)
+       {
+               *p++ = 0;
+
+               n = strtoul(p, &p, 10);
+
+               if ((*p != 0) || (n > 32))
+                       return false;
+       }
+
+       return inet_pton(AF_INET, buf, &a);
+}
+
+static bool
+dt_type_cidr6(struct dt_state *s, int nargs)
+{
+       int n;
+       struct in6_addr a;
+       char *p, buf[sizeof("FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:255.255.255.255/128\0")];
+
+       if (strlen(s->value) >= sizeof(buf))
+               return false;
+
+       strcpy(buf, s->value);
+       p = strchr(buf, '/');
+
+       if (p)
+       {
+               *p++ = 0;
+
+               n = strtoul(p, &p, 10);
+
+               if ((*p != 0) || (n > 128))
+                       return false;
+       }
+
+       return inet_pton(AF_INET6, buf, &a);
+}
+
+static bool
+dt_type_cidr(struct dt_state *s, int nargs)
+{
+       return (dt_type_cidr4(s, 0) || dt_type_cidr6(s, 0));
+}
+
+static bool
+dt_type_ipmask4(struct dt_state *s, int nargs)
+{
+       bool rv;
+       struct in_addr a;
+       const char *value;
+       char *p, buf[sizeof("255.255.255.255/255.255.255.255\0")];
+
+       if (strlen(s->value) >= sizeof(buf))
+               return false;
+
+       strcpy(buf, s->value);
+       p = strchr(buf, '/');
+
+       if (p)
+       {
+               *p++ = 0;
+
+               value = s->value;
+               s->value = p;
+               rv = dt_type_netmask4(s, 0);
+               s->value = value;
+
+               if (!rv)
+                       return false;
+       }
+
+       return inet_pton(AF_INET, buf, &a);
+}
+
+static bool
+dt_type_ipmask6(struct dt_state *s, int nargs)
+{
+       bool rv;
+       struct in6_addr a;
+       const char *value;
+       char *p, buf[sizeof("FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:255.255.255.255/"
+                           "FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:255.255.255.255\0")];
+
+       if (strlen(s->value) >= sizeof(buf))
+               return false;
+
+       strcpy(buf, s->value);
+       p = strchr(buf, '/');
+
+       if (p)
+       {
+               *p++ = 0;
+
+               value = s->value;
+               s->value = p;
+               rv = dt_type_netmask6(s, 0);
+               s->value = value;
+
+               if (!rv)
+                       return false;
+       }
+
+       return inet_pton(AF_INET6, buf, &a);
+}
+
+static bool
+dt_type_ipmask(struct dt_state *s, int nargs)
+{
+       return (dt_type_ipmask4(s, 0) || dt_type_ipmask6(s, 0));
+}
+
+static bool
+dt_type_port(struct dt_state *s, int nargs)
+{
+       int n;
+       char *e;
+
+       n = strtoul(s->value, &e, 10);
+
+       return (e > s->value && *e == 0 && n <= 65535);
+}
+
+static bool
+dt_type_portrange(struct dt_state *s, int nargs)
+{
+       int n, m;
+       char *e;
+
+       n = strtoul(s->value, &e, 10);
+
+       if (e == s->value || *e != '-')
+               return false;
+
+       m = strtoul(e + 1, &e, 10);
+
+       return (*e == 0 && n <= 65535 && m <= 65535 && n <= m);
+}
+
+static bool
+dt_type_macaddr(struct dt_state *s, int nargs)
+{
+       return !!ether_aton(s->value);
+}
+
+static bool
+dt_type_uciname(struct dt_state *s, int nargs)
+{
+       const char *p;
+
+       for (p = s->value;
+            *p && ((*p >= 'A' && *p <= 'Z') || (*p >= 'a' && *p <= 'z') ||
+                   (*p >= '0' && *p <= '9') || (*p == '_'));
+                p++);
+
+       return (*p == 0);
+}
+
+static bool
+dt_type_wpakey(struct dt_state *s, int nargs)
+{
+       int len = strlen(s->value);
+       const char *p = s->value;
+
+       if (len == 64)
+       {
+               while (isxdigit(*p))
+                       p++;
+
+               return (*p == 0);
+       }
+
+       return (len >= 8 && len <= 63);
+}
+
+static bool
+dt_type_wepkey(struct dt_state *s, int nargs)
+{
+       int len = strlen(s->value);
+       const char *p = s->value;
+
+       if (!strncmp(p, "s:", 2))
+       {
+               len -= 2;
+               p += 2;
+       }
+
+       if (len == 10 || len == 26)
+       {
+               while (isxdigit(*p))
+                       p++;
+
+               return (*p == 0);
+       }
+
+       return (len == 5 || len == 13);
+}
+
+static bool
+dt_type_hostname(struct dt_state *s, int nargs)
+{
+       const char *p, *last;
+
+       for (p = last = s->value; *p; p++)
+       {
+               if (*p == '.')
+               {
+                       if ((p - last) == 0 || (p - last) > 63)
+                               return false;
+
+                       last = p + 1;
+                       continue;
+               }
+               else if ((*p >= 'A' && *p <= 'Z') || (*p >= 'a' && *p <= 'z') ||
+                        (*p >= '0' && *p <= '9') || (*p == '_') || (*p == '-'))
+               {
+                       continue;
+               }
+
+               return false;
+       }
+
+       return ((p - last) > 0 && (p - last) <= 255);
+}
+
+static bool
+dt_type_host(struct dt_state *s, int nargs)
+{
+       return (dt_type_hostname(s, 0) || dt_type_ipaddr(s, 0));
+}
+
+static bool
+dt_type_network(struct dt_state *s, int nargs)
+{
+       return (dt_type_uciname(s, 0) || dt_type_host(s, 0));
+}
+
+static bool
+dt_type_phonedigit(struct dt_state *s, int nargs)
+{
+       const char *p;
+
+       for (p = s->value;
+            *p && ((*p >= '0' && *p <= '9') || (*p == '*') || (*p == '#') ||
+                   (*p == '!') || (*p == '.'));
+                p++);
+
+       return (*p == 0);
+}
+
+static bool
+dt_type_directory(struct dt_state *s, int nargs)
+{
+       struct stat st;
+       return (!stat(s->value, &st) && S_ISDIR(st.st_mode));
+}
+
+
+static bool
+dt_type_device(struct dt_state *s, int nargs)
+{
+       struct stat st;
+       return (!stat(s->value, &st) &&
+               (S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode)));
+}
+
+static bool
+dt_type_file(struct dt_state *s, int nargs)
+{
+       struct stat st;
+       return (!stat(s->value, &st) && S_ISREG(st.st_mode));
+}
+
+
+static struct dt_fun dt_types[] = {
+       { "or",                         dt_type_or                      },
+       { "and",                        dt_type_and                     },
+       { "not",                        dt_type_not                     },
+       { "neg",                        dt_type_neg                     },
+       { "list",                       dt_type_list            },
+       { "min",                        dt_type_min                     },
+       { "max",                        dt_type_max                     },
+       { "range",                      dt_type_range           },
+       { "minlength",          dt_type_minlen          },
+       { "maxlength",          dt_type_maxlen          },
+       { "rangelength",        dt_type_rangelen        },
+       { "integer",            dt_type_int                     },
+       { "uinteger",           dt_type_uint            },
+       { "float",                      dt_type_float           },
+       { "ufloat",                     dt_type_ufloat          },
+       { "bool",                       dt_type_bool            },
+       { "string",                     dt_type_string          },
+       { "ip4addr",            dt_type_ip4addr         },
+       { "ip6addr",            dt_type_ip6addr         },
+       { "ipaddr",                     dt_type_ipaddr          },
+       { "cidr4",                      dt_type_cidr4           },
+       { "cidr6",                      dt_type_cidr6           },
+       { "cidr",                       dt_type_cidr            },
+       { "netmask4",           dt_type_netmask4        },
+       { "netmask6",           dt_type_netmask6        },
+       { "ipmask4",            dt_type_ipmask4         },
+       { "ipmask6",            dt_type_ipmask6         },
+       { "ipmask",                     dt_type_ipmask          },
+       { "port",                       dt_type_port            },
+       { "portrange",          dt_type_portrange       },
+       { "macaddr",            dt_type_macaddr         },
+       { "uciname",        dt_type_uciname             },
+       { "wpakey",                     dt_type_wpakey          },
+       { "wepkey",                     dt_type_wepkey          },
+       { "hostname",           dt_type_hostname        },
+       { "host",                       dt_type_host            },
+       { "network",            dt_type_network         },
+       { "phonedigit",         dt_type_phonedigit      },
+       { "directory",          dt_type_directory       },
+       { "device",                     dt_type_device          },
+       { "file",                       dt_type_file            },
+
+       { }
+};
+
+static struct dt_fun *
+dt_lookup_function(const char *s, const char *e)
+{
+       struct dt_fun *fun = dt_types;
+
+       while (fun->name)
+       {
+               if (!strncmp(fun->name, s, e - s) && *(fun->name + (e - s)) == '\0')
+                       return fun;
+
+               fun++;
+       }
+
+       return NULL;
+}
+
+static bool
+dt_parse_atom(struct dt_state *s, const char *label, const char *end)
+{
+       char q, *e;
+       const char *p;
+       bool esc;
+       double dval;
+       struct dt_fun *func;
+       struct dt_op *op = &s->stack[s->depth];
+
+       if ((s->depth + 1) >= (sizeof(s->stack) / sizeof(s->stack[0])))
+       {
+               printf("Syntax error, expression too long\n");
+               return false;
+       }
+
+       while (isspace(*label))
+               label++;
+
+       /* test whether label is a float */
+       dval = strtod(label, &e);
+
+       if (e > label)
+       {
+               op->next = e;
+               op->type = OP_NUMBER;
+               op->value.number = dval;
+               op->nextop = ++s->depth;
+
+               return true;
+       }
+       else if ((*label == '"') || (*label == '\''))
+       {
+               for (p = label + 1, q = *label, esc = false; p <= end; p++)
+               {
+                       if (esc)
+                       {
+                               esc = false;
+                               continue;
+                       }
+                       else if (*p == '\\')
+                       {
+                               esc = true;
+                               continue;
+                       }
+                       else if (*p == q)
+                       {
+                               op->next = p + 1;
+                               op->type = OP_STRING;
+                               op->length = (p - label) - 2;
+                               op->value.string = label + 1;
+                               op->nextop = ++s->depth;
+
+                               return true;
+                       }
+               }
+
+               printf("Syntax error, unterminated string\n");
+               return false;
+       }
+       else if (*label)
+       {
+               for (p = label;
+                    p <= end && ((*p >= 'A' && *p <= 'Z') ||
+                                 (*p >= 'a' && *p <= 'z') ||
+                                 (*p >= '0' && *p <= '9') ||
+                                 (*p == '_'));
+                    p++);
+
+               func = dt_lookup_function(label, p);
+
+               if (!func)
+               {
+                       printf("Syntax error, unrecognized function\n");
+                       return false;
+               }
+
+               op->next = p;
+               op->type = OP_FUNCTION;
+               op->value.function = func;
+               op->nextop = ++s->depth;
+
+               return true;
+       }
+
+       printf("Syntax error, unexpected EOF\n");
+       return false;
+}
+
+static bool
+dt_parse_list(struct dt_state *s, const char *code, const char *end);
+
+static bool
+dt_parse_expr(const char *code, const char *end, struct dt_state *s)
+{
+       struct dt_op *tok;
+
+       if (!dt_parse_atom(s, code, end))
+               return false;
+
+       tok = &s->stack[s->depth - 1];
+
+       while (isspace(*tok->next))
+               tok->next++;
+
+       if (tok->type == OP_FUNCTION)
+       {
+               if (*tok->next == '(')
+               {
+                       end--;
+
+                       while (isspace(*end) && end > tok->next + 1)
+                               end--;
+
+                       return dt_parse_list(s, tok->next + 1, end);
+               }
+               else if (tok->next == end)
+               {
+                       return dt_parse_list(s, tok->next, tok->next);
+               }
+
+               printf("Syntax error, expected '(' or EOF after function label\n");
+               return false;
+       }
+       else if (tok->next == end)
+       {
+               return true;
+       }
+
+       printf("Syntax error, expected ',' after literal\n");
+       return false;
+}
+
+static bool
+dt_parse_list(struct dt_state *s, const char *code, const char *end)
+{
+       char c;
+       bool esc;
+       int nest;
+       const char *p, *last;
+       struct dt_op *fptr;
+
+       if (!code)
+               return false;
+
+       fptr = &s->stack[s->depth - 1];
+
+       for (nest = 0, p = last = code, esc = false, c = *p;
+            p <= end;
+            p++, c = (p < end) ? *p : '\0')
+       {
+               if (esc)
+               {
+                       esc = false;
+                       continue;
+               }
+
+               switch (c)
+               {
+               case '\\':
+                       esc = true;
+                       break;
+
+               case '(':
+                       nest++;
+                       break;
+
+               case ')':
+                       nest--;
+                       break;
+
+               case ',':
+               case '\0':
+                       if (nest <= 0)
+                       {
+                               if (p > last)
+                               {
+                                       if (!dt_parse_expr(last, p, s))
+                                               return false;
+
+                                       fptr->length++;
+                               }
+
+                               last = p + 1;
+                       }
+
+                       break;
+               }
+       }
+
+       fptr->nextop = s->depth;
+       return true;
+}
+
+static bool
+dt_step(struct dt_state *s)
+{
+       bool rv;
+       struct dt_op *op = &s->stack[s->pos];
+
+       switch (op->type)
+       {
+       case OP_BOOL:
+               rv = op->value.boolean;
+               break;
+
+       case OP_NUMBER:
+               rv = dt_test_number(op->value.number, s->value);
+               break;
+
+       case OP_STRING:
+               rv = dt_test_string(op->value.string, op->value.string + op->length, s->value);
+               break;
+
+       case OP_FUNCTION:
+               rv = dt_call(s);
+               break;
+
+       default:
+               rv = false;
+               break;
+       }
+
+       s->pos = op->nextop;
+       return rv;
+}
+
+static bool
+dt_call(struct dt_state *s)
+{
+       bool rv;
+       struct dt_op *fptr = &s->stack[s->pos];
+       struct dt_fun *func = fptr->value.function;
+
+       s->pos++;
+
+       rv = func->call(s, fptr->length);
+
+       s->pos = fptr->nextop;
+
+       return rv;
+}
+
+bool
+dt_parse(const char *code, const char *value)
+{
+       struct dt_state s = {
+               .depth = 1,
+               .stack = {
+                       {
+                               .type = OP_FUNCTION,
+                               .value.function = &dt_types[0],
+                               .next = code
+                       }
+               }
+       };
+
+       if (!value || !*value)
+               return false;
+
+       if (!dt_parse_list(&s, code, code + strlen(code)))
+               return false;
+
+       s.value = value;
+
+       return dt_call(&s);
+}