From 3673bb524736d5888bff400cb85c201791d17e69 Mon Sep 17 00:00:00 2001
From: Anders Blomdell <anders.blomdell@gmail.com>
Date: Fri, 15 Feb 2019 21:49:49 +0100
Subject: [PATCH] More parsing work

---
 Makefile                |  15 +++++++------
 moberg_config_parser.h  |  30 ++++++++++++++++----------
 modules/comedi/comedi.c |  46 +++++++++++++++++++++++++++++++++-------
 parse_config.c          |  37 +++++++++++++++++++++++++++++++-
 test/a/moberg.conf      |   4 ++--
 test/test_c             | Bin 20904 -> 0 bytes
 6 files changed, 103 insertions(+), 29 deletions(-)
 delete mode 100755 test/test_c

diff --git a/Makefile b/Makefile
index 77e25af..ec15b0a 100644
--- a/Makefile
+++ b/Makefile
@@ -3,7 +3,7 @@ CCFLAGS+=-Wall -Werror -I. -g
 LDFLAGS+=-Lbuild/ -lmoberg
 MODULES:=$(wildcard modules/*)
 
-LDFLAGS_parse_config=-ldl moberg_driver.o
+LDFLAGS_parse_config=-ldl -export-dynamic
 
 all: $(LIBRARIES:%=build/%) $(MODULES) test/test_c parse_config
 	echo $(MODULES)
@@ -14,11 +14,11 @@ build/libmoberg.so:	moberg.c Makefile | build
 build:
 	mkdir $@
 
-%:	%.o
-	$(CC) $(LDFLAGS) $(LDFLAGS_$(*)) -o $@  $<
+%:	build/%.o Makefile
+	$(CC) $(LDFLAGS) $(LDFLAGS_$(*)) -o $@  $(filter %.o,$^)
 
-%.o:	%.c
-	$(CC) $(CCFLAGS) -c  $<
+build/%.o:	%.c Makefile
+	$(CC) $(CCFLAGS) -c -o $@ $<
 
 
 .PHONY: $(MODULES)
@@ -40,5 +40,6 @@ clean:
 	rm build/*
 
 
-parse_config: moberg_config_parser.h
-parse_config: moberg_driver.o
+build/parse_config.o: moberg_config_parser.h
+parse_config: build/moberg_driver.o
+parse_config: build/parse_config.o
diff --git a/moberg_config_parser.h b/moberg_config_parser.h
index 806af0d..ab68b41 100644
--- a/moberg_config_parser.h
+++ b/moberg_config_parser.h
@@ -7,13 +7,13 @@ struct moberg_config_parser_token;
 enum moberg_config_parser_token_kind {
   tok_none,
   tok_LBRACE = '{',
-  tok_RBRACE,
-  tok_LBRACKET,
-  tok_RBRACKET,
-  tok_EQUAL,
-  tok_COLON,
-  tok_SEMICOLON,
-  tok_INTEGER,
+  tok_RBRACE = '}',
+  tok_LBRACKET = '[',
+  tok_RBRACKET = ']',
+  tok_EQUAL = '=',
+  tok_COLON = ':',
+  tok_SEMICOLON = ';',
+  tok_INTEGER = 256,
   tok_CONFIG,
   tok_MAP,
   tok_ANALOGIN,
@@ -22,15 +22,19 @@ enum moberg_config_parser_token_kind {
   tok_DIGITALOUT,
   tok_ENCODERIN,
   tok_IDENT,
+  tok_STRING,
+};
+
+struct moberg_config_parser_ident {
+  int length;
+  const char *value;
 };
 
 struct moberg_config_parser_token {
   enum moberg_config_parser_token_kind kind;
   union {
-    struct moberg_config_parser_ident {
-      int length;
-      const char *value;
-    } ident;
+    struct moberg_config_parser_ident ident;
+    struct moberg_config_parser_ident string;
     struct moberg_config_parser_integer {
       int value;
     } integer;
@@ -42,4 +46,8 @@ int moberg_config_parser_acceptsym(
   enum moberg_config_parser_token_kind kind,
   struct moberg_config_parser_token *token);
 
+int moberg_config_parser_peeksym(
+  struct moberg_config_parser_context *c,
+  struct moberg_config_parser_token *token);
+
 #endif
diff --git a/modules/comedi/comedi.c b/modules/comedi/comedi.c
index a34e526..c40aef5 100644
--- a/modules/comedi/comedi.c
+++ b/modules/comedi/comedi.c
@@ -1,20 +1,50 @@
 #include <moberg_config_parser.h>
 #include <moberg_driver.h>
 #include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define acceptsym moberg_config_parser_acceptsym
+#define peeksym moberg_config_parser_peeksym
+
+typedef struct moberg_config_parser_token token_t;
+typedef struct moberg_config_parser_ident ident_t;
 
 static struct moberg_driver_config parse_config(
-  struct moberg_config_parser_context *context)
+  struct moberg_config_parser_context *c)
 {
   struct moberg_driver_config result;
-
+  token_t t;
   printf("PARSE_CONFIG %s\n", __FILE__);
-/*
-  const char *buf = context->buf;
-  while (*buf && *buf != '}') {
-    buf++;
+  printf("LBRACE %d", acceptsym(c, tok_LBRACE, &t));
+  for (;;) {
+    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("comedi", v, l) == 0) {
+	printf("CONFIG\n");
+      }
+      acceptsym(c, tok_EQUAL, NULL);
+      for (;;) {
+	peeksym(c, &t);
+	acceptsym(c, t.kind, NULL);
+	printf("%d\n", t.kind);
+	if (t.kind == tok_SEMICOLON) { break; }
+      }
+    } else if (acceptsym(c, tok_RBRACE, NULL)) {
+	break;
+    } else {
+      goto err;
+    }
   }
-  context->buf = buf + 1;
-*/
+  return result;
+err:
+  exit(1);
   return result;
 }
 
diff --git a/parse_config.c b/parse_config.c
index 45b6f65..1f65d46 100644
--- a/parse_config.c
+++ b/parse_config.c
@@ -65,6 +65,7 @@ out: ;
 
 static const void nextsym_integer(context_t *c)
 {
+  c->token.kind = tok_INTEGER;
   c->token.u.integer.value = 0;
   while (*c->p && '0' <= *c->p && *c->p <= '9') {
     c->token.u.integer.value *= 10;
@@ -74,6 +75,27 @@ static const void nextsym_integer(context_t *c)
   printf("INTEGER: %d\n", c->token.u.integer.value);
 }
 
+static const void nextsym_string(context_t *c)
+{
+  printf("STTRING");
+  c->token.kind = tok_STRING;
+  c->p++;
+  c->token.u.string.value = c->p;
+  c->token.u.string.length = 0;
+  while (*c->p && *c->p != '"') {
+    if (*c->p == '\\') {
+      c->token.u.string.length++;
+      c->p++;
+    }
+    if (*c->p) {
+      c->token.u.string.length++;
+      c->p++;
+    }
+  }
+  c->p++;
+  printf("STRING: %.*s\n", c->token.u.string.length, c->token.u.string.value);
+}
+
 static int nextsym(context_t *c)
 {
   c->token.kind = tok_none;
@@ -124,6 +146,9 @@ static int nextsym(context_t *c)
         c->token.kind = tok_SEMICOLON;
         c->p++;
         break;
+      case '"':
+        nextsym_string(c);
+        break;
       case 'a'...'z':
       case 'A'...'Z':
       case '_':
@@ -138,7 +163,7 @@ static int nextsym(context_t *c)
         break;
     }
   }
-  printf("TOKEN %d\n\n", c->token.kind);
+  printf("TOKEN %d %c\n\n", c->token.kind, c->token.kind<255?c->token.kind:' ');
   if (c->token.kind != tok_none) {
     return 1;
   } else {
@@ -160,6 +185,15 @@ int moberg_config_parser_acceptsym(context_t *c,
   return 0;
 }
 
+int moberg_config_parser_peeksym(context_t *c,
+				 token_t *token)
+{
+  if (token) {
+    *token = c->token;
+  }
+  return *c->p != 0;
+}
+
 static int parse_map_range(context_t *c)
 {
   token_t low, high;
@@ -260,6 +294,7 @@ static int parse_config(context_t *c)
   return 1;
 err:
   printf("Failed!!");
+  exit (1);
   return 0;
 }
 
diff --git a/test/a/moberg.conf b/test/a/moberg.conf
index 39fedd1..270ba54 100644
--- a/test/a/moberg.conf
+++ b/test/a/moberg.conf
@@ -1,7 +1,7 @@
 comedi {
     config {
         /* Parsed by parse_config in libmoberg_comedi.so */
-        device = /dev/comedi0 ;
+        device = "/dev/comedi0" ;
         modprobe = [ comedi 8255 comedi_fc mite ni_tio ni_tiocmd ni_pcimio ] ;
         config = [ ni_pcimio ] ;
     }
@@ -12,7 +12,7 @@ comedi {
 serial2002 {
     config {
         /* Parsed by parse_config in libmoberg_serial2002.so */
-        device = /dev/ttyS0 ;
+        device = "/dev/ttyS0" ;
         baud = 115200 ;
     }
     /* Moberg mapping[indices] = {driver specific}[indices]
diff --git a/test/test_c b/test/test_c
deleted file mode 100755
index c438e6bf1ca8839f32a19270463fce4a7e5704a8..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 20904
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
M14AU51~e`M03chkq5uE@

-- 
GitLab