diff --git a/Makefile b/Makefile
index ec15b0ab6ddbd883bc4ca0ed71b4eb9dd5c51c95..5951655ea65fa43749a2c9188f32ea4021833a97 100644
--- a/Makefile
+++ b/Makefile
@@ -29,7 +29,7 @@ $(MODULES):
 .PHONY: test
 test: all
 	LD_LIBRARY_PATH=build \
-		./parse_config test/*/*.conf
+		valgrind ./parse_config test/*/*.conf
 	LD_LIBRARY_PATH=build HOME=$$(pwd)/test \
 		valgrind -q --error-exitcode=1 test/test_c
 
diff --git a/moberg_config_parser.h b/moberg_config_parser.h
index ab68b415a3cb8af9f6f6c6fb8003c1f7e87405f5..faf48f104eef5d052572a612242c59f372607f8e 100644
--- a/moberg_config_parser.h
+++ b/moberg_config_parser.h
@@ -1,6 +1,8 @@
 #ifndef __MOBERG_CONFIG_PARSER_H__
 #define __MOBERG_CONFIG_PARSER_H__
 
+#include <stdio.h>
+
 struct moberg_config_parser_context;
 struct moberg_config_parser_token;
 
@@ -14,13 +16,6 @@ enum moberg_config_parser_token_kind {
   tok_COLON = ':',
   tok_SEMICOLON = ';',
   tok_INTEGER = 256,
-  tok_CONFIG,
-  tok_MAP,
-  tok_ANALOGIN,
-  tok_ANALOGOUT,
-  tok_DIGITALIN,
-  tok_DIGITALOUT,
-  tok_ENCODERIN,
   tok_IDENT,
   tok_STRING,
 };
@@ -46,8 +41,12 @@ int moberg_config_parser_acceptsym(
   enum moberg_config_parser_token_kind kind,
   struct moberg_config_parser_token *token);
 
-int moberg_config_parser_peeksym(
+int moberg_config_parser_acceptkeyword(
   struct moberg_config_parser_context *c,
-  struct moberg_config_parser_token *token);
+  const char *keyword);
+
+void moberg_config_parser_failed(
+  struct moberg_config_parser_context *c,
+  FILE *f);
 
 #endif
diff --git a/modules/comedi/comedi.c b/modules/comedi/comedi.c
index db2d6642d0d4c83a91e31377e978483630f9abc5..b91b0551326e583153c6636713832440cf397df9 100644
--- a/modules/comedi/comedi.c
+++ b/modules/comedi/comedi.c
@@ -4,11 +4,24 @@
 #include <stdlib.h>
 #include <string.h>
 
-#define acceptsym moberg_config_parser_acceptsym
-#define peeksym moberg_config_parser_peeksym
-
+typedef enum moberg_config_parser_token_kind kind_t;
 typedef struct moberg_config_parser_token token_t;
 typedef struct moberg_config_parser_ident ident_t;
+typedef struct moberg_config_parser_context context_t;
+
+static inline int acceptsym(context_t *c,
+			   kind_t kind,
+			   token_t *token)
+{
+  return moberg_config_parser_acceptsym(c, kind, token);
+}
+  
+static inline int acceptkeyword(context_t *c,
+				const char *keyword)
+{
+  return moberg_config_parser_acceptkeyword(c, keyword);
+}
+  
 
 static struct moberg_driver_config parse_config(
   struct moberg_config_parser_context *c)
@@ -20,30 +33,46 @@ static struct moberg_driver_config parse_config(
   for (;;) {
     if (acceptsym(c, tok_RBRACE, NULL)) {
 	break;
-    } else  if (acceptsym(c, tok_IDENT, &t) ||
-		acceptsym(c, tok_CONFIG, &t)) {
-      const char *v = t.u.ident.value;
-      int l = t.u.ident.length;
-      if (strncmp("device", v, l) == 0) {
-	printf("DEVICE\n");
-      } else if (strncmp("modprobe", v, l) == 0) {
-	printf("MODPROBE\n");
-      } else if (strncmp("config", v, l) == 0) {
-	printf("CONFIG\n");
+    } else if (acceptkeyword(c, "device")) {
+      token_t device;
+      if (! acceptsym(c, tok_EQUAL, NULL)) { goto err; }
+      if (! acceptsym(c, tok_STRING, &device)) { goto err; }
+      if (! acceptsym(c, tok_SEMICOLON, NULL)) { goto err; }
+    } else if (acceptkeyword(c, "config")) {
+      if (! acceptsym(c, tok_EQUAL, NULL)) { goto err; }
+      if (! acceptsym(c, tok_LBRACKET, NULL)) { goto err; }
+      for (;;) {
+	if (acceptsym(c, tok_RBRACKET, NULL)) {
+	  break;
+	} else if (acceptsym(c, tok_IDENT, NULL) ||
+		   acceptsym(c, tok_STRING, NULL)) {
+	} else {
+	  goto err;
+	}
       }
-      acceptsym(c, tok_EQUAL, NULL);
+      if (! acceptsym(c, tok_SEMICOLON, NULL)) { goto err; }
+    } else if (acceptkeyword(c, "modprobe")) {
+      if (! acceptsym(c, tok_EQUAL, NULL)) { goto err; }
+      if (! acceptsym(c, tok_LBRACKET, NULL)) { goto err; }
       for (;;) {
-	peeksym(c, &t);
-	acceptsym(c, t.kind, NULL);
-	printf("%d\n", t.kind);
-	if (t.kind == tok_SEMICOLON) { break; }
+	if (acceptsym(c, tok_RBRACKET, NULL)) {
+	  break;
+	} else if (acceptsym(c, tok_IDENT, NULL) ||
+		   acceptsym(c, tok_STRING, NULL)) {
+	} else {
+	  goto err;
+	}
       }
+      if (! acceptsym(c, tok_SEMICOLON, NULL)) { goto err; }
     } else {
-	break;
+      goto err;
     }
   }
   printf("PARSE_CONFIG DONE%s\n", __FILE__);
   return result;
+ err:
+  moberg_config_parser_failed(c, stderr);
+  exit(1);
 }
 
 static struct moberg_driver_map parse_map(
@@ -54,14 +83,10 @@ static struct moberg_driver_map parse_map(
 
   token_t t;
   printf("PARSE_MAP %s\n", __FILE__);
-  if (acceptsym(c, tok_IDENT, &t)) {
-    if (strncmp("subdevice", t.u.ident.value, t.u.ident.length) != 0) {
-      goto err;
-    }
-    if (! acceptsym(c, tok_LBRACKET, NULL)) { goto err; }
-    if (! acceptsym(c, tok_INTEGER, &t)) { goto err; }
-    if (! acceptsym(c, tok_RBRACKET, NULL)) { goto err; }
-  }
+  if (! acceptkeyword(c, "subdevice") != 0) {  goto err; }
+  if (! acceptsym(c, tok_LBRACKET, NULL)) { goto err; }
+  if (! acceptsym(c, tok_INTEGER, &t)) { goto err; }
+  if (! acceptsym(c, tok_RBRACKET, NULL)) { goto err; }
     
 /*
   const char *buf = context->buf;
@@ -72,6 +97,7 @@ static struct moberg_driver_map parse_map(
 */
   return result;
 err:
+  moberg_config_parser_failed(c, stderr);
   exit(1);
 }
 
diff --git a/parse_config.c b/parse_config.c
index cb7194431621cc5e164de1bad17431db23f2e245..f6f465918682802604925a6ffc776ea76a58bb13 100644
--- a/parse_config.c
+++ b/parse_config.c
@@ -8,21 +8,38 @@
 #include <moberg_config_parser.h>
 #include <moberg_driver.h>
 
-#define token moberg_config_parser_token
-#define token_kind moberg_config_parser_token_kind 
-#define acceptsym moberg_config_parser_acceptsym
-#define peeksym moberg_config_parser_peeksym
-
+typedef enum moberg_config_parser_token_kind kind_t;
 typedef struct moberg_config_parser_token token_t;
 typedef struct moberg_config_parser_ident ident_t;
+typedef struct moberg_config_parser_context context_t;
+
+static inline int acceptsym(context_t *c,
+			   kind_t kind,
+			   token_t *token)
+{
+  return moberg_config_parser_acceptsym(c, kind, token);
+}
+  
+static inline int acceptkeyword(context_t *c,
+				const char *keyword)
+{
+  return moberg_config_parser_acceptkeyword(c, keyword);
+}
+ 
+#define MAX_EXPECTED 10
+static char expected_char[256][2];
 
 typedef struct moberg_config_parser_context {
   char *buf;      /* Pointer to data to be parsed */
   const char *p;  /* current parse location */
   token_t token;
+  struct {
+    int n;
+    const char *what[MAX_EXPECTED];
+  } expected;
 } context_t;
 
-static const void nextsym_ident_or_keyword(context_t *c)
+static const void nextsym_ident(context_t *c)
 {
   c->token.u.ident.length = 1;
   c->token.u.ident.value = c->p;
@@ -44,23 +61,7 @@ static const void nextsym_ident_or_keyword(context_t *c)
 out: ;
   const char *v = c->token.u.ident.value;
   int l = c->token.u.ident.length;
-  if (strncmp("config", v, l) == 0) {
-    c->token.kind = tok_CONFIG;
-  } else if (strncmp("map", v, l) == 0) {
-    c->token.kind = tok_MAP;
-  } else if (strncmp("analog_in", v, l) == 0) {
-    c->token.kind = tok_ANALOGIN;
-  } else if (strncmp("analog_out", v, l) == 0) {
-    c->token.kind = tok_ANALOGOUT;
-  } else if (strncmp("digital_in", v, l) == 0) {
-    c->token.kind = tok_DIGITALIN;
-  } else if (strncmp("digital_out", v, l) == 0) {
-    c->token.kind = tok_DIGITALOUT;
-  } else if (strncmp("encoder_in", v, l) == 0) {
-    c->token.kind = tok_ENCODERIN;
-  } else {
-    c->token.kind = tok_IDENT;
-  }
+  c->token.kind = tok_IDENT;
   printf("IDENT: %.*s %d\n", l, v, c->token.kind);
 }
 
@@ -153,7 +154,7 @@ static int nextsym(context_t *c)
       case 'a'...'z':
       case 'A'...'Z':
       case '_':
-        nextsym_ident_or_keyword(c);
+        nextsym_ident(c);
         break;
       case '0'...'9':
         nextsym_integer(c);
@@ -172,31 +173,100 @@ static int nextsym(context_t *c)
   }
 }
 
+static int peeksym(context_t *c,
+		   kind_t kind,
+		   token_t *token)
+{
+  if (c->token.kind == kind) {
+    if (token) {
+      *token = c->token;
+    }
+    return 1;
+  }
+  return 0;
+}
+
 int moberg_config_parser_acceptsym(context_t *c,
-                                   enum token_kind kind,
+                                   kind_t kind,
                                    token_t *token)
 {
   if (c->token.kind == kind) {
-    printf("ACCEPT %d", c->token.kind);
+    printf("ACCEPT %d %s", c->token.kind, expected_char[kind]);
     if (token) {
       *token = c->token;
     }
     nextsym(c);
+    c->expected.n = 0;
     return 1;
   }
+  if (c->expected.n < MAX_EXPECTED) {
+    const char *what = NULL;
+    switch (kind) {
+    case tok_none: break;
+    case tok_LBRACE: what = "{"; break;
+    case tok_RBRACE: what = "}"; break;
+    case tok_LBRACKET: what = "["; break;
+    case tok_RBRACKET: what = "]"; break;
+    case tok_EQUAL: what = "="; break;
+    case tok_COLON: what = ":"; break;
+    case tok_SEMICOLON: what = "y;"; break;
+    case tok_INTEGER: what = "<INTEGER>"; break;
+    case tok_IDENT: what = "<IDENT>"; break;
+    case tok_STRING: what = "<STRING>"; break;
+    }
+    if (what) {
+      c->expected.what[c->expected.n] = what;
+      c->expected.n++;
+    }
+  }
   printf("REJECT %d (%d)", kind, c->token.kind);
   return 0;
 }
 
-int moberg_config_parser_peeksym(context_t *c,
-				 token_t *token)
+int moberg_config_parser_acceptkeyword(context_t *c,
+				       const char *keyword)
 {
-  if (token) {
-    *token = c->token;
+  token_t t;
+  if (peeksym(c, tok_IDENT, &t) &&
+      strncmp(keyword, t.u.ident.value, t.u.ident.length) == 0) {
+    nextsym(c);
+    c->expected.n = 0;
+    return 1;
   }
-  return *c->p != 0;
+  if (c->expected.n < MAX_EXPECTED) {
+    c->expected.what[c->expected.n] = keyword;
+    c->expected.n++;
+  }
+  return 0;
 }
 
+void moberg_config_parser_failed(
+  struct moberg_config_parser_context *c,
+  FILE *f)
+{
+  fprintf(f, "EXPECTED ");
+  for (int i = 0 ; i < c->expected.n ; i++) {
+    fprintf(f, "%s ", c->expected.what[i]);
+  }
+  const char *what = "";
+  switch (c->token.kind) {
+  case tok_none: break;
+  case tok_LBRACE: what = "{"; break;
+  case tok_RBRACE: what = "}"; break;
+  case tok_LBRACKET: what = "["; break;
+  case tok_RBRACKET: what = "]"; break;
+  case tok_EQUAL: what = "="; break;
+  case tok_COLON: what = ":"; break;
+  case tok_SEMICOLON: what = ";"; break;
+  case tok_INTEGER: what = "<INTEGER>"; break;
+  case tok_IDENT: what = "<IDENT>"; break;
+  case tok_STRING: what = "<STRING>"; break;
+  }
+  
+  fprintf(f, "\nGOT %s %s\n", what, c->p);
+}
+
+
 static int parse_map_range(context_t *c)
 {
   token_t low, high;
@@ -210,7 +280,8 @@ static int parse_map_range(context_t *c)
   if (! acceptsym(c, tok_RBRACKET, NULL)) { goto err; }
   return 1;
 err:
-  printf("OOPS");
+  moberg_config_parser_failed(c, stderr);
+  exit(1);
   return 0;
 }
 
@@ -218,36 +289,23 @@ static int parse_map(context_t *c,
                      struct moberg_driver *driver)
 {
   printf("parsemap");
-  struct token t;
-  if (acceptsym(c, tok_ANALOGIN, &t) ||
-      acceptsym(c, tok_ANALOGOUT, &t) ||
-      acceptsym(c, tok_DIGITALIN, &t) ||
-      acceptsym(c, tok_DIGITALOUT, &t) ||
-      acceptsym(c, tok_ENCODERIN, &t)) {
+  if (acceptkeyword(c, "analog_in") ||
+      acceptkeyword(c, "analog_out") ||
+      acceptkeyword(c, "digital_in") ||
+      acceptkeyword(c, "digital_out") ||
+      acceptkeyword(c, "encoder_in")) {
     if (! parse_map_range(c)) { goto err; }
     if (! acceptsym(c, tok_EQUAL, NULL)) { goto err; }
-    driver->module.parse_map(c, t.kind);
+    driver->module.parse_map(c, 0);
     if (! parse_map_range(c)) { goto err; }
     if (! acceptsym(c, tok_SEMICOLON, NULL)) { goto err; }
-    switch (t.kind) {
-      case tok_ANALOGIN:
-        
-      case tok_ANALOGOUT:
-        
-      case tok_DIGITALIN:
-
-      case tok_DIGITALOUT:
-        
-      case tok_ENCODERIN:
-        break;
-      default:
-        goto err;
-    }
   } else {
     goto err;
   }
   return 1;
 err:    
+  moberg_config_parser_failed(c, stderr);
+  exit(1);
   return 0;
 }
 
@@ -256,12 +314,9 @@ static int parse_device(context_t *c,
 {
   if (! acceptsym(c, tok_LBRACE, NULL)) { goto err; }
   for (;;) {
-    struct token t;
-    peeksym(c, &t);
-    printf("PEEK %d", t.kind);
-    if (acceptsym(c, tok_CONFIG, NULL)) {
+    if (acceptkeyword(c, "config")) {
       driver->module.parse_config(c);
-    } else if (acceptsym(c, tok_MAP, NULL)) {
+    } else if (acceptkeyword(c, "map")) {
       parse_map(c, driver);
     } else if (acceptsym(c, tok_RBRACE, NULL)) {
       break;
@@ -294,9 +349,7 @@ err:
 static int parse_config(context_t *c)
 {
   for (;;) {
-    struct token t;
-    peeksym(c, &t);
-    printf("PEEK %d", t.kind);
+    token_t t;
     if (acceptsym(c, tok_IDENT, &t)) {
       printf("DRIVER=%.*s\n", t.u.ident.length, t.u.ident.value);
       if (! parse_driver(c, t.u.ident)) { goto err; }
diff --git a/test/a/moberg.conf b/test/a/moberg.conf
index 4a3c7c736c7a10c03da26c44fd5282b33e7f5352..81092d1b963d747e1dd5e26d6b6b1849915a2aa8 100644
--- a/test/a/moberg.conf
+++ b/test/a/moberg.conf
@@ -2,7 +2,7 @@ comedi {
     config {
         /* Parsed by parse_config in libmoberg_comedi.so */
         device = "/dev/comedi0" ;
-        modprobe = [ comedi 8255 comedi_fc mite ni_tio ni_tiocmd ni_pcimio ] ;
+        modprobe = [ comedi "8255" comedi_fc mite ni_tio ni_tiocmd ni_pcimio ] ;
         config = [ ni_pcimio ] ;
     }
     /* Moberg mapping[indices] = {driver specific}[indices]
@@ -11,7 +11,7 @@ comedi {
     map analog_out[0:1] = subdevice[1][0:1] ;
     map digital_in[0] = subdevice[7][15] ;
     map digital_in[1] = subdevice[7][2] ;
-    map digital_out[0:1] = subdevice[7][0:1] route 16 ;
+    map digital_out[0:1] = subdevice[7] route 16 [0:1];
 }
 serial2002 {
     config {