ambrop7 14 лет назад
Родитель
Сommit
d21f852b9f

+ 4 - 0
examples/ncd_parser_test.c

@@ -68,6 +68,10 @@ static void print_list (struct NCDConfig_list *l, unsigned int indent)
                 printf("list\n");
                 print_list(l->list, indent + 1);
             } break;
+            case NCDCONFIG_ARG_MAPLIST: {
+                printf("maplist\n");
+                print_list(l->list, indent + 1);
+            } break;
             default:
                 ASSERT(0);
         }

+ 9 - 0
examples/ncd_tokenizer_test.c

@@ -87,6 +87,15 @@ static int tokenizer_output (void *user, int token, char *value, size_t line, si
         case NCD_TOKEN_TEMPLATE:
             printf("template\n");
             break;
+        case NCD_TOKEN_COLON:
+            printf("colon\n");
+            break;
+        case NCD_TOKEN_BRACKET_OPEN:
+            printf("bracket open\n");
+            break;
+        case NCD_TOKEN_BRACKET_CLOSE:
+            printf("bracket close\n");
+            break;
         default:
             ASSERT(0);
     }

+ 274 - 182
generated/NCDConfigParser_parse.c

@@ -70,18 +70,18 @@ struct parser_out {
 **                       defined, then do no error processing.
 */
 #define YYCODETYPE unsigned char
-#define YYNOCODE 25
+#define YYNOCODE 30
 #define YYACTIONTYPE unsigned char
 #define ParseTOKENTYPE void *
 typedef union {
   int yyinit;
   ParseTOKENTYPE yy0;
-  char * yy1;
-  struct NCDConfig_list * yy14;
-  struct NCDConfig_processes * yy21;
-  int yy28;
-  struct NCDConfig_statements * yy38;
-  struct NCDConfig_strings * yy44;
+  char * yy5;
+  struct NCDConfig_list * yy12;
+  struct NCDConfig_statements * yy16;
+  struct NCDConfig_strings * yy24;
+  struct NCDConfig_processes * yy45;
+  int yy46;
 } YYMINORTYPE;
 #ifndef YYSTACKDEPTH
 #define YYSTACKDEPTH 0
@@ -90,8 +90,8 @@ typedef union {
 #define ParseARG_PDECL ,struct parser_out *parser_out
 #define ParseARG_FETCH struct parser_out *parser_out = yypParser->parser_out
 #define ParseARG_STORE yypParser->parser_out = parser_out
-#define YYNSTATE 40
-#define YYNRULE 22
+#define YYNSTATE 50
+#define YYNRULE 27
 #define YY_NO_ACTION      (YYNSTATE+YYNRULE+2)
 #define YY_ACCEPT_ACTION  (YYNSTATE+YYNRULE+1)
 #define YY_ERROR_ACTION   (YYNSTATE+YYNRULE)
@@ -161,39 +161,49 @@ static const YYMINORTYPE yyzerominor = { 0 };
 **  yy_default[]       Default action for each state.
 */
 static const YYACTIONTYPE yy_action[] = {
- /*     0 */    37,   18,   32,   38,   21,   37,   24,   32,   38,   21,
- /*    10 */    20,    3,   34,   37,   20,   22,   38,   21,   37,   36,
- /*    20 */    33,   38,   21,   31,   14,   19,   20,    3,   27,   28,
- /*    30 */    26,   30,   15,   63,   23,   36,   17,   13,   15,   40,
- /*    40 */    25,   16,   29,   13,   39,   13,    6,    1,    5,   10,
- /*    50 */    11,    9,   35,    7,    2,   64,   12,    4,    8,
+ /*     0 */    44,   21,   38,   45,   23,   46,   24,   44,   30,   38,
+ /*    10 */    45,   32,   46,   24,   23,    3,   40,   44,   37,   25,
+ /*    20 */    45,   18,   46,   24,    4,   22,   43,   44,   23,    3,
+ /*    30 */    45,   28,   46,   26,   33,   34,   20,   16,    4,   47,
+ /*    40 */    43,   36,   44,   29,   39,   45,   17,   46,   24,   44,
+ /*    50 */    23,    3,   45,   42,   46,   26,   18,   78,   44,   31,
+ /*    60 */     4,   45,   43,   46,   27,   35,   16,    1,   49,   16,
+ /*    70 */    14,   50,    9,   19,   12,   13,    8,    5,   41,   79,
+ /*    80 */    10,   79,   79,    2,   79,   79,    7,    6,   79,   48,
+ /*    90 */    11,   79,   15,
 };
 static const YYCODETYPE yy_lookahead[] = {
- /*     0 */    16,   17,   18,   19,   20,   16,   17,   18,   19,   20,
- /*    10 */     1,    2,    3,   16,    1,   18,   19,   20,   16,   10,
- /*    20 */    18,   19,   20,    1,   14,   21,    1,    2,   11,   12,
- /*    30 */    14,   16,   22,   23,   16,   10,   15,   16,   22,    0,
- /*    40 */    21,    1,   15,   16,   15,   16,    2,    4,    3,    8,
- /*    50 */     7,    5,    3,    6,    4,   24,    5,    9,    6,
+ /*     0 */    19,   20,   21,   22,    1,   24,   25,   19,   20,   21,
+ /*    10 */    22,   17,   24,   25,    1,    2,    3,   19,    1,   21,
+ /*    20 */    22,   27,   24,   25,   11,   26,   13,   19,    1,    2,
+ /*    30 */    22,   23,   24,   25,   14,   15,   18,   19,   11,   12,
+ /*    40 */    13,   19,   19,   19,   21,   22,   17,   24,   25,   19,
+ /*    50 */     1,    2,   22,   23,   24,   25,   27,   28,   19,   26,
+ /*    60 */    11,   22,   13,   24,   25,   18,   19,    4,   18,   19,
+ /*    70 */     7,    0,    2,    1,    5,    8,    3,    9,    3,   29,
+ /*    80 */     6,   29,   29,    4,   29,   29,   10,    9,   29,   12,
+ /*    90 */     6,   29,    5,
 };
 #define YY_SHIFT_USE_DFLT (-1)
-#define YY_SHIFT_MAX 25
+#define YY_SHIFT_MAX 31
 static const signed char yy_shift_ofst[] = {
- /*     0 */    17,   25,   25,    9,   25,   17,   13,   13,   13,   22,
- /*    10 */    13,   13,   22,   43,   39,   40,   44,   45,   46,   47,
- /*    20 */    41,   48,   49,   50,   51,   52,
+ /*     0 */    20,   49,   49,   13,   27,   49,   49,   49,   20,    3,
+ /*    10 */     3,    3,   17,    3,    3,   17,   63,   71,   72,   70,
+ /*    20 */    73,   69,   74,   67,   68,   75,   76,   78,   77,   79,
+ /*    30 */    87,   84,
 };
-#define YY_REDUCE_USE_DFLT (-17)
-#define YY_REDUCE_MAX 12
+#define YY_REDUCE_USE_DFLT (-20)
+#define YY_REDUCE_MAX 15
 static const signed char yy_reduce_ofst[] = {
- /*     0 */    10,  -16,  -11,   -3,    2,   16,   21,   27,   29,    4,
- /*    10 */    15,   18,   19,
+ /*     0 */    29,  -19,  -12,   -2,    8,   23,   30,   39,   -6,   18,
+ /*    10 */    47,   50,   -1,   22,   24,   33,
 };
 static const YYACTIONTYPE yy_default[] = {
- /*     0 */    62,   49,   49,   62,   62,   41,   62,   43,   45,   58,
- /*    10 */    62,   62,   58,   62,   62,   62,   62,   62,   62,   62,
- /*    20 */    47,   51,   62,   62,   62,   62,   42,   60,   61,   44,
- /*    30 */    48,   59,   50,   52,   53,   54,   55,   56,   57,   46,
+ /*     0 */    77,   59,   59,   77,   77,   77,   77,   77,   51,   77,
+ /*    10 */    53,   55,   73,   77,   77,   73,   77,   77,   77,   77,
+ /*    20 */    77,   77,   77,   57,   61,   77,   77,   65,   77,   77,
+ /*    30 */    77,   77,   52,   75,   76,   54,   58,   74,   60,   62,
+ /*    40 */    63,   64,   66,   69,   70,   71,   72,   67,   68,   56,
 };
 #define YY_SZ_ACTTAB (int)(sizeof(yy_action)/sizeof(yy_action[0]))
 
@@ -289,10 +299,12 @@ void ParseTrace(FILE *TraceFILE, char *zTracePrompt){
 static const char *const yyTokenName[] = { 
   "$",             "NAME",          "CURLY_OPEN",    "CURLY_CLOSE", 
   "ROUND_OPEN",    "ROUND_CLOSE",   "SEMICOLON",     "ARROW",       
-  "DOT",           "COMMA",         "STRING",        "PROCESS",     
-  "TEMPLATE",      "error",         "processes",     "statements",  
-  "statement_names",  "statement_args_maybe",  "list_contents",  "list",        
-  "value",         "name_maybe",    "process_or_template",  "input",       
+  "DOT",           "COMMA",         "COLON",         "BRACKET_OPEN",
+  "BRACKET_CLOSE",  "STRING",        "PROCESS",       "TEMPLATE",    
+  "error",         "processes",     "statements",    "statement_names",
+  "statement_args_maybe",  "list_contents",  "list",          "map_contents",
+  "map",           "value",         "name_maybe",    "process_or_template",
+  "input",       
 };
 #endif /* NDEBUG */
 
@@ -315,13 +327,18 @@ static const char *const yyRuleName[] = {
  /*  12 */ "list_contents ::= value COMMA list_contents",
  /*  13 */ "list ::= CURLY_OPEN CURLY_CLOSE",
  /*  14 */ "list ::= CURLY_OPEN list_contents CURLY_CLOSE",
- /*  15 */ "value ::= STRING",
- /*  16 */ "value ::= statement_names",
- /*  17 */ "value ::= list",
- /*  18 */ "name_maybe ::=",
- /*  19 */ "name_maybe ::= NAME",
- /*  20 */ "process_or_template ::= PROCESS",
- /*  21 */ "process_or_template ::= TEMPLATE",
+ /*  15 */ "map_contents ::= value COLON value",
+ /*  16 */ "map_contents ::= value COLON value COMMA map_contents",
+ /*  17 */ "map ::= BRACKET_OPEN BRACKET_CLOSE",
+ /*  18 */ "map ::= BRACKET_OPEN map_contents BRACKET_CLOSE",
+ /*  19 */ "value ::= STRING",
+ /*  20 */ "value ::= statement_names",
+ /*  21 */ "value ::= list",
+ /*  22 */ "value ::= map",
+ /*  23 */ "name_maybe ::=",
+ /*  24 */ "name_maybe ::= NAME",
+ /*  25 */ "process_or_template ::= PROCESS",
+ /*  26 */ "process_or_template ::= TEMPLATE",
 };
 #endif /* NDEBUG */
 
@@ -410,51 +427,56 @@ static void yy_destructor(
     case 7: /* ARROW */
     case 8: /* DOT */
     case 9: /* COMMA */
-    case 10: /* STRING */
-    case 11: /* PROCESS */
-    case 12: /* TEMPLATE */
+    case 10: /* COLON */
+    case 11: /* BRACKET_OPEN */
+    case 12: /* BRACKET_CLOSE */
+    case 13: /* STRING */
+    case 14: /* PROCESS */
+    case 15: /* TEMPLATE */
 {
 #line 50 "NCDConfigParser_parse.y"
  free((yypminor->yy0)); 
-#line 420 "NCDConfigParser_parse.c"
+#line 440 "NCDConfigParser_parse.c"
 }
       break;
-    case 14: /* processes */
+    case 17: /* processes */
 {
-#line 62 "NCDConfigParser_parse.y"
- NCDConfig_free_processes((yypminor->yy21)); 
-#line 427 "NCDConfigParser_parse.c"
+#line 64 "NCDConfigParser_parse.y"
+ NCDConfig_free_processes((yypminor->yy45)); 
+#line 447 "NCDConfigParser_parse.c"
 }
       break;
-    case 15: /* statements */
+    case 18: /* statements */
 {
-#line 63 "NCDConfigParser_parse.y"
- NCDConfig_free_statements((yypminor->yy38)); 
-#line 434 "NCDConfigParser_parse.c"
+#line 65 "NCDConfigParser_parse.y"
+ NCDConfig_free_statements((yypminor->yy16)); 
+#line 454 "NCDConfigParser_parse.c"
 }
       break;
-    case 16: /* statement_names */
+    case 19: /* statement_names */
 {
-#line 64 "NCDConfigParser_parse.y"
- NCDConfig_free_strings((yypminor->yy44)); 
-#line 441 "NCDConfigParser_parse.c"
+#line 66 "NCDConfigParser_parse.y"
+ NCDConfig_free_strings((yypminor->yy24)); 
+#line 461 "NCDConfigParser_parse.c"
 }
       break;
-    case 17: /* statement_args_maybe */
-    case 18: /* list_contents */
-    case 19: /* list */
-    case 20: /* value */
+    case 20: /* statement_args_maybe */
+    case 21: /* list_contents */
+    case 22: /* list */
+    case 23: /* map_contents */
+    case 24: /* map */
+    case 25: /* value */
 {
-#line 65 "NCDConfigParser_parse.y"
- NCDConfig_free_list((yypminor->yy14)); 
-#line 451 "NCDConfigParser_parse.c"
+#line 67 "NCDConfigParser_parse.y"
+ NCDConfig_free_list((yypminor->yy12)); 
+#line 473 "NCDConfigParser_parse.c"
 }
       break;
-    case 21: /* name_maybe */
+    case 26: /* name_maybe */
 {
-#line 69 "NCDConfigParser_parse.y"
- free((yypminor->yy1)); 
-#line 458 "NCDConfigParser_parse.c"
+#line 73 "NCDConfigParser_parse.y"
+ free((yypminor->yy5)); 
+#line 480 "NCDConfigParser_parse.c"
 }
       break;
     default:  break;   /* If no destructor action specified: do nothing */
@@ -627,12 +649,12 @@ static void yyStackOverflow(yyParser *yypParser, YYMINORTYPE *yypMinor){
    while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser);
    /* Here code is inserted which will execute if the parser
    ** stack every overflows */
-#line 78 "NCDConfigParser_parse.y"
+#line 82 "NCDConfigParser_parse.y"
 
     if (yypMinor) {
         free(yypMinor->yy0);
     }
-#line 636 "NCDConfigParser_parse.c"
+#line 658 "NCDConfigParser_parse.c"
    ParseARG_STORE; /* Suppress warning about unused %extra_argument var */
 }
 
@@ -689,28 +711,33 @@ static const struct {
   YYCODETYPE lhs;         /* Symbol on the left-hand side of the rule */
   unsigned char nrhs;     /* Number of right-hand side symbols in the rule */
 } yyRuleInfo[] = {
-  { 23, 1 },
-  { 14, 5 },
-  { 14, 6 },
-  { 15, 6 },
-  { 15, 7 },
-  { 15, 8 },
-  { 15, 9 },
-  { 16, 1 },
-  { 16, 3 },
-  { 17, 0 },
-  { 17, 1 },
-  { 18, 1 },
-  { 18, 3 },
-  { 19, 2 },
+  { 28, 1 },
+  { 17, 5 },
+  { 17, 6 },
+  { 18, 6 },
+  { 18, 7 },
+  { 18, 8 },
+  { 18, 9 },
+  { 19, 1 },
   { 19, 3 },
+  { 20, 0 },
   { 20, 1 },
-  { 20, 1 },
-  { 20, 1 },
-  { 21, 0 },
   { 21, 1 },
-  { 22, 1 },
-  { 22, 1 },
+  { 21, 3 },
+  { 22, 2 },
+  { 22, 3 },
+  { 23, 3 },
+  { 23, 5 },
+  { 24, 2 },
+  { 24, 3 },
+  { 25, 1 },
+  { 25, 1 },
+  { 25, 1 },
+  { 25, 1 },
+  { 26, 0 },
+  { 26, 1 },
+  { 27, 1 },
+  { 27, 1 },
 };
 
 static void yy_accept(yyParser*);  /* Forward Declaration */
@@ -766,71 +793,71 @@ static void yy_reduce(
   **     break;
   */
       case 0: /* input ::= processes */
-#line 84 "NCDConfigParser_parse.y"
+#line 88 "NCDConfigParser_parse.y"
 {
-    parser_out->ast = yymsp[0].minor.yy21;
+    parser_out->ast = yymsp[0].minor.yy45;
 
-    if (!yymsp[0].minor.yy21) {
+    if (!yymsp[0].minor.yy45) {
         parser_out->out_of_memory = 1;
     }
 }
-#line 778 "NCDConfigParser_parse.c"
+#line 805 "NCDConfigParser_parse.c"
         break;
       case 1: /* processes ::= process_or_template NAME CURLY_OPEN statements CURLY_CLOSE */
-#line 92 "NCDConfigParser_parse.y"
+#line 96 "NCDConfigParser_parse.y"
 {
-    yygotominor.yy21 = NCDConfig_make_processes(yymsp[-4].minor.yy28, yymsp[-3].minor.yy0, yymsp[-1].minor.yy38, 0, NULL);
-    if (!yygotominor.yy21) {
+    yygotominor.yy45 = NCDConfig_make_processes(yymsp[-4].minor.yy46, yymsp[-3].minor.yy0, yymsp[-1].minor.yy16, 0, NULL);
+    if (!yygotominor.yy45) {
         parser_out->out_of_memory = 1;
     }
   yy_destructor(yypParser,2,&yymsp[-2].minor);
   yy_destructor(yypParser,3,&yymsp[0].minor);
 }
-#line 790 "NCDConfigParser_parse.c"
+#line 817 "NCDConfigParser_parse.c"
         break;
       case 2: /* processes ::= process_or_template NAME CURLY_OPEN statements CURLY_CLOSE processes */
-#line 99 "NCDConfigParser_parse.y"
+#line 103 "NCDConfigParser_parse.y"
 {
-    yygotominor.yy21 = NCDConfig_make_processes(yymsp[-5].minor.yy28, yymsp[-4].minor.yy0, yymsp[-2].minor.yy38, 1, yymsp[0].minor.yy21);
-    if (!yygotominor.yy21) {
+    yygotominor.yy45 = NCDConfig_make_processes(yymsp[-5].minor.yy46, yymsp[-4].minor.yy0, yymsp[-2].minor.yy16, 1, yymsp[0].minor.yy45);
+    if (!yygotominor.yy45) {
         parser_out->out_of_memory = 1;
     }
   yy_destructor(yypParser,2,&yymsp[-3].minor);
   yy_destructor(yypParser,3,&yymsp[-1].minor);
 }
-#line 802 "NCDConfigParser_parse.c"
+#line 829 "NCDConfigParser_parse.c"
         break;
       case 3: /* statements ::= statement_names ROUND_OPEN statement_args_maybe ROUND_CLOSE name_maybe SEMICOLON */
-#line 106 "NCDConfigParser_parse.y"
+#line 110 "NCDConfigParser_parse.y"
 {
-    yygotominor.yy38 = NCDConfig_make_statements(NULL, yymsp[-5].minor.yy44, yymsp[-3].minor.yy14, yymsp[-1].minor.yy1, NULL);
-    if (!yygotominor.yy38) {
+    yygotominor.yy16 = NCDConfig_make_statements(NULL, yymsp[-5].minor.yy24, yymsp[-3].minor.yy12, yymsp[-1].minor.yy5, NULL);
+    if (!yygotominor.yy16) {
         parser_out->out_of_memory = 1;
     }
   yy_destructor(yypParser,4,&yymsp[-4].minor);
   yy_destructor(yypParser,5,&yymsp[-2].minor);
   yy_destructor(yypParser,6,&yymsp[0].minor);
 }
-#line 815 "NCDConfigParser_parse.c"
+#line 842 "NCDConfigParser_parse.c"
         break;
       case 4: /* statements ::= statement_names ROUND_OPEN statement_args_maybe ROUND_CLOSE name_maybe SEMICOLON statements */
-#line 113 "NCDConfigParser_parse.y"
+#line 117 "NCDConfigParser_parse.y"
 {
-    yygotominor.yy38 = NCDConfig_make_statements(NULL, yymsp[-6].minor.yy44, yymsp[-4].minor.yy14, yymsp[-2].minor.yy1, yymsp[0].minor.yy38);
-    if (!yygotominor.yy38) {
+    yygotominor.yy16 = NCDConfig_make_statements(NULL, yymsp[-6].minor.yy24, yymsp[-4].minor.yy12, yymsp[-2].minor.yy5, yymsp[0].minor.yy16);
+    if (!yygotominor.yy16) {
         parser_out->out_of_memory = 1;
     }
   yy_destructor(yypParser,4,&yymsp[-5].minor);
   yy_destructor(yypParser,5,&yymsp[-3].minor);
   yy_destructor(yypParser,6,&yymsp[-1].minor);
 }
-#line 828 "NCDConfigParser_parse.c"
+#line 855 "NCDConfigParser_parse.c"
         break;
       case 5: /* statements ::= statement_names ARROW statement_names ROUND_OPEN statement_args_maybe ROUND_CLOSE name_maybe SEMICOLON */
-#line 120 "NCDConfigParser_parse.y"
+#line 124 "NCDConfigParser_parse.y"
 {
-    yygotominor.yy38 = NCDConfig_make_statements(yymsp[-7].minor.yy44, yymsp[-5].minor.yy44, yymsp[-3].minor.yy14, yymsp[-1].minor.yy1, NULL);
-    if (!yygotominor.yy38) {
+    yygotominor.yy16 = NCDConfig_make_statements(yymsp[-7].minor.yy24, yymsp[-5].minor.yy24, yymsp[-3].minor.yy12, yymsp[-1].minor.yy5, NULL);
+    if (!yygotominor.yy16) {
         parser_out->out_of_memory = 1;
     }
   yy_destructor(yypParser,7,&yymsp[-6].minor);
@@ -838,13 +865,13 @@ static void yy_reduce(
   yy_destructor(yypParser,5,&yymsp[-2].minor);
   yy_destructor(yypParser,6,&yymsp[0].minor);
 }
-#line 842 "NCDConfigParser_parse.c"
+#line 869 "NCDConfigParser_parse.c"
         break;
       case 6: /* statements ::= statement_names ARROW statement_names ROUND_OPEN statement_args_maybe ROUND_CLOSE name_maybe SEMICOLON statements */
-#line 127 "NCDConfigParser_parse.y"
+#line 131 "NCDConfigParser_parse.y"
 {
-    yygotominor.yy38 = NCDConfig_make_statements(yymsp[-8].minor.yy44, yymsp[-6].minor.yy44, yymsp[-4].minor.yy14, yymsp[-2].minor.yy1, yymsp[0].minor.yy38);
-    if (!yygotominor.yy38) {
+    yygotominor.yy16 = NCDConfig_make_statements(yymsp[-8].minor.yy24, yymsp[-6].minor.yy24, yymsp[-4].minor.yy12, yymsp[-2].minor.yy5, yymsp[0].minor.yy16);
+    if (!yygotominor.yy16) {
         parser_out->out_of_memory = 1;
     }
   yy_destructor(yypParser,7,&yymsp[-7].minor);
@@ -852,135 +879,200 @@ static void yy_reduce(
   yy_destructor(yypParser,5,&yymsp[-3].minor);
   yy_destructor(yypParser,6,&yymsp[-1].minor);
 }
-#line 856 "NCDConfigParser_parse.c"
+#line 883 "NCDConfigParser_parse.c"
         break;
       case 7: /* statement_names ::= NAME */
-#line 134 "NCDConfigParser_parse.y"
+#line 138 "NCDConfigParser_parse.y"
 {
-    yygotominor.yy44 = NCDConfig_make_strings(yymsp[0].minor.yy0, 0, NULL);
-    if (!yygotominor.yy44) {
+    yygotominor.yy24 = NCDConfig_make_strings(yymsp[0].minor.yy0, 0, NULL);
+    if (!yygotominor.yy24) {
         parser_out->out_of_memory = 1;
     }
 }
-#line 866 "NCDConfigParser_parse.c"
+#line 893 "NCDConfigParser_parse.c"
         break;
       case 8: /* statement_names ::= NAME DOT statement_names */
-#line 141 "NCDConfigParser_parse.y"
+#line 145 "NCDConfigParser_parse.y"
 {
-    yygotominor.yy44 = NCDConfig_make_strings(yymsp[-2].minor.yy0, 1, yymsp[0].minor.yy44);
-    if (!yygotominor.yy44) {
+    yygotominor.yy24 = NCDConfig_make_strings(yymsp[-2].minor.yy0, 1, yymsp[0].minor.yy24);
+    if (!yygotominor.yy24) {
         parser_out->out_of_memory = 1;
     }
   yy_destructor(yypParser,8,&yymsp[-1].minor);
 }
-#line 877 "NCDConfigParser_parse.c"
+#line 904 "NCDConfigParser_parse.c"
         break;
       case 9: /* statement_args_maybe ::= */
-#line 148 "NCDConfigParser_parse.y"
+#line 152 "NCDConfigParser_parse.y"
 {
-    yygotominor.yy14 = NULL;
+    yygotominor.yy12 = NULL;
 }
-#line 884 "NCDConfigParser_parse.c"
+#line 911 "NCDConfigParser_parse.c"
         break;
       case 10: /* statement_args_maybe ::= list_contents */
       case 11: /* list_contents ::= value */ yytestcase(yyruleno==11);
-#line 152 "NCDConfigParser_parse.y"
+#line 156 "NCDConfigParser_parse.y"
 {
-    yygotominor.yy14 = yymsp[0].minor.yy14;
+    yygotominor.yy12 = yymsp[0].minor.yy12;
 }
-#line 892 "NCDConfigParser_parse.c"
+#line 919 "NCDConfigParser_parse.c"
         break;
       case 12: /* list_contents ::= value COMMA list_contents */
-#line 160 "NCDConfigParser_parse.y"
+#line 164 "NCDConfigParser_parse.y"
 {
-    if (!yymsp[-2].minor.yy14) {
-        NCDConfig_free_list(yymsp[0].minor.yy14);
+    if (!yymsp[-2].minor.yy12) {
+        NCDConfig_free_list(yymsp[0].minor.yy12);
     } else {
-        ASSERT(!yymsp[-2].minor.yy14->next)
-        yymsp[-2].minor.yy14->next = yymsp[0].minor.yy14;
+        ASSERT(!yymsp[-2].minor.yy12->next)
+        yymsp[-2].minor.yy12->next = yymsp[0].minor.yy12;
     }
-    yygotominor.yy14 = yymsp[-2].minor.yy14;
+    yygotominor.yy12 = yymsp[-2].minor.yy12;
   yy_destructor(yypParser,9,&yymsp[-1].minor);
 }
-#line 906 "NCDConfigParser_parse.c"
+#line 933 "NCDConfigParser_parse.c"
         break;
       case 13: /* list ::= CURLY_OPEN CURLY_CLOSE */
-#line 170 "NCDConfigParser_parse.y"
+#line 174 "NCDConfigParser_parse.y"
 {
-    yygotominor.yy14 = NULL;
+    yygotominor.yy12 = NULL;
   yy_destructor(yypParser,2,&yymsp[-1].minor);
   yy_destructor(yypParser,3,&yymsp[0].minor);
 }
-#line 915 "NCDConfigParser_parse.c"
+#line 942 "NCDConfigParser_parse.c"
         break;
       case 14: /* list ::= CURLY_OPEN list_contents CURLY_CLOSE */
-#line 174 "NCDConfigParser_parse.y"
+#line 178 "NCDConfigParser_parse.y"
 {
-    yygotominor.yy14 = yymsp[-1].minor.yy14;
+    yygotominor.yy12 = yymsp[-1].minor.yy12;
   yy_destructor(yypParser,2,&yymsp[-2].minor);
   yy_destructor(yypParser,3,&yymsp[0].minor);
 }
-#line 924 "NCDConfigParser_parse.c"
+#line 951 "NCDConfigParser_parse.c"
         break;
-      case 15: /* value ::= STRING */
-#line 178 "NCDConfigParser_parse.y"
+      case 15: /* map_contents ::= value COLON value */
+#line 182 "NCDConfigParser_parse.y"
 {
-    yygotominor.yy14 = NCDConfig_make_list_string(yymsp[0].minor.yy0, NULL);
-    if (!yygotominor.yy14) {
+    if (!yymsp[-2].minor.yy12 || !yymsp[0].minor.yy12) {
+        NCDConfig_free_list(yymsp[-2].minor.yy12);
+        NCDConfig_free_list(yymsp[0].minor.yy12);
+        yygotominor.yy12 = NULL;
+    } else {
+        ASSERT(!yymsp[-2].minor.yy12->next)
+        ASSERT(!yymsp[0].minor.yy12->next)
+        yymsp[-2].minor.yy12->next = yymsp[0].minor.yy12;
+        yygotominor.yy12 = yymsp[-2].minor.yy12;
+    }
+  yy_destructor(yypParser,10,&yymsp[-1].minor);
+}
+#line 968 "NCDConfigParser_parse.c"
+        break;
+      case 16: /* map_contents ::= value COLON value COMMA map_contents */
+#line 195 "NCDConfigParser_parse.y"
+{
+    if (!yymsp[-4].minor.yy12 || !yymsp[-2].minor.yy12) {
+        NCDConfig_free_list(yymsp[-4].minor.yy12);
+        NCDConfig_free_list(yymsp[-2].minor.yy12);
+        NCDConfig_free_list(yymsp[0].minor.yy12);
+        yygotominor.yy12 = NULL;
+    } else {
+        ASSERT(!yymsp[-4].minor.yy12->next)
+        ASSERT(!yymsp[-2].minor.yy12->next)
+        yymsp[-4].minor.yy12->next = yymsp[-2].minor.yy12;
+        yymsp[-2].minor.yy12->next = yymsp[0].minor.yy12;
+        yygotominor.yy12 = yymsp[-4].minor.yy12;
+    }
+  yy_destructor(yypParser,10,&yymsp[-3].minor);
+  yy_destructor(yypParser,9,&yymsp[-1].minor);
+}
+#line 988 "NCDConfigParser_parse.c"
+        break;
+      case 17: /* map ::= BRACKET_OPEN BRACKET_CLOSE */
+#line 210 "NCDConfigParser_parse.y"
+{
+    yygotominor.yy12 = NULL;
+  yy_destructor(yypParser,11,&yymsp[-1].minor);
+  yy_destructor(yypParser,12,&yymsp[0].minor);
+}
+#line 997 "NCDConfigParser_parse.c"
+        break;
+      case 18: /* map ::= BRACKET_OPEN map_contents BRACKET_CLOSE */
+#line 214 "NCDConfigParser_parse.y"
+{
+    yygotominor.yy12 = yymsp[-1].minor.yy12;
+  yy_destructor(yypParser,11,&yymsp[-2].minor);
+  yy_destructor(yypParser,12,&yymsp[0].minor);
+}
+#line 1006 "NCDConfigParser_parse.c"
+        break;
+      case 19: /* value ::= STRING */
+#line 218 "NCDConfigParser_parse.y"
+{
+    yygotominor.yy12 = NCDConfig_make_list_string(yymsp[0].minor.yy0, NULL);
+    if (!yygotominor.yy12) {
         parser_out->out_of_memory = 1;
     }
 }
-#line 934 "NCDConfigParser_parse.c"
+#line 1016 "NCDConfigParser_parse.c"
         break;
-      case 16: /* value ::= statement_names */
-#line 185 "NCDConfigParser_parse.y"
+      case 20: /* value ::= statement_names */
+#line 225 "NCDConfigParser_parse.y"
 {
-    yygotominor.yy14 = NCDConfig_make_list_var(yymsp[0].minor.yy44, NULL);
-    if (!yygotominor.yy14) {
+    yygotominor.yy12 = NCDConfig_make_list_var(yymsp[0].minor.yy24, NULL);
+    if (!yygotominor.yy12) {
         parser_out->out_of_memory = 1;
     }
 }
-#line 944 "NCDConfigParser_parse.c"
+#line 1026 "NCDConfigParser_parse.c"
         break;
-      case 17: /* value ::= list */
-#line 192 "NCDConfigParser_parse.y"
+      case 21: /* value ::= list */
+#line 232 "NCDConfigParser_parse.y"
 {
-    yygotominor.yy14 = NCDConfig_make_list_list(yymsp[0].minor.yy14, NULL);
-    if (!yygotominor.yy14) {
+    yygotominor.yy12 = NCDConfig_make_list_list(yymsp[0].minor.yy12, NULL);
+    if (!yygotominor.yy12) {
         parser_out->out_of_memory = 1;
     }
 }
-#line 954 "NCDConfigParser_parse.c"
+#line 1036 "NCDConfigParser_parse.c"
         break;
-      case 18: /* name_maybe ::= */
-#line 199 "NCDConfigParser_parse.y"
+      case 22: /* value ::= map */
+#line 239 "NCDConfigParser_parse.y"
 {
-    yygotominor.yy1 = NULL;
+    yygotominor.yy12 = NCDConfig_make_list_maplist(yymsp[0].minor.yy12, NULL);
+    if (!yygotominor.yy12) {
+        parser_out->out_of_memory = 1;
+    }
 }
-#line 961 "NCDConfigParser_parse.c"
+#line 1046 "NCDConfigParser_parse.c"
         break;
-      case 19: /* name_maybe ::= NAME */
-#line 203 "NCDConfigParser_parse.y"
+      case 23: /* name_maybe ::= */
+#line 246 "NCDConfigParser_parse.y"
 {
-    yygotominor.yy1 = yymsp[0].minor.yy0;
+    yygotominor.yy5 = NULL;
 }
-#line 968 "NCDConfigParser_parse.c"
+#line 1053 "NCDConfigParser_parse.c"
         break;
-      case 20: /* process_or_template ::= PROCESS */
-#line 207 "NCDConfigParser_parse.y"
+      case 24: /* name_maybe ::= NAME */
+#line 250 "NCDConfigParser_parse.y"
 {
-    yygotominor.yy28 = 0;
-  yy_destructor(yypParser,11,&yymsp[0].minor);
+    yygotominor.yy5 = yymsp[0].minor.yy0;
 }
-#line 976 "NCDConfigParser_parse.c"
+#line 1060 "NCDConfigParser_parse.c"
         break;
-      case 21: /* process_or_template ::= TEMPLATE */
-#line 211 "NCDConfigParser_parse.y"
+      case 25: /* process_or_template ::= PROCESS */
+#line 254 "NCDConfigParser_parse.y"
 {
-    yygotominor.yy28 = 1;
-  yy_destructor(yypParser,12,&yymsp[0].minor);
+    yygotominor.yy46 = 0;
+  yy_destructor(yypParser,14,&yymsp[0].minor);
 }
-#line 984 "NCDConfigParser_parse.c"
+#line 1068 "NCDConfigParser_parse.c"
+        break;
+      case 26: /* process_or_template ::= TEMPLATE */
+#line 258 "NCDConfigParser_parse.y"
+{
+    yygotominor.yy46 = 1;
+  yy_destructor(yypParser,15,&yymsp[0].minor);
+}
+#line 1076 "NCDConfigParser_parse.c"
         break;
       default:
         break;
@@ -1042,10 +1134,10 @@ static void yy_syntax_error(
 ){
   ParseARG_FETCH;
 #define TOKEN (yyminor.yy0)
-#line 73 "NCDConfigParser_parse.y"
+#line 77 "NCDConfigParser_parse.y"
 
     parser_out->syntax_error = 1;
-#line 1049 "NCDConfigParser_parse.c"
+#line 1141 "NCDConfigParser_parse.c"
   ParseARG_STORE; /* Suppress warning about unused %extra_argument variable */
 }
 

+ 6 - 3
generated/NCDConfigParser_parse.h

@@ -7,6 +7,9 @@
 #define ARROW                           7
 #define DOT                             8
 #define COMMA                           9
-#define STRING                         10
-#define PROCESS                        11
-#define TEMPLATE                       12
+#define COLON                          10
+#define BRACKET_OPEN                   11
+#define BRACKET_CLOSE                  12
+#define STRING                         13
+#define PROCESS                        14
+#define TEMPLATE                       15

+ 265 - 127
generated/NCDConfigParser_parse.out

@@ -5,10 +5,10 @@ State 0:
           process_or_template ::= * PROCESS
           process_or_template ::= * TEMPLATE
 
-                       PROCESS shift  27
-                      TEMPLATE shift  28
-                     processes shift  14
-           process_or_template shift  15
+                       PROCESS shift  33
+                      TEMPLATE shift  34
+                     processes shift  17
+           process_or_template shift  18
                          input accept
 
 State 1:
@@ -22,18 +22,23 @@ State 1:
           list_contents ::= * value COMMA list_contents
           list ::= * CURLY_OPEN CURLY_CLOSE
           list ::= * CURLY_OPEN list_contents CURLY_CLOSE
+          map ::= * BRACKET_OPEN BRACKET_CLOSE
+          map ::= * BRACKET_OPEN map_contents BRACKET_CLOSE
           value ::= * STRING
           value ::= * statement_names
           value ::= * list
+          value ::= * map
 
-                          NAME shift  20
+                          NAME shift  23
                     CURLY_OPEN shift  3
-                        STRING shift  36
-               statement_names shift  37
-          statement_args_maybe shift  18
-                 list_contents shift  32
-                          list shift  38
-                         value shift  21
+                  BRACKET_OPEN shift  4
+                        STRING shift  43
+               statement_names shift  44
+          statement_args_maybe shift  21
+                 list_contents shift  38
+                          list shift  45
+                           map shift  46
+                         value shift  24
                      {default} reduce 9
 
 State 2:
@@ -47,18 +52,23 @@ State 2:
           list_contents ::= * value COMMA list_contents
           list ::= * CURLY_OPEN CURLY_CLOSE
           list ::= * CURLY_OPEN list_contents CURLY_CLOSE
+          map ::= * BRACKET_OPEN BRACKET_CLOSE
+          map ::= * BRACKET_OPEN map_contents BRACKET_CLOSE
           value ::= * STRING
           value ::= * statement_names
           value ::= * list
+          value ::= * map
 
-                          NAME shift  20
+                          NAME shift  23
                     CURLY_OPEN shift  3
-                        STRING shift  36
-               statement_names shift  37
-          statement_args_maybe shift  24
-                 list_contents shift  32
-                          list shift  38
-                         value shift  21
+                  BRACKET_OPEN shift  4
+                        STRING shift  43
+               statement_names shift  44
+          statement_args_maybe shift  30
+                 list_contents shift  38
+                          list shift  45
+                           map shift  46
+                         value shift  24
                      {default} reduce 9
 
 State 3:
@@ -70,20 +80,52 @@ State 3:
           list ::= CURLY_OPEN * CURLY_CLOSE
           list ::= * CURLY_OPEN list_contents CURLY_CLOSE
           list ::= CURLY_OPEN * list_contents CURLY_CLOSE
+          map ::= * BRACKET_OPEN BRACKET_CLOSE
+          map ::= * BRACKET_OPEN map_contents BRACKET_CLOSE
           value ::= * STRING
           value ::= * statement_names
           value ::= * list
+          value ::= * map
 
-                          NAME shift  20
+                          NAME shift  23
                     CURLY_OPEN shift  3
-                   CURLY_CLOSE shift  34
-                        STRING shift  36
-               statement_names shift  37
-                 list_contents shift  22
-                          list shift  38
-                         value shift  21
+                   CURLY_CLOSE shift  40
+                  BRACKET_OPEN shift  4
+                        STRING shift  43
+               statement_names shift  44
+                 list_contents shift  25
+                          list shift  45
+                           map shift  46
+                         value shift  24
 
 State 4:
+          statement_names ::= * NAME
+          statement_names ::= * NAME DOT statement_names
+          list ::= * CURLY_OPEN CURLY_CLOSE
+          list ::= * CURLY_OPEN list_contents CURLY_CLOSE
+          map_contents ::= * value COLON value
+          map_contents ::= * value COLON value COMMA map_contents
+          map ::= * BRACKET_OPEN BRACKET_CLOSE
+          map ::= BRACKET_OPEN * BRACKET_CLOSE
+          map ::= * BRACKET_OPEN map_contents BRACKET_CLOSE
+          map ::= BRACKET_OPEN * map_contents BRACKET_CLOSE
+          value ::= * STRING
+          value ::= * statement_names
+          value ::= * list
+          value ::= * map
+
+                          NAME shift  23
+                    CURLY_OPEN shift  3
+                  BRACKET_OPEN shift  4
+                 BRACKET_CLOSE shift  47
+                        STRING shift  43
+               statement_names shift  44
+                          list shift  45
+                  map_contents shift  28
+                           map shift  46
+                         value shift  26
+
+State 5:
           statement_names ::= * NAME
           statement_names ::= * NAME DOT statement_names
           list_contents ::= * value
@@ -91,19 +133,72 @@ State 4:
           list_contents ::= value COMMA * list_contents
           list ::= * CURLY_OPEN CURLY_CLOSE
           list ::= * CURLY_OPEN list_contents CURLY_CLOSE
+          map ::= * BRACKET_OPEN BRACKET_CLOSE
+          map ::= * BRACKET_OPEN map_contents BRACKET_CLOSE
           value ::= * STRING
           value ::= * statement_names
           value ::= * list
+          value ::= * map
 
-                          NAME shift  20
+                          NAME shift  23
                     CURLY_OPEN shift  3
-                        STRING shift  36
-               statement_names shift  37
-                 list_contents shift  33
-                          list shift  38
-                         value shift  21
+                  BRACKET_OPEN shift  4
+                        STRING shift  43
+               statement_names shift  44
+                 list_contents shift  39
+                          list shift  45
+                           map shift  46
+                         value shift  24
 
-State 5:
+State 6:
+          statement_names ::= * NAME
+          statement_names ::= * NAME DOT statement_names
+          list ::= * CURLY_OPEN CURLY_CLOSE
+          list ::= * CURLY_OPEN list_contents CURLY_CLOSE
+          map_contents ::= * value COLON value
+          map_contents ::= * value COLON value COMMA map_contents
+          map_contents ::= value COLON value COMMA * map_contents
+          map ::= * BRACKET_OPEN BRACKET_CLOSE
+          map ::= * BRACKET_OPEN map_contents BRACKET_CLOSE
+          value ::= * STRING
+          value ::= * statement_names
+          value ::= * list
+          value ::= * map
+
+                          NAME shift  23
+                    CURLY_OPEN shift  3
+                  BRACKET_OPEN shift  4
+                        STRING shift  43
+               statement_names shift  44
+                          list shift  45
+                  map_contents shift  42
+                           map shift  46
+                         value shift  26
+
+State 7:
+          statement_names ::= * NAME
+          statement_names ::= * NAME DOT statement_names
+          list ::= * CURLY_OPEN CURLY_CLOSE
+          list ::= * CURLY_OPEN list_contents CURLY_CLOSE
+          map_contents ::= value COLON * value
+          map_contents ::= value COLON * value COMMA map_contents
+          map ::= * BRACKET_OPEN BRACKET_CLOSE
+          map ::= * BRACKET_OPEN map_contents BRACKET_CLOSE
+          value ::= * STRING
+          value ::= * statement_names
+          value ::= * list
+          value ::= * map
+
+                          NAME shift  23
+                    CURLY_OPEN shift  3
+                  BRACKET_OPEN shift  4
+                        STRING shift  43
+               statement_names shift  44
+                          list shift  45
+                           map shift  46
+                         value shift  27
+
+State 8:
           processes ::= * process_or_template NAME CURLY_OPEN statements CURLY_CLOSE
       (1) processes ::= process_or_template NAME CURLY_OPEN statements CURLY_CLOSE *
           processes ::= * process_or_template NAME CURLY_OPEN statements CURLY_CLOSE processes
@@ -111,13 +206,13 @@ State 5:
           process_or_template ::= * PROCESS
           process_or_template ::= * TEMPLATE
 
-                       PROCESS shift  27
-                      TEMPLATE shift  28
-                     processes shift  26
-           process_or_template shift  15
+                       PROCESS shift  33
+                      TEMPLATE shift  34
+                     processes shift  32
+           process_or_template shift  18
                      {default} reduce 1
 
-State 6:
+State 9:
           processes ::= process_or_template NAME CURLY_OPEN * statements CURLY_CLOSE
           processes ::= process_or_template NAME CURLY_OPEN * statements CURLY_CLOSE processes
           statements ::= * statement_names ROUND_OPEN statement_args_maybe ROUND_CLOSE name_maybe SEMICOLON
@@ -127,11 +222,11 @@ State 6:
           statement_names ::= * NAME
           statement_names ::= * NAME DOT statement_names
 
-                          NAME shift  20
-                    statements shift  17
-               statement_names shift  13
+                          NAME shift  23
+                    statements shift  20
+               statement_names shift  16
 
-State 7:
+State 10:
           statements ::= * statement_names ROUND_OPEN statement_args_maybe ROUND_CLOSE name_maybe SEMICOLON
       (3) statements ::= statement_names ROUND_OPEN statement_args_maybe ROUND_CLOSE name_maybe SEMICOLON *
           statements ::= * statement_names ROUND_OPEN statement_args_maybe ROUND_CLOSE name_maybe SEMICOLON statements
@@ -141,12 +236,12 @@ State 7:
           statement_names ::= * NAME
           statement_names ::= * NAME DOT statement_names
 
-                          NAME shift  20
-                    statements shift  29
-               statement_names shift  13
+                          NAME shift  23
+                    statements shift  35
+               statement_names shift  16
                      {default} reduce 3
 
-State 8:
+State 11:
           statements ::= * statement_names ROUND_OPEN statement_args_maybe ROUND_CLOSE name_maybe SEMICOLON
           statements ::= * statement_names ROUND_OPEN statement_args_maybe ROUND_CLOSE name_maybe SEMICOLON statements
           statements ::= * statement_names ARROW statement_names ROUND_OPEN statement_args_maybe ROUND_CLOSE name_maybe SEMICOLON
@@ -156,195 +251,233 @@ State 8:
           statement_names ::= * NAME
           statement_names ::= * NAME DOT statement_names
 
-                          NAME shift  20
-                    statements shift  39
-               statement_names shift  13
+                          NAME shift  23
+                    statements shift  49
+               statement_names shift  16
                      {default} reduce 5
 
-State 9:
+State 12:
           statements ::= statement_names ROUND_OPEN statement_args_maybe ROUND_CLOSE * name_maybe SEMICOLON
           statements ::= statement_names ROUND_OPEN statement_args_maybe ROUND_CLOSE * name_maybe SEMICOLON statements
-     (18) name_maybe ::= *
+     (23) name_maybe ::= *
           name_maybe ::= * NAME
 
-                          NAME shift  31
-                    name_maybe shift  19
-                     {default} reduce 18
+                          NAME shift  37
+                    name_maybe shift  22
+                     {default} reduce 23
 
-State 10:
+State 13:
           statement_names ::= * NAME
           statement_names ::= * NAME DOT statement_names
           statement_names ::= NAME DOT * statement_names
 
-                          NAME shift  20
-               statement_names shift  30
+                          NAME shift  23
+               statement_names shift  36
 
-State 11:
+State 14:
           statements ::= statement_names ARROW * statement_names ROUND_OPEN statement_args_maybe ROUND_CLOSE name_maybe SEMICOLON
           statements ::= statement_names ARROW * statement_names ROUND_OPEN statement_args_maybe ROUND_CLOSE name_maybe SEMICOLON statements
           statement_names ::= * NAME
           statement_names ::= * NAME DOT statement_names
 
-                          NAME shift  20
-               statement_names shift  23
+                          NAME shift  23
+               statement_names shift  29
 
-State 12:
+State 15:
           statements ::= statement_names ARROW statement_names ROUND_OPEN statement_args_maybe ROUND_CLOSE * name_maybe SEMICOLON
           statements ::= statement_names ARROW statement_names ROUND_OPEN statement_args_maybe ROUND_CLOSE * name_maybe SEMICOLON statements
-     (18) name_maybe ::= *
+     (23) name_maybe ::= *
           name_maybe ::= * NAME
 
-                          NAME shift  31
-                    name_maybe shift  25
-                     {default} reduce 18
+                          NAME shift  37
+                    name_maybe shift  31
+                     {default} reduce 23
 
-State 13:
+State 16:
           statements ::= statement_names * ROUND_OPEN statement_args_maybe ROUND_CLOSE name_maybe SEMICOLON
           statements ::= statement_names * ROUND_OPEN statement_args_maybe ROUND_CLOSE name_maybe SEMICOLON statements
           statements ::= statement_names * ARROW statement_names ROUND_OPEN statement_args_maybe ROUND_CLOSE name_maybe SEMICOLON
           statements ::= statement_names * ARROW statement_names ROUND_OPEN statement_args_maybe ROUND_CLOSE name_maybe SEMICOLON statements
 
                     ROUND_OPEN shift  1
-                         ARROW shift  11
+                         ARROW shift  14
 
-State 14:
+State 17:
       (0) input ::= processes *
 
                              $ reduce 0
 
-State 15:
+State 18:
           processes ::= process_or_template * NAME CURLY_OPEN statements CURLY_CLOSE
           processes ::= process_or_template * NAME CURLY_OPEN statements CURLY_CLOSE processes
 
-                          NAME shift  16
+                          NAME shift  19
 
-State 16:
+State 19:
           processes ::= process_or_template NAME * CURLY_OPEN statements CURLY_CLOSE
           processes ::= process_or_template NAME * CURLY_OPEN statements CURLY_CLOSE processes
 
-                    CURLY_OPEN shift  6
+                    CURLY_OPEN shift  9
 
-State 17:
+State 20:
           processes ::= process_or_template NAME CURLY_OPEN statements * CURLY_CLOSE
           processes ::= process_or_template NAME CURLY_OPEN statements * CURLY_CLOSE processes
 
-                   CURLY_CLOSE shift  5
+                   CURLY_CLOSE shift  8
 
-State 18:
+State 21:
           statements ::= statement_names ROUND_OPEN statement_args_maybe * ROUND_CLOSE name_maybe SEMICOLON
           statements ::= statement_names ROUND_OPEN statement_args_maybe * ROUND_CLOSE name_maybe SEMICOLON statements
 
-                   ROUND_CLOSE shift  9
+                   ROUND_CLOSE shift  12
 
-State 19:
+State 22:
           statements ::= statement_names ROUND_OPEN statement_args_maybe ROUND_CLOSE name_maybe * SEMICOLON
           statements ::= statement_names ROUND_OPEN statement_args_maybe ROUND_CLOSE name_maybe * SEMICOLON statements
 
-                     SEMICOLON shift  7
+                     SEMICOLON shift  10
 
-State 20:
+State 23:
       (7) statement_names ::= NAME *
           statement_names ::= NAME * DOT statement_names
 
-                           DOT shift  10
+                           DOT shift  13
                      {default} reduce 7
 
-State 21:
+State 24:
      (11) list_contents ::= value *
           list_contents ::= value * COMMA list_contents
 
-                         COMMA shift  4
+                         COMMA shift  5
                      {default} reduce 11
 
-State 22:
+State 25:
           list ::= CURLY_OPEN list_contents * CURLY_CLOSE
 
-                   CURLY_CLOSE shift  35
+                   CURLY_CLOSE shift  41
 
-State 23:
+State 26:
+          map_contents ::= value * COLON value
+          map_contents ::= value * COLON value COMMA map_contents
+
+                         COLON shift  7
+
+State 27:
+     (15) map_contents ::= value COLON value *
+          map_contents ::= value COLON value * COMMA map_contents
+
+                         COMMA shift  6
+                     {default} reduce 15
+
+State 28:
+          map ::= BRACKET_OPEN map_contents * BRACKET_CLOSE
+
+                 BRACKET_CLOSE shift  48
+
+State 29:
           statements ::= statement_names ARROW statement_names * ROUND_OPEN statement_args_maybe ROUND_CLOSE name_maybe SEMICOLON
           statements ::= statement_names ARROW statement_names * ROUND_OPEN statement_args_maybe ROUND_CLOSE name_maybe SEMICOLON statements
 
                     ROUND_OPEN shift  2
 
-State 24:
+State 30:
           statements ::= statement_names ARROW statement_names ROUND_OPEN statement_args_maybe * ROUND_CLOSE name_maybe SEMICOLON
           statements ::= statement_names ARROW statement_names ROUND_OPEN statement_args_maybe * ROUND_CLOSE name_maybe SEMICOLON statements
 
-                   ROUND_CLOSE shift  12
+                   ROUND_CLOSE shift  15
 
-State 25:
+State 31:
           statements ::= statement_names ARROW statement_names ROUND_OPEN statement_args_maybe ROUND_CLOSE name_maybe * SEMICOLON
           statements ::= statement_names ARROW statement_names ROUND_OPEN statement_args_maybe ROUND_CLOSE name_maybe * SEMICOLON statements
 
-                     SEMICOLON shift  8
+                     SEMICOLON shift  11
 
-State 26:
+State 32:
       (2) processes ::= process_or_template NAME CURLY_OPEN statements CURLY_CLOSE processes *
 
                      {default} reduce 2
 
-State 27:
-     (20) process_or_template ::= PROCESS *
+State 33:
+     (25) process_or_template ::= PROCESS *
 
-                     {default} reduce 20
+                     {default} reduce 25
 
-State 28:
-     (21) process_or_template ::= TEMPLATE *
+State 34:
+     (26) process_or_template ::= TEMPLATE *
 
-                     {default} reduce 21
+                     {default} reduce 26
 
-State 29:
+State 35:
       (4) statements ::= statement_names ROUND_OPEN statement_args_maybe ROUND_CLOSE name_maybe SEMICOLON statements *
 
                      {default} reduce 4
 
-State 30:
+State 36:
       (8) statement_names ::= NAME DOT statement_names *
 
                      {default} reduce 8
 
-State 31:
-     (19) name_maybe ::= NAME *
+State 37:
+     (24) name_maybe ::= NAME *
 
-                     {default} reduce 19
+                     {default} reduce 24
 
-State 32:
+State 38:
      (10) statement_args_maybe ::= list_contents *
 
                      {default} reduce 10
 
-State 33:
+State 39:
      (12) list_contents ::= value COMMA list_contents *
 
                      {default} reduce 12
 
-State 34:
+State 40:
      (13) list ::= CURLY_OPEN CURLY_CLOSE *
 
                      {default} reduce 13
 
-State 35:
+State 41:
      (14) list ::= CURLY_OPEN list_contents CURLY_CLOSE *
 
                      {default} reduce 14
 
-State 36:
-     (15) value ::= STRING *
+State 42:
+     (16) map_contents ::= value COLON value COMMA map_contents *
 
-                     {default} reduce 15
+                     {default} reduce 16
 
-State 37:
-     (16) value ::= statement_names *
+State 43:
+     (19) value ::= STRING *
 
-                     {default} reduce 16
+                     {default} reduce 19
 
-State 38:
-     (17) value ::= list *
+State 44:
+     (20) value ::= statement_names *
+
+                     {default} reduce 20
+
+State 45:
+     (21) value ::= list *
+
+                     {default} reduce 21
+
+State 46:
+     (22) value ::= map *
+
+                     {default} reduce 22
+
+State 47:
+     (17) map ::= BRACKET_OPEN BRACKET_CLOSE *
 
                      {default} reduce 17
 
-State 39:
+State 48:
+     (18) map ::= BRACKET_OPEN map_contents BRACKET_CLOSE *
+
+                     {default} reduce 18
+
+State 49:
       (6) statements ::= statement_names ARROW statement_names ROUND_OPEN statement_args_maybe ROUND_CLOSE name_maybe SEMICOLON statements *
 
                      {default} reduce 6
@@ -361,17 +494,22 @@ Symbols:
     7: ARROW
     8: DOT
     9: COMMA
-   10: STRING
-   11: PROCESS
-   12: TEMPLATE
-   13: error:
-   14: processes: PROCESS TEMPLATE
-   15: statements: NAME
-   16: statement_names: NAME
-   17: statement_args_maybe: <lambda> NAME CURLY_OPEN STRING
-   18: list_contents: NAME CURLY_OPEN STRING
-   19: list: CURLY_OPEN
-   20: value: NAME CURLY_OPEN STRING
-   21: name_maybe: <lambda> NAME
-   22: process_or_template: PROCESS TEMPLATE
-   23: input: PROCESS TEMPLATE
+   10: COLON
+   11: BRACKET_OPEN
+   12: BRACKET_CLOSE
+   13: STRING
+   14: PROCESS
+   15: TEMPLATE
+   16: error:
+   17: processes: PROCESS TEMPLATE
+   18: statements: NAME
+   19: statement_names: NAME
+   20: statement_args_maybe: <lambda> NAME CURLY_OPEN BRACKET_OPEN STRING
+   21: list_contents: NAME CURLY_OPEN BRACKET_OPEN STRING
+   22: list: CURLY_OPEN
+   23: map_contents: NAME CURLY_OPEN BRACKET_OPEN STRING
+   24: map: BRACKET_OPEN
+   25: value: NAME CURLY_OPEN BRACKET_OPEN STRING
+   26: name_maybe: <lambda> NAME
+   27: process_or_template: PROCESS TEMPLATE
+   28: input: PROCESS TEMPLATE

+ 47 - 0
generated/NCDConfigParser_parse.y

@@ -55,6 +55,8 @@ struct parser_out {
 %type statement_args_maybe {struct NCDConfig_list *}
 %type list_contents {struct NCDConfig_list *}
 %type list {struct NCDConfig_list *}
+%type map_contents {struct NCDConfig_list *}
+%type map {struct NCDConfig_list *}
 %type value {struct NCDConfig_list *}
 %type name_maybe {char *}
 %type process_or_template {int}
@@ -65,6 +67,8 @@ struct parser_out {
 %destructor statement_args_maybe { NCDConfig_free_list($$); }
 %destructor list_contents { NCDConfig_free_list($$); }
 %destructor list { NCDConfig_free_list($$); }
+%destructor map_contents { NCDConfig_free_list($$); }
+%destructor map { NCDConfig_free_list($$); }
 %destructor value { NCDConfig_free_list($$); }
 %destructor name_maybe { free($$); }
 
@@ -175,6 +179,42 @@ list(R) ::= CURLY_OPEN list_contents(A) CURLY_CLOSE. {
     R = A;
 }
 
+map_contents(R) ::= value(A) COLON value(B). {
+    if (!A || !B) {
+        NCDConfig_free_list(A);
+        NCDConfig_free_list(B);
+        R = NULL;
+    } else {
+        ASSERT(!A->next)
+        ASSERT(!B->next)
+        A->next = B;
+        R = A;
+    }
+}
+
+map_contents(R) ::= value(A) COLON value(B) COMMA map_contents(N). {
+    if (!A || !B) {
+        NCDConfig_free_list(A);
+        NCDConfig_free_list(B);
+        NCDConfig_free_list(N);
+        R = NULL;
+    } else {
+        ASSERT(!A->next)
+        ASSERT(!B->next)
+        A->next = B;
+        B->next = N;
+        R = A;
+    }
+}
+
+map(R) ::= BRACKET_OPEN BRACKET_CLOSE. {
+    R = NULL;
+}
+
+map(R) ::= BRACKET_OPEN map_contents(A) BRACKET_CLOSE. {
+    R = A;
+}
+
 value(R) ::= STRING(A). {
     R = NCDConfig_make_list_string(A, NULL);
     if (!R) {
@@ -196,6 +236,13 @@ value(R) ::= list(A). {
     }
 }
 
+value(R) ::= map(A). {
+    R = NCDConfig_make_list_maplist(A, NULL);
+    if (!R) {
+        parser_out->out_of_memory = 1;
+    }
+}
+
 name_maybe(R) ::= . {
     R = NULL;
 }

+ 173 - 71
generated/NCDValueParser_parse.c

@@ -16,6 +16,7 @@
 #define AST_TYPE_NONE 0
 #define AST_TYPE_STRING 1
 #define AST_TYPE_LIST 2
+#define AST_TYPE_MAP 3
 
 struct parser_out {
     int out_of_memory;
@@ -25,7 +26,7 @@ struct parser_out {
     struct NCDConfig_list *ast_list;
 };
 
-#line 29 "NCDValueParser_parse.c"
+#line 30 "NCDValueParser_parse.c"
 /* Next is all token values, in a form suitable for use by makeheaders.
 ** This section will be null unless lemon is run with the -m switch.
 */
@@ -76,7 +77,7 @@ struct parser_out {
 **                       defined, then do no error processing.
 */
 #define YYCODETYPE unsigned char
-#define YYNOCODE 11
+#define YYNOCODE 16
 #define YYACTIONTYPE unsigned char
 #define ParseTOKENTYPE void *
 typedef union {
@@ -91,8 +92,8 @@ typedef union {
 #define ParseARG_PDECL ,struct parser_out *parser_out
 #define ParseARG_FETCH struct parser_out *parser_out = yypParser->parser_out
 #define ParseARG_STORE yypParser->parser_out = parser_out
-#define YYNSTATE 12
-#define YYNRULE 8
+#define YYNSTATE 23
+#define YYNRULE 14
 #define YY_NO_ACTION      (YYNSTATE+YYNRULE+2)
 #define YY_ACCEPT_ACTION  (YYNSTATE+YYNRULE+1)
 #define YY_ERROR_ACTION   (YYNSTATE+YYNRULE)
@@ -162,26 +163,34 @@ static const YYMINORTYPE yyzerominor = { 0 };
 **  yy_default[]       Default action for each state.
 */
 static const YYACTIONTYPE yy_action[] = {
- /*     0 */     8,   11,    1,   10,    6,    9,    5,    7,    9,    5,
- /*    10 */     3,    4,    1,   21,    8,   12,    1,   13,    2,
+ /*     0 */    16,   23,   24,    1,   21,    2,   13,   17,   16,   18,
+ /*    10 */     9,    1,   25,    2,   19,   17,   12,   18,   10,   14,
+ /*    20 */    17,    3,   18,    9,   17,   15,   18,   10,    6,    5,
+ /*    30 */     7,    1,    8,    2,   38,   16,   22,    4,    1,   17,
+ /*    40 */     2,   18,   11,   20,
 };
 static const YYCODETYPE yy_lookahead[] = {
- /*     0 */     1,    4,    3,    4,    6,    7,    8,    6,    7,    8,
- /*    10 */     1,    7,    3,    9,    1,    0,    3,    0,    2,
+ /*     0 */     1,    0,    0,    4,    5,    6,    9,   10,    1,   12,
+ /*    10 */    13,    4,    0,    6,    7,   10,   11,   12,   13,    9,
+ /*    20 */    10,    2,   12,   13,   10,   11,   12,   13,    1,    3,
+ /*    30 */    10,    4,   12,    6,   14,    1,    5,    2,    4,   10,
+ /*    40 */     6,   12,   13,    7,
 };
-#define YY_SHIFT_USE_DFLT (-4)
-#define YY_SHIFT_MAX 6
+#define YY_SHIFT_USE_DFLT (-2)
+#define YY_SHIFT_MAX 13
 static const signed char yy_shift_ofst[] = {
- /*     0 */     9,   -1,   13,   15,   17,   16,   -3,
+ /*     0 */    27,   -1,    7,   34,   34,   34,    1,    2,   12,   19,
+ /*    10 */    26,   35,   36,   31,
 };
-#define YY_REDUCE_USE_DFLT (-3)
-#define YY_REDUCE_MAX 2
+#define YY_REDUCE_USE_DFLT (-4)
+#define YY_REDUCE_MAX 5
 static const signed char yy_reduce_ofst[] = {
- /*     0 */     4,   -2,    1,
+ /*     0 */    20,   -3,    5,   10,   14,   29,
 };
 static const YYACTIONTYPE yy_default[] = {
- /*     0 */    20,   20,   20,   20,   20,   14,   20,   15,   18,   19,
- /*    10 */    16,   17,
+ /*     0 */    37,   37,   37,   37,   37,   37,   37,   37,   37,   26,
+ /*    10 */    37,   28,   37,   37,   27,   29,   34,   35,   36,   32,
+ /*    20 */    33,   30,   31,
 };
 #define YY_SZ_ACTTAB (int)(sizeof(yy_action)/sizeof(yy_action[0]))
 
@@ -275,9 +284,10 @@ void ParseTrace(FILE *TraceFILE, char *zTracePrompt){
 /* For tracing shifts, the names of all terminals and nonterminals
 ** are required.  The following table supplies these names */
 static const char *const yyTokenName[] = { 
-  "$",             "STRING",        "COMMA",         "CURLY_OPEN",  
-  "CURLY_CLOSE",   "error",         "list_contents",  "list",        
-  "value",         "input",       
+  "$",             "STRING",        "COMMA",         "COLON",       
+  "CURLY_OPEN",    "CURLY_CLOSE",   "BRACKET_OPEN",  "BRACKET_CLOSE",
+  "error",         "list_contents",  "list",          "map_contents",
+  "map",           "value",         "input",       
 };
 #endif /* NDEBUG */
 
@@ -287,12 +297,18 @@ static const char *const yyTokenName[] = {
 static const char *const yyRuleName[] = {
  /*   0 */ "input ::= STRING",
  /*   1 */ "input ::= list",
- /*   2 */ "list_contents ::= value",
- /*   3 */ "list_contents ::= value COMMA list_contents",
- /*   4 */ "list ::= CURLY_OPEN CURLY_CLOSE",
- /*   5 */ "list ::= CURLY_OPEN list_contents CURLY_CLOSE",
- /*   6 */ "value ::= STRING",
- /*   7 */ "value ::= list",
+ /*   2 */ "input ::= map",
+ /*   3 */ "list_contents ::= value",
+ /*   4 */ "list_contents ::= value COMMA list_contents",
+ /*   5 */ "map_contents ::= value COLON value",
+ /*   6 */ "map_contents ::= value COLON value COMMA map_contents",
+ /*   7 */ "list ::= CURLY_OPEN CURLY_CLOSE",
+ /*   8 */ "list ::= CURLY_OPEN list_contents CURLY_CLOSE",
+ /*   9 */ "map ::= BRACKET_OPEN BRACKET_CLOSE",
+ /*  10 */ "map ::= BRACKET_OPEN map_contents BRACKET_CLOSE",
+ /*  11 */ "value ::= STRING",
+ /*  12 */ "value ::= list",
+ /*  13 */ "value ::= map",
 };
 #endif /* NDEBUG */
 
@@ -374,21 +390,26 @@ static void yy_destructor(
       /* TERMINAL Destructor */
     case 1: /* STRING */
     case 2: /* COMMA */
-    case 3: /* CURLY_OPEN */
-    case 4: /* CURLY_CLOSE */
+    case 3: /* COLON */
+    case 4: /* CURLY_OPEN */
+    case 5: /* CURLY_CLOSE */
+    case 6: /* BRACKET_OPEN */
+    case 7: /* BRACKET_CLOSE */
 {
-#line 56 "NCDValueParser_parse.y"
+#line 57 "NCDValueParser_parse.y"
  free((yypminor->yy0)); 
-#line 383 "NCDValueParser_parse.c"
+#line 402 "NCDValueParser_parse.c"
 }
       break;
-    case 6: /* list_contents */
-    case 7: /* list */
-    case 8: /* value */
+    case 9: /* list_contents */
+    case 10: /* list */
+    case 11: /* map_contents */
+    case 12: /* map */
+    case 13: /* value */
 {
-#line 62 "NCDValueParser_parse.y"
+#line 65 "NCDValueParser_parse.y"
  NCDConfig_free_list((yypminor->yy14)); 
-#line 392 "NCDValueParser_parse.c"
+#line 413 "NCDValueParser_parse.c"
 }
       break;
     default:  break;   /* If no destructor action specified: do nothing */
@@ -561,12 +582,12 @@ static void yyStackOverflow(yyParser *yypParser, YYMINORTYPE *yypMinor){
    while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser);
    /* Here code is inserted which will execute if the parser
    ** stack every overflows */
-#line 73 "NCDValueParser_parse.y"
+#line 78 "NCDValueParser_parse.y"
 
     if (yypMinor) {
         free(yypMinor->yy0);
     }
-#line 570 "NCDValueParser_parse.c"
+#line 591 "NCDValueParser_parse.c"
    ParseARG_STORE; /* Suppress warning about unused %extra_argument var */
 }
 
@@ -623,14 +644,20 @@ static const struct {
   YYCODETYPE lhs;         /* Symbol on the left-hand side of the rule */
   unsigned char nrhs;     /* Number of right-hand side symbols in the rule */
 } yyRuleInfo[] = {
+  { 14, 1 },
+  { 14, 1 },
+  { 14, 1 },
   { 9, 1 },
-  { 9, 1 },
-  { 6, 1 },
-  { 6, 3 },
-  { 7, 2 },
-  { 7, 3 },
-  { 8, 1 },
-  { 8, 1 },
+  { 9, 3 },
+  { 11, 3 },
+  { 11, 5 },
+  { 10, 2 },
+  { 10, 3 },
+  { 12, 2 },
+  { 12, 3 },
+  { 13, 1 },
+  { 13, 1 },
+  { 13, 1 },
 };
 
 static void yy_accept(yyParser*);  /* Forward Declaration */
@@ -686,34 +713,44 @@ static void yy_reduce(
   **     break;
   */
       case 0: /* input ::= STRING */
-#line 79 "NCDValueParser_parse.y"
+#line 84 "NCDValueParser_parse.y"
 {
     ASSERT(parser_out->ast_type == AST_TYPE_NONE)
 
     parser_out->ast_string = yymsp[0].minor.yy0;
     parser_out->ast_type = AST_TYPE_STRING;
 }
-#line 697 "NCDValueParser_parse.c"
+#line 724 "NCDValueParser_parse.c"
         break;
       case 1: /* input ::= list */
-#line 86 "NCDValueParser_parse.y"
+#line 91 "NCDValueParser_parse.y"
 {
     ASSERT(parser_out->ast_type == AST_TYPE_NONE)
 
     parser_out->ast_list = yymsp[0].minor.yy14;
     parser_out->ast_type = AST_TYPE_LIST;
 }
-#line 707 "NCDValueParser_parse.c"
+#line 734 "NCDValueParser_parse.c"
         break;
-      case 2: /* list_contents ::= value */
-#line 93 "NCDValueParser_parse.y"
+      case 2: /* input ::= map */
+#line 98 "NCDValueParser_parse.y"
+{
+    ASSERT(parser_out->ast_type == AST_TYPE_NONE)
+
+    parser_out->ast_list = yymsp[0].minor.yy14;
+    parser_out->ast_type = AST_TYPE_MAP;
+}
+#line 744 "NCDValueParser_parse.c"
+        break;
+      case 3: /* list_contents ::= value */
+#line 105 "NCDValueParser_parse.y"
 {
     yygotominor.yy14 = yymsp[0].minor.yy14;
 }
-#line 714 "NCDValueParser_parse.c"
+#line 751 "NCDValueParser_parse.c"
         break;
-      case 3: /* list_contents ::= value COMMA list_contents */
-#line 97 "NCDValueParser_parse.y"
+      case 4: /* list_contents ::= value COMMA list_contents */
+#line 109 "NCDValueParser_parse.y"
 {
     if (!yymsp[-2].minor.yy14) {
         NCDConfig_free_list(yymsp[0].minor.yy14);
@@ -724,45 +761,110 @@ static void yy_reduce(
     yygotominor.yy14 = yymsp[-2].minor.yy14;
   yy_destructor(yypParser,2,&yymsp[-1].minor);
 }
-#line 728 "NCDValueParser_parse.c"
+#line 765 "NCDValueParser_parse.c"
         break;
-      case 4: /* list ::= CURLY_OPEN CURLY_CLOSE */
-#line 107 "NCDValueParser_parse.y"
+      case 5: /* map_contents ::= value COLON value */
+#line 119 "NCDValueParser_parse.y"
 {
-    yygotominor.yy14 = NULL;
+    if (!yymsp[-2].minor.yy14 || !yymsp[0].minor.yy14) {
+        NCDConfig_free_list(yymsp[-2].minor.yy14);
+        NCDConfig_free_list(yymsp[0].minor.yy14);
+        yygotominor.yy14 = NULL;
+    } else {
+        ASSERT(!yymsp[-2].minor.yy14->next)
+        ASSERT(!yymsp[0].minor.yy14->next)
+        yymsp[-2].minor.yy14->next = yymsp[0].minor.yy14;
+        yygotominor.yy14 = yymsp[-2].minor.yy14;
+    }
   yy_destructor(yypParser,3,&yymsp[-1].minor);
-  yy_destructor(yypParser,4,&yymsp[0].minor);
 }
-#line 737 "NCDValueParser_parse.c"
+#line 782 "NCDValueParser_parse.c"
+        break;
+      case 6: /* map_contents ::= value COLON value COMMA map_contents */
+#line 132 "NCDValueParser_parse.y"
+{
+    if (!yymsp[-4].minor.yy14 || !yymsp[-2].minor.yy14) {
+        NCDConfig_free_list(yymsp[-4].minor.yy14);
+        NCDConfig_free_list(yymsp[-2].minor.yy14);
+        NCDConfig_free_list(yymsp[0].minor.yy14);
+        yygotominor.yy14 = NULL;
+    } else {
+        ASSERT(!yymsp[-4].minor.yy14->next)
+        ASSERT(!yymsp[-2].minor.yy14->next)
+        yymsp[-4].minor.yy14->next = yymsp[-2].minor.yy14;
+        yymsp[-2].minor.yy14->next = yymsp[0].minor.yy14;
+        yygotominor.yy14 = yymsp[-4].minor.yy14;
+    }
+  yy_destructor(yypParser,3,&yymsp[-3].minor);
+  yy_destructor(yypParser,2,&yymsp[-1].minor);
+}
+#line 802 "NCDValueParser_parse.c"
+        break;
+      case 7: /* list ::= CURLY_OPEN CURLY_CLOSE */
+#line 147 "NCDValueParser_parse.y"
+{
+    yygotominor.yy14 = NULL;
+  yy_destructor(yypParser,4,&yymsp[-1].minor);
+  yy_destructor(yypParser,5,&yymsp[0].minor);
+}
+#line 811 "NCDValueParser_parse.c"
         break;
-      case 5: /* list ::= CURLY_OPEN list_contents CURLY_CLOSE */
-#line 111 "NCDValueParser_parse.y"
+      case 8: /* list ::= CURLY_OPEN list_contents CURLY_CLOSE */
+#line 151 "NCDValueParser_parse.y"
 {
     yygotominor.yy14 = yymsp[-1].minor.yy14;
-  yy_destructor(yypParser,3,&yymsp[-2].minor);
-  yy_destructor(yypParser,4,&yymsp[0].minor);
+  yy_destructor(yypParser,4,&yymsp[-2].minor);
+  yy_destructor(yypParser,5,&yymsp[0].minor);
 }
-#line 746 "NCDValueParser_parse.c"
+#line 820 "NCDValueParser_parse.c"
         break;
-      case 6: /* value ::= STRING */
-#line 115 "NCDValueParser_parse.y"
+      case 9: /* map ::= BRACKET_OPEN BRACKET_CLOSE */
+#line 155 "NCDValueParser_parse.y"
+{
+    yygotominor.yy14 = NULL;
+  yy_destructor(yypParser,6,&yymsp[-1].minor);
+  yy_destructor(yypParser,7,&yymsp[0].minor);
+}
+#line 829 "NCDValueParser_parse.c"
+        break;
+      case 10: /* map ::= BRACKET_OPEN map_contents BRACKET_CLOSE */
+#line 159 "NCDValueParser_parse.y"
+{
+    yygotominor.yy14 = yymsp[-1].minor.yy14;
+  yy_destructor(yypParser,6,&yymsp[-2].minor);
+  yy_destructor(yypParser,7,&yymsp[0].minor);
+}
+#line 838 "NCDValueParser_parse.c"
+        break;
+      case 11: /* value ::= STRING */
+#line 163 "NCDValueParser_parse.y"
 {
     yygotominor.yy14 = NCDConfig_make_list_string(yymsp[0].minor.yy0, NULL);
     if (!yygotominor.yy14) {
         parser_out->out_of_memory = 1;
     }
 }
-#line 756 "NCDValueParser_parse.c"
+#line 848 "NCDValueParser_parse.c"
         break;
-      case 7: /* value ::= list */
-#line 122 "NCDValueParser_parse.y"
+      case 12: /* value ::= list */
+#line 170 "NCDValueParser_parse.y"
 {
     yygotominor.yy14 = NCDConfig_make_list_list(yymsp[0].minor.yy14, NULL);
     if (!yygotominor.yy14) {
         parser_out->out_of_memory = 1;
     }
 }
-#line 766 "NCDValueParser_parse.c"
+#line 858 "NCDValueParser_parse.c"
+        break;
+      case 13: /* value ::= map */
+#line 177 "NCDValueParser_parse.y"
+{
+    yygotominor.yy14 = NCDConfig_make_list_maplist(yymsp[0].minor.yy14, NULL);
+    if (!yygotominor.yy14) {
+        parser_out->out_of_memory = 1;
+    }
+}
+#line 868 "NCDValueParser_parse.c"
         break;
       default:
         break;
@@ -824,10 +926,10 @@ static void yy_syntax_error(
 ){
   ParseARG_FETCH;
 #define TOKEN (yyminor.yy0)
-#line 68 "NCDValueParser_parse.y"
+#line 73 "NCDValueParser_parse.y"
 
     parser_out->syntax_error = 1;
-#line 831 "NCDValueParser_parse.c"
+#line 933 "NCDValueParser_parse.c"
   ParseARG_STORE; /* Suppress warning about unused %extra_argument variable */
 }
 

+ 5 - 2
generated/NCDValueParser_parse.h

@@ -1,4 +1,7 @@
 #define STRING                          1
 #define COMMA                           2
-#define CURLY_OPEN                      3
-#define CURLY_CLOSE                     4
+#define COLON                           3
+#define CURLY_OPEN                      4
+#define CURLY_CLOSE                     5
+#define BRACKET_OPEN                    6
+#define BRACKET_CLOSE                   7

+ 163 - 40
generated/NCDValueParser_parse.out

@@ -1,12 +1,17 @@
 State 0:
           input ::= * STRING
           input ::= * list
+          input ::= * map
           list ::= * CURLY_OPEN CURLY_CLOSE
           list ::= * CURLY_OPEN list_contents CURLY_CLOSE
+          map ::= * BRACKET_OPEN BRACKET_CLOSE
+          map ::= * BRACKET_OPEN map_contents BRACKET_CLOSE
 
-                        STRING shift  3
+                        STRING shift  6
                     CURLY_OPEN shift  1
-                          list shift  4
+                  BRACKET_OPEN shift  2
+                          list shift  7
+                           map shift  8
                          input accept
 
 State 1:
@@ -16,87 +21,205 @@ State 1:
           list ::= CURLY_OPEN * CURLY_CLOSE
           list ::= * CURLY_OPEN list_contents CURLY_CLOSE
           list ::= CURLY_OPEN * list_contents CURLY_CLOSE
+          map ::= * BRACKET_OPEN BRACKET_CLOSE
+          map ::= * BRACKET_OPEN map_contents BRACKET_CLOSE
           value ::= * STRING
           value ::= * list
+          value ::= * map
 
-                        STRING shift  8
+                        STRING shift  16
                     CURLY_OPEN shift  1
-                   CURLY_CLOSE shift  10
-                 list_contents shift  6
-                          list shift  9
-                         value shift  5
+                   CURLY_CLOSE shift  21
+                  BRACKET_OPEN shift  2
+                 list_contents shift  13
+                          list shift  17
+                           map shift  18
+                         value shift  9
 
 State 2:
-          list_contents ::= * value
-          list_contents ::= * value COMMA list_contents
-          list_contents ::= value COMMA * list_contents
+          map_contents ::= * value COLON value
+          map_contents ::= * value COLON value COMMA map_contents
           list ::= * CURLY_OPEN CURLY_CLOSE
           list ::= * CURLY_OPEN list_contents CURLY_CLOSE
+          map ::= * BRACKET_OPEN BRACKET_CLOSE
+          map ::= BRACKET_OPEN * BRACKET_CLOSE
+          map ::= * BRACKET_OPEN map_contents BRACKET_CLOSE
+          map ::= BRACKET_OPEN * map_contents BRACKET_CLOSE
           value ::= * STRING
           value ::= * list
+          value ::= * map
 
-                        STRING shift  8
+                        STRING shift  16
                     CURLY_OPEN shift  1
-                 list_contents shift  7
-                          list shift  9
-                         value shift  5
+                  BRACKET_OPEN shift  2
+                 BRACKET_CLOSE shift  19
+                          list shift  17
+                  map_contents shift  12
+                           map shift  18
+                         value shift  10
 
 State 3:
-      (0) input ::= STRING *
+          list_contents ::= * value
+          list_contents ::= * value COMMA list_contents
+          list_contents ::= value COMMA * list_contents
+          list ::= * CURLY_OPEN CURLY_CLOSE
+          list ::= * CURLY_OPEN list_contents CURLY_CLOSE
+          map ::= * BRACKET_OPEN BRACKET_CLOSE
+          map ::= * BRACKET_OPEN map_contents BRACKET_CLOSE
+          value ::= * STRING
+          value ::= * list
+          value ::= * map
 
-                             $ reduce 0
+                        STRING shift  16
+                    CURLY_OPEN shift  1
+                  BRACKET_OPEN shift  2
+                 list_contents shift  14
+                          list shift  17
+                           map shift  18
+                         value shift  9
 
 State 4:
-      (1) input ::= list *
+          map_contents ::= * value COLON value
+          map_contents ::= * value COLON value COMMA map_contents
+          map_contents ::= value COLON value COMMA * map_contents
+          list ::= * CURLY_OPEN CURLY_CLOSE
+          list ::= * CURLY_OPEN list_contents CURLY_CLOSE
+          map ::= * BRACKET_OPEN BRACKET_CLOSE
+          map ::= * BRACKET_OPEN map_contents BRACKET_CLOSE
+          value ::= * STRING
+          value ::= * list
+          value ::= * map
 
-                             $ reduce 1
+                        STRING shift  16
+                    CURLY_OPEN shift  1
+                  BRACKET_OPEN shift  2
+                          list shift  17
+                  map_contents shift  15
+                           map shift  18
+                         value shift  10
 
 State 5:
-      (2) list_contents ::= value *
-          list_contents ::= value * COMMA list_contents
+          map_contents ::= value COLON * value
+          map_contents ::= value COLON * value COMMA map_contents
+          list ::= * CURLY_OPEN CURLY_CLOSE
+          list ::= * CURLY_OPEN list_contents CURLY_CLOSE
+          map ::= * BRACKET_OPEN BRACKET_CLOSE
+          map ::= * BRACKET_OPEN map_contents BRACKET_CLOSE
+          value ::= * STRING
+          value ::= * list
+          value ::= * map
 
-                         COMMA shift  2
-                     {default} reduce 2
+                        STRING shift  16
+                    CURLY_OPEN shift  1
+                  BRACKET_OPEN shift  2
+                          list shift  17
+                           map shift  18
+                         value shift  11
 
 State 6:
-          list ::= CURLY_OPEN list_contents * CURLY_CLOSE
+      (0) input ::= STRING *
 
-                   CURLY_CLOSE shift  11
+                             $ reduce 0
 
 State 7:
-      (3) list_contents ::= value COMMA list_contents *
+      (1) input ::= list *
 
-                     {default} reduce 3
+                             $ reduce 1
 
 State 8:
-      (6) value ::= STRING *
+      (2) input ::= map *
 
-                     {default} reduce 6
+                             $ reduce 2
 
 State 9:
-      (7) value ::= list *
+      (3) list_contents ::= value *
+          list_contents ::= value * COMMA list_contents
 
-                     {default} reduce 7
+                         COMMA shift  3
+                     {default} reduce 3
 
 State 10:
-      (4) list ::= CURLY_OPEN CURLY_CLOSE *
+          map_contents ::= value * COLON value
+          map_contents ::= value * COLON value COMMA map_contents
 
-                     {default} reduce 4
+                         COLON shift  5
 
 State 11:
-      (5) list ::= CURLY_OPEN list_contents CURLY_CLOSE *
+      (5) map_contents ::= value COLON value *
+          map_contents ::= value COLON value * COMMA map_contents
 
+                         COMMA shift  4
                      {default} reduce 5
 
+State 12:
+          map ::= BRACKET_OPEN map_contents * BRACKET_CLOSE
+
+                 BRACKET_CLOSE shift  20
+
+State 13:
+          list ::= CURLY_OPEN list_contents * CURLY_CLOSE
+
+                   CURLY_CLOSE shift  22
+
+State 14:
+      (4) list_contents ::= value COMMA list_contents *
+
+                     {default} reduce 4
+
+State 15:
+      (6) map_contents ::= value COLON value COMMA map_contents *
+
+                     {default} reduce 6
+
+State 16:
+     (11) value ::= STRING *
+
+                     {default} reduce 11
+
+State 17:
+     (12) value ::= list *
+
+                     {default} reduce 12
+
+State 18:
+     (13) value ::= map *
+
+                     {default} reduce 13
+
+State 19:
+      (9) map ::= BRACKET_OPEN BRACKET_CLOSE *
+
+                     {default} reduce 9
+
+State 20:
+     (10) map ::= BRACKET_OPEN map_contents BRACKET_CLOSE *
+
+                     {default} reduce 10
+
+State 21:
+      (7) list ::= CURLY_OPEN CURLY_CLOSE *
+
+                     {default} reduce 7
+
+State 22:
+      (8) list ::= CURLY_OPEN list_contents CURLY_CLOSE *
+
+                     {default} reduce 8
+
 ----------------------------------------------------
 Symbols:
     0: $:
     1: STRING
     2: COMMA
-    3: CURLY_OPEN
-    4: CURLY_CLOSE
-    5: error:
-    6: list_contents: STRING CURLY_OPEN
-    7: list: CURLY_OPEN
-    8: value: STRING CURLY_OPEN
-    9: input: STRING CURLY_OPEN
+    3: COLON
+    4: CURLY_OPEN
+    5: CURLY_CLOSE
+    6: BRACKET_OPEN
+    7: BRACKET_CLOSE
+    8: error:
+    9: list_contents: STRING CURLY_OPEN BRACKET_OPEN
+   10: list: CURLY_OPEN
+   11: map_contents: STRING CURLY_OPEN BRACKET_OPEN
+   12: map: BRACKET_OPEN
+   13: value: STRING CURLY_OPEN BRACKET_OPEN
+   14: input: STRING CURLY_OPEN BRACKET_OPEN

+ 55 - 0
generated/NCDValueParser_parse.y

@@ -38,6 +38,7 @@
 #define AST_TYPE_NONE 0
 #define AST_TYPE_STRING 1
 #define AST_TYPE_LIST 2
+#define AST_TYPE_MAP 3
 
 struct parser_out {
     int out_of_memory;
@@ -57,10 +58,14 @@ struct parser_out {
 
 %type list_contents {struct NCDConfig_list *}
 %type list {struct NCDConfig_list *}
+%type map_contents {struct NCDConfig_list *}
+%type map {struct NCDConfig_list *}
 %type value {struct NCDConfig_list *}
 
 %destructor list_contents { NCDConfig_free_list($$); }
 %destructor list { NCDConfig_free_list($$); }
+%destructor map_contents { NCDConfig_free_list($$); }
+%destructor map { NCDConfig_free_list($$); }
 %destructor value { NCDConfig_free_list($$); }
 
 %stack_size 0
@@ -90,6 +95,13 @@ input ::= list(A). {
     parser_out->ast_type = AST_TYPE_LIST;
 }
 
+input ::= map(A). {
+    ASSERT(parser_out->ast_type == AST_TYPE_NONE)
+
+    parser_out->ast_list = A;
+    parser_out->ast_type = AST_TYPE_MAP;
+}
+
 list_contents(R) ::= value(A). {
     R = A;
 }
@@ -104,6 +116,34 @@ list_contents(R) ::= value(A) COMMA list_contents(N). {
     R = A;
 }
 
+map_contents(R) ::= value(A) COLON value(B). {
+    if (!A || !B) {
+        NCDConfig_free_list(A);
+        NCDConfig_free_list(B);
+        R = NULL;
+    } else {
+        ASSERT(!A->next)
+        ASSERT(!B->next)
+        A->next = B;
+        R = A;
+    }
+}
+
+map_contents(R) ::= value(A) COLON value(B) COMMA map_contents(N). {
+    if (!A || !B) {
+        NCDConfig_free_list(A);
+        NCDConfig_free_list(B);
+        NCDConfig_free_list(N);
+        R = NULL;
+    } else {
+        ASSERT(!A->next)
+        ASSERT(!B->next)
+        A->next = B;
+        B->next = N;
+        R = A;
+    }
+}
+
 list(R) ::= CURLY_OPEN CURLY_CLOSE. {
     R = NULL;
 }
@@ -112,6 +152,14 @@ list(R) ::= CURLY_OPEN list_contents(A) CURLY_CLOSE. {
     R = A;
 }
 
+map(R) ::= BRACKET_OPEN BRACKET_CLOSE. {
+    R = NULL;
+}
+
+map(R) ::= BRACKET_OPEN map_contents(A) BRACKET_CLOSE. {
+    R = A;
+}
+
 value(R) ::= STRING(A). {
     R = NCDConfig_make_list_string(A, NULL);
     if (!R) {
@@ -125,3 +173,10 @@ value(R) ::= list(A). {
         parser_out->out_of_memory = 1;
     }
 }
+
+value(R) ::= map(A). {
+    R = NCDConfig_make_list_maplist(A, NULL);
+    if (!R) {
+        parser_out->out_of_memory = 1;
+    }
+}

+ 20 - 0
ncd/NCDConfig.c

@@ -77,6 +77,7 @@ void NCDConfig_free_list (struct NCDConfig_list *v)
             NCDConfig_free_strings(v->var);
             break;
         case NCDCONFIG_ARG_LIST:
+        case NCDCONFIG_ARG_MAPLIST:
             NCDConfig_free_list(v->list);
             break;
         default:
@@ -205,6 +206,25 @@ fail:
     return NULL;
 }
 
+struct NCDConfig_list * NCDConfig_make_list_maplist (struct NCDConfig_list *list, struct NCDConfig_list *next)
+{
+    struct NCDConfig_list *v = malloc(sizeof(*v));
+    if (!v) {
+        goto fail;
+    }
+    
+    v->type = NCDCONFIG_ARG_MAPLIST;
+    v->list = list;
+    v->next = next;
+    
+    return v;
+    
+fail:
+    NCDConfig_free_list(list);
+    NCDConfig_free_list(next);
+    return NULL;
+}
+
 struct NCDConfig_strings * NCDConfig_make_strings (char *value, int need_next, struct NCDConfig_strings *next)
 {
     if (!value || (need_next && !next)) {

+ 2 - 0
ncd/NCDConfig.h

@@ -53,6 +53,7 @@ struct NCDConfig_statements {
 #define NCDCONFIG_ARG_STRING 1
 #define NCDCONFIG_ARG_VAR 2
 #define NCDCONFIG_ARG_LIST 3
+#define NCDCONFIG_ARG_MAPLIST 4
 
 struct NCDConfig_list {
     int type;
@@ -78,6 +79,7 @@ struct NCDConfig_statements * NCDConfig_make_statements (struct NCDConfig_string
 struct NCDConfig_list * NCDConfig_make_list_string (char *str, struct NCDConfig_list *next);
 struct NCDConfig_list * NCDConfig_make_list_var (struct NCDConfig_strings *var, struct NCDConfig_list *next);
 struct NCDConfig_list * NCDConfig_make_list_list (struct NCDConfig_list *list, struct NCDConfig_list *next);
+struct NCDConfig_list * NCDConfig_make_list_maplist (struct NCDConfig_list *list, struct NCDConfig_list *next);
 struct NCDConfig_strings * NCDConfig_make_strings (char *value, int have_next, struct NCDConfig_strings *next);
 
 int NCDConfig_statement_name_is (struct NCDConfig_statements *st, const char *needle);

+ 12 - 0
ncd/NCDConfigParser.c

@@ -114,6 +114,18 @@ static int tokenizer_output (void *user, int token, char *value, size_t line, si
             Parse(state->parser, STRING, value, &state->out);
         } break;
         
+        case NCD_TOKEN_COLON: {
+            Parse(state->parser, COLON, NULL, &state->out);
+        } break;
+        
+        case NCD_TOKEN_BRACKET_OPEN: {
+            Parse(state->parser, BRACKET_OPEN, NULL, &state->out);
+        } break;
+        
+        case NCD_TOKEN_BRACKET_CLOSE: {
+            Parse(state->parser, BRACKET_CLOSE, NULL, &state->out);
+        } break;
+        
         default:
             ASSERT(0);
     }

+ 47 - 0
ncd/NCDConfigParser_parse.y

@@ -55,6 +55,8 @@ struct parser_out {
 %type statement_args_maybe {struct NCDConfig_list *}
 %type list_contents {struct NCDConfig_list *}
 %type list {struct NCDConfig_list *}
+%type map_contents {struct NCDConfig_list *}
+%type map {struct NCDConfig_list *}
 %type value {struct NCDConfig_list *}
 %type name_maybe {char *}
 %type process_or_template {int}
@@ -65,6 +67,8 @@ struct parser_out {
 %destructor statement_args_maybe { NCDConfig_free_list($$); }
 %destructor list_contents { NCDConfig_free_list($$); }
 %destructor list { NCDConfig_free_list($$); }
+%destructor map_contents { NCDConfig_free_list($$); }
+%destructor map { NCDConfig_free_list($$); }
 %destructor value { NCDConfig_free_list($$); }
 %destructor name_maybe { free($$); }
 
@@ -175,6 +179,42 @@ list(R) ::= CURLY_OPEN list_contents(A) CURLY_CLOSE. {
     R = A;
 }
 
+map_contents(R) ::= value(A) COLON value(B). {
+    if (!A || !B) {
+        NCDConfig_free_list(A);
+        NCDConfig_free_list(B);
+        R = NULL;
+    } else {
+        ASSERT(!A->next)
+        ASSERT(!B->next)
+        A->next = B;
+        R = A;
+    }
+}
+
+map_contents(R) ::= value(A) COLON value(B) COMMA map_contents(N). {
+    if (!A || !B) {
+        NCDConfig_free_list(A);
+        NCDConfig_free_list(B);
+        NCDConfig_free_list(N);
+        R = NULL;
+    } else {
+        ASSERT(!A->next)
+        ASSERT(!B->next)
+        A->next = B;
+        B->next = N;
+        R = A;
+    }
+}
+
+map(R) ::= BRACKET_OPEN BRACKET_CLOSE. {
+    R = NULL;
+}
+
+map(R) ::= BRACKET_OPEN map_contents(A) BRACKET_CLOSE. {
+    R = A;
+}
+
 value(R) ::= STRING(A). {
     R = NCDConfig_make_list_string(A, NULL);
     if (!R) {
@@ -196,6 +236,13 @@ value(R) ::= list(A). {
     }
 }
 
+value(R) ::= map(A). {
+    R = NCDConfig_make_list_maplist(A, NULL);
+    if (!R) {
+        parser_out->out_of_memory = 1;
+    }
+}
+
 name_maybe(R) ::= . {
     R = NULL;
 }

+ 9 - 0
ncd/NCDConfigTokenizer.c

@@ -100,6 +100,15 @@ void NCDConfigTokenizer_Tokenize (char *str, size_t left, NCDConfigTokenizer_out
         else if (l = data_begins_with(str, left, ",")) {
             token = NCD_TOKEN_COMMA;
         }
+        else if (l = data_begins_with(str, left, ":")) {
+            token = NCD_TOKEN_COLON;
+        }
+        else if (l = data_begins_with(str, left, "[")) {
+            token = NCD_TOKEN_BRACKET_OPEN;
+        }
+        else if (l = data_begins_with(str, left, "]")) {
+            token = NCD_TOKEN_BRACKET_CLOSE;
+        }
         else if (l = data_begins_with(str, left, "->")) {
             token = NCD_TOKEN_ARROW;
         }

+ 3 - 0
ncd/NCDConfigTokenizer.h

@@ -46,6 +46,9 @@
 #define NCD_TOKEN_STRING 10
 #define NCD_TOKEN_ARROW 11
 #define NCD_TOKEN_TEMPLATE 12
+#define NCD_TOKEN_COLON 13
+#define NCD_TOKEN_BRACKET_OPEN 14
+#define NCD_TOKEN_BRACKET_CLOSE 15
 
 typedef int (*NCDConfigTokenizer_output) (void *user, int token, char *value, size_t line, size_t line_char);
 

+ 254 - 18
ncd/NCDValue.c

@@ -37,11 +37,20 @@
 
 #include <ncd/NCDValue.h>
 
+static int ncdvalue_comparator (void *unused, void *vv1, void *vv2)
+{
+    NCDValue *v1 = vv1;
+    NCDValue *v2 = vv2;
+    
+    return NCDValue_Compare(v1, v2);
+}
+
 static void value_assert (NCDValue *o)
 {
     switch (o->type) {
         case NCDVALUE_STRING:
         case NCDVALUE_LIST:
+        case NCDVALUE_MAP:
             return;
         default:
             ASSERT(0);
@@ -85,6 +94,36 @@ int NCDValue_InitCopy (NCDValue *o, NCDValue *v)
             return 0;
         } break;
         
+        case NCDVALUE_MAP: {
+            NCDValue_InitMap(o);
+            
+            for (NCDValue *ekey = NCDValue_MapFirstKey(v); ekey; ekey = NCDValue_MapNextKey(v, ekey)) {
+                NCDValue *eval = NCDValue_MapKeyValue(v, ekey);
+                
+                NCDValue tmp_key;
+                NCDValue tmp_val;
+                if (!NCDValue_InitCopy(&tmp_key, ekey)) {
+                    goto mapfail;
+                }
+                if (!NCDValue_InitCopy(&tmp_val, eval)) {
+                    NCDValue_Free(&tmp_key);
+                    goto mapfail;
+                }
+                
+                if (!NCDValue_MapInsert(o, tmp_key, tmp_val)) {
+                    NCDValue_Free(&tmp_key);
+                    NCDValue_Free(&tmp_val);
+                    goto mapfail;
+                }
+            }
+            
+            return 1;
+            
+        mapfail:
+            NCDValue_Free(o);
+            return 0;
+        } break;
+        
         default:
             ASSERT(0);
     }
@@ -110,6 +149,18 @@ void NCDValue_Free (NCDValue *o)
             }
         } break;
         
+        case NCDVALUE_MAP: {
+            BAVLNode *tn;
+            while (tn = BAVL_GetFirst(&o->map_tree)) {
+                NCDMapElement *e = UPPER_OBJECT(tn, NCDMapElement, map_tree_node);
+                
+                BAVL_Remove(&o->map_tree, &e->map_tree_node);
+                NCDValue_Free(&e->key);
+                NCDValue_Free(&e->val);
+                free(e);
+            }
+        } break;
+        
         default:
             ASSERT(0);
     }
@@ -353,6 +404,138 @@ NCDValue NCDValue_ListRemove (NCDValue *o, NCDValue *ev)
     return v;
 }
 
+void NCDValue_InitMap (NCDValue *o)
+{
+    o->type = NCDVALUE_MAP;
+    BAVL_Init(&o->map_tree, OFFSET_DIFF(NCDMapElement, key, map_tree_node), ncdvalue_comparator, NULL);
+    o->map_count = 0;
+}
+
+size_t NCDValue_MapCount (NCDValue *o)
+{
+    value_assert(o);
+    ASSERT(o->type == NCDVALUE_MAP)
+    
+    return o->map_count;
+}
+
+NCDValue * NCDValue_MapFirstKey (NCDValue *o)
+{
+    value_assert(o);
+    ASSERT(o->type == NCDVALUE_MAP)
+    
+    BAVLNode *tn = BAVL_GetFirst(&o->map_tree);
+    if (!tn) {
+        return NULL;
+    }
+    
+    NCDMapElement *e = UPPER_OBJECT(tn, NCDMapElement, map_tree_node);
+    value_assert(&e->key);
+    value_assert(&e->val);
+    
+    return &e->key;
+}
+
+NCDValue * NCDValue_MapNextKey (NCDValue *o, NCDValue *ekey)
+{
+    value_assert(o);
+    ASSERT(o->type == NCDVALUE_MAP)
+    
+    NCDMapElement *e = UPPER_OBJECT(ekey, NCDMapElement, key);
+    value_assert(&e->key);
+    value_assert(&e->val);
+    
+    BAVLNode *tn = BAVL_GetNext(&o->map_tree, &e->map_tree_node);
+    if (!tn) {
+        return NULL;
+    }
+    
+    NCDMapElement *ne = UPPER_OBJECT(tn, NCDMapElement, map_tree_node);
+    value_assert(&ne->key);
+    value_assert(&ne->val);
+    
+    return &ne->key;
+}
+
+NCDValue * NCDValue_MapKeyValue (NCDValue *o, NCDValue *ekey)
+{
+    value_assert(o);
+    ASSERT(o->type == NCDVALUE_MAP)
+    
+    NCDMapElement *e = UPPER_OBJECT(ekey, NCDMapElement, key);
+    value_assert(&e->key);
+    value_assert(&e->val);
+    
+    return &e->val;
+}
+
+NCDValue * NCDValue_MapFindKey (NCDValue *o, NCDValue *key)
+{
+    value_assert(o);
+    ASSERT(o->type == NCDVALUE_MAP)
+    
+    BAVLNode *tn = BAVL_LookupExact(&o->map_tree, key);
+    if (!tn) {
+        return NULL;
+    }
+    
+    NCDMapElement *e = UPPER_OBJECT(tn, NCDMapElement, map_tree_node);
+    value_assert(&e->key);
+    value_assert(&e->val);
+    ASSERT(!NCDValue_Compare(&e->key, key))
+    
+    return &e->key;
+}
+
+NCDValue * NCDValue_MapInsert (NCDValue *o, NCDValue key, NCDValue val)
+{
+    value_assert(o);
+    ASSERT(o->type == NCDVALUE_MAP)
+    value_assert(&key);
+    value_assert(&val);
+    ASSERT(!NCDValue_MapFindKey(o, &key))
+    
+    if (o->map_count == SIZE_MAX) {
+        return NULL;
+    }
+    
+    NCDMapElement *e = malloc(sizeof(*e));
+    if (!e) {
+        return NULL;
+    }
+    
+    e->key = key;
+    e->val = val;
+    int res = BAVL_Insert(&o->map_tree, &e->map_tree_node, NULL);
+    ASSERT(res)
+    
+    o->map_count++;
+    
+    return &e->key;
+}
+
+void NCDValue_MapRemove (NCDValue *o, NCDValue *ekey, NCDValue *out_key, NCDValue *out_val)
+{
+    value_assert(o);
+    ASSERT(o->type == NCDVALUE_MAP)
+    ASSERT(out_key)
+    ASSERT(out_val)
+    ASSERT(o->map_count > 0)
+    
+    NCDMapElement *e = UPPER_OBJECT(ekey, NCDMapElement, key);
+    value_assert(&e->key);
+    value_assert(&e->val);
+    
+    BAVL_Remove(&o->map_tree, &e->map_tree_node);
+    
+    *out_key = e->key;
+    *out_val = e->val;
+    
+    o->map_count--;
+    
+    free(e);
+}
+
 int NCDValue_Compare (NCDValue *o, NCDValue *v)
 {
     value_assert(o);
@@ -366,6 +549,22 @@ int NCDValue_Compare (NCDValue *o, NCDValue *v)
         return 1;
     }
     
+    if (o->type == NCDVALUE_STRING && v->type == NCDVALUE_MAP) {
+        return -1;
+    }
+    
+    if (o->type == NCDVALUE_MAP && v->type == NCDVALUE_STRING) {
+        return 1;
+    }
+    
+    if (o->type == NCDVALUE_LIST && v->type == NCDVALUE_MAP) {
+        return -1;
+    }
+    
+    if (o->type == NCDVALUE_MAP && v->type == NCDVALUE_LIST) {
+        return 1;
+    }
+    
     if (o->type == NCDVALUE_STRING && v->type == NCDVALUE_STRING) {
         int cmp = strcmp(o->string, v->string);
         if (cmp < 0) {
@@ -377,26 +576,63 @@ int NCDValue_Compare (NCDValue *o, NCDValue *v)
         return 0;
     }
     
-    NCDValue *x = NCDValue_ListFirst(o);
-    NCDValue *y = NCDValue_ListFirst(v);
-    
-    while (1) {
-        if (!x && y) {
-            return -1;
-        }
-        if (x && !y) {
-            return 1;
-        }
-        if (!x && !y) {
-            return 0;
-        }
+    if (o->type == NCDVALUE_LIST && v->type == NCDVALUE_LIST) {
+        NCDValue *x = NCDValue_ListFirst(o);
+        NCDValue *y = NCDValue_ListFirst(v);
         
-        int res = NCDValue_Compare(x, y);
-        if (res) {
-            return res;
+        while (1) {
+            if (!x && y) {
+                return -1;
+            }
+            if (x && !y) {
+                return 1;
+            }
+            if (!x && !y) {
+                return 0;
+            }
+            
+            int res = NCDValue_Compare(x, y);
+            if (res) {
+                return res;
+            }
+            
+            x = NCDValue_ListNext(o, x);
+            y = NCDValue_ListNext(v, y);
         }
+    }
+    
+    if (o->type == NCDVALUE_MAP && v->type == NCDVALUE_MAP) {
+        NCDValue *key1 = NCDValue_MapFirstKey(o);
+        NCDValue *key2 = NCDValue_MapFirstKey(v);
         
-        x = NCDValue_ListNext(o, x);
-        y = NCDValue_ListNext(v, y);
+        while (1) {
+            if (!key1 && key2) {
+                return -1;
+            }
+            if (key1 && !key2) {
+                return 1;
+            }
+            if (!key1 && !key2) {
+                return 0;
+            }
+            
+            int res = NCDValue_Compare(key1, key2);
+            if (res) {
+                return res;
+            }
+            
+            NCDValue *val1 = NCDValue_MapKeyValue(o, key1);
+            NCDValue *val2 = NCDValue_MapKeyValue(v, key2);
+            
+            res = NCDValue_Compare(val1, val2);
+            if (res) {
+                return res;
+            }
+            
+            key1 = NCDValue_MapNextKey(o, key1);
+            key2 = NCDValue_MapNextKey(v, key2);
+        }
     }
+    
+    ASSERT(0)
 }

+ 21 - 0
ncd/NCDValue.h

@@ -34,9 +34,11 @@
 
 #include <misc/debug.h>
 #include <structure/LinkedList2.h>
+#include <structure/BAVL.h>
 
 #define NCDVALUE_STRING 1
 #define NCDVALUE_LIST 2
+#define NCDVALUE_MAP 3
 
 typedef struct {
     int type;
@@ -46,6 +48,10 @@ typedef struct {
             LinkedList2 list;
             size_t list_count;
         };
+        struct {
+            BAVL map_tree;
+            size_t map_count;
+        };
     };
 } NCDValue;
 
@@ -54,6 +60,12 @@ typedef struct {
     NCDValue v;
 } NCDListElement;
 
+typedef struct {
+    BAVLNode map_tree_node;
+    NCDValue key;
+    NCDValue val;
+} NCDMapElement;
+
 int NCDValue_InitCopy (NCDValue *o, NCDValue *v) WARN_UNUSED;
 void NCDValue_Free (NCDValue *o);
 int NCDValue_Type (NCDValue *o);
@@ -73,6 +85,15 @@ NCDValue * NCDValue_ListGet (NCDValue *o, size_t pos);
 NCDValue NCDValue_ListShift (NCDValue *o);
 NCDValue NCDValue_ListRemove (NCDValue *o, NCDValue *ev);
 
+void NCDValue_InitMap (NCDValue *o);
+size_t NCDValue_MapCount (NCDValue *o);
+NCDValue * NCDValue_MapFirstKey (NCDValue *o);
+NCDValue * NCDValue_MapNextKey (NCDValue *o, NCDValue *ekey);
+NCDValue * NCDValue_MapKeyValue (NCDValue *o, NCDValue *ekey);
+NCDValue * NCDValue_MapFindKey (NCDValue *o, NCDValue *key);
+NCDValue * NCDValue_MapInsert (NCDValue *o, NCDValue key, NCDValue val) WARN_UNUSED;
+void NCDValue_MapRemove (NCDValue *o, NCDValue *ekey, NCDValue *out_key, NCDValue *out_val);
+
 int NCDValue_Compare (NCDValue *o, NCDValue *v);
 
 #endif

+ 40 - 0
ncd/NCDValueGenerator.c

@@ -99,6 +99,46 @@ static int generate_value (NCDValue *value, ExpString *out_str)
             }
         } break;
         
+        case NCDVALUE_MAP: {
+            if (!ExpString_AppendChar(out_str, '[')) {
+                BLog(BLOG_ERROR, "ExpString_AppendChar failed");
+                goto fail;
+            }
+            
+            int is_first = 1;
+            
+            for (NCDValue *ekey = NCDValue_MapFirstKey(value); ekey; ekey = NCDValue_MapNextKey(value, ekey)) {
+                NCDValue *eval = NCDValue_MapKeyValue(value, ekey);
+                
+                if (!is_first) {
+                    if (!ExpString_Append(out_str, ", ")) {
+                        BLog(BLOG_ERROR, "ExpString_Append failed");
+                        goto fail;
+                    }
+                }
+                
+                if (!generate_value(ekey, out_str)) {
+                    goto fail;
+                }
+                
+                if (!ExpString_AppendChar(out_str, ':')) {
+                    BLog(BLOG_ERROR, "ExpString_AppendChar failed");
+                    goto fail;
+                }
+                
+                if (!generate_value(eval, out_str)) {
+                    goto fail;
+                }
+                
+                is_first = 0;
+            }
+            
+            if (!ExpString_AppendChar(out_str, ']')) {
+                BLog(BLOG_ERROR, "ExpString_AppendChar failed");
+                goto fail;
+            }
+        } break;
+        
         default: ASSERT(0);
     }
     

+ 111 - 23
ncd/NCDValueParser.c

@@ -56,6 +56,11 @@ struct parser_state {
     void *parser;
 };
 
+static int tokenizer_output (void *user, int token, char *value, size_t line, size_t line_char);
+static int build_value (struct NCDConfig_list *ast, NCDValue *out);
+static int build_list_value (struct NCDConfig_list *list, NCDValue *out);
+static int build_map_value (struct NCDConfig_list *list, NCDValue *out);
+
 static int tokenizer_output (void *user, int token, char *value, size_t line, size_t line_char)
 {
     struct parser_state *state = (struct parser_state *)user;
@@ -103,6 +108,18 @@ static int tokenizer_output (void *user, int token, char *value, size_t line, si
             Parse(state->parser, STRING, value, &state->out);
         } break;
         
+        case NCD_TOKEN_COLON: {
+            Parse(state->parser, COLON, NULL, &state->out);
+        } break;
+        
+        case NCD_TOKEN_BRACKET_OPEN: {
+            Parse(state->parser, BRACKET_OPEN, NULL, &state->out);
+        } break;
+        
+        case NCD_TOKEN_BRACKET_CLOSE: {
+            Parse(state->parser, BRACKET_CLOSE, NULL, &state->out);
+        } break;
+        
         default:
             ASSERT(0);
     }
@@ -124,6 +141,34 @@ fail:
     return 0;
 }
 
+static int build_value (struct NCDConfig_list *ast, NCDValue *out)
+{
+    switch (ast->type) {
+        case NCDCONFIG_ARG_STRING: {
+            if (!NCDValue_InitString(out, ast->string)) {
+                BLog(BLOG_ERROR, "NCDValue_InitString failed");
+                return 0;
+            }
+        } break;
+        
+        case NCDCONFIG_ARG_LIST: {
+            if (!build_list_value(ast->list, out)) {
+                return 0;
+            }
+        } break;
+        
+        case NCDCONFIG_ARG_MAPLIST: {
+            if (!build_map_value(ast->list, out)) {
+                return 0;
+            }
+        } break;
+        
+        default: ASSERT(0);
+    }
+    
+    return 1;
+}
+
 static int build_list_value (struct NCDConfig_list *list, NCDValue *out)
 {
     NCDValue list_val;
@@ -132,19 +177,8 @@ static int build_list_value (struct NCDConfig_list *list, NCDValue *out)
     for (struct NCDConfig_list *elem = list; elem; elem = elem->next) {
         NCDValue v;
         
-        if (elem->type == NCDCONFIG_ARG_STRING) {
-            if (!NCDValue_InitString(&v, elem->string)) {
-                BLog(BLOG_ERROR, "NCDValue_InitString failed");
-                goto fail;
-            }
-        }
-        else if (elem->type == NCDCONFIG_ARG_LIST) {
-            if (!build_list_value(elem->list, &v)) {
-                goto fail;
-            }
-        }
-        else {
-            ASSERT(0)
+        if (!build_value(elem, &v)) {
+            goto fail;
         }
         
         if (!NCDValue_ListAppend(&list_val, v)) {
@@ -162,6 +196,49 @@ fail:
     return 0;
 }
 
+static int build_map_value (struct NCDConfig_list *list, NCDValue *out)
+{
+    NCDValue map_val;
+    NCDValue_InitMap(&map_val);
+    
+    for (struct NCDConfig_list *elem = list; elem; elem = elem->next->next) {
+        ASSERT(elem->next)
+        
+        NCDValue key;
+        NCDValue val;
+        
+        if (!build_value(elem, &key)) {
+            goto fail;
+        }
+        
+        if (!build_value(elem->next, &val)) {
+            NCDValue_Free(&key);
+            goto fail;
+        }
+        
+        if (NCDValue_MapFindKey(&map_val, &key)) {
+            BLog(BLOG_ERROR, "duplicate map keys");
+            NCDValue_Free(&key);
+            NCDValue_Free(&val);
+            goto fail;
+        }
+        
+        if (!NCDValue_MapInsert(&map_val, key, val)) {
+            BLog(BLOG_ERROR, "NCDValue_MapInsert failed");
+            NCDValue_Free(&key);
+            NCDValue_Free(&val);
+            goto fail;
+        }
+    }
+    
+    *out = map_val;
+    return 1;
+    
+fail:
+    NCDValue_Free(&map_val);
+    return 0;
+}
+
 int NCDValueParser_Parse (const char *str, size_t str_len, NCDValue *out_value)
 {
     int res = 0;
@@ -189,19 +266,30 @@ int NCDValueParser_Parse (const char *str, size_t str_len, NCDValue *out_value)
         goto out;
     }
     
-    ASSERT(state.out.ast_type == AST_TYPE_STRING || state.out.ast_type == AST_TYPE_LIST)
+    ASSERT(state.out.ast_type == AST_TYPE_STRING || state.out.ast_type == AST_TYPE_LIST ||
+           state.out.ast_type == AST_TYPE_MAP)
     
     // convert AST to value
     NCDValue val;
-    if (state.out.ast_type == AST_TYPE_STRING) {
-        if (!NCDValue_InitString(&val, state.out.ast_string)) {
-            BLog(BLOG_ERROR, "NCDValue_InitString failed");
-            goto out;
-        }
-    } else {
-        if (!build_list_value(state.out.ast_list, &val)) {
-            goto out;
-        }
+    switch (state.out.ast_type) {
+        case AST_TYPE_STRING: {
+            if (!NCDValue_InitString(&val, state.out.ast_string)) {
+                BLog(BLOG_ERROR, "NCDValue_InitString failed");
+                goto out;
+            }
+        } break;
+        
+        case AST_TYPE_LIST: {
+            if (!build_list_value(state.out.ast_list, &val)) {
+                goto out;
+            }
+        } break;
+        
+        case AST_TYPE_MAP: {
+            if (!build_map_value(state.out.ast_list, &val)) {
+                goto out;
+            }
+        } break;
     }
     
     *out_value = val;

+ 55 - 0
ncd/NCDValueParser_parse.y

@@ -38,6 +38,7 @@
 #define AST_TYPE_NONE 0
 #define AST_TYPE_STRING 1
 #define AST_TYPE_LIST 2
+#define AST_TYPE_MAP 3
 
 struct parser_out {
     int out_of_memory;
@@ -57,10 +58,14 @@ struct parser_out {
 
 %type list_contents {struct NCDConfig_list *}
 %type list {struct NCDConfig_list *}
+%type map_contents {struct NCDConfig_list *}
+%type map {struct NCDConfig_list *}
 %type value {struct NCDConfig_list *}
 
 %destructor list_contents { NCDConfig_free_list($$); }
 %destructor list { NCDConfig_free_list($$); }
+%destructor map_contents { NCDConfig_free_list($$); }
+%destructor map { NCDConfig_free_list($$); }
 %destructor value { NCDConfig_free_list($$); }
 
 %stack_size 0
@@ -90,6 +95,13 @@ input ::= list(A). {
     parser_out->ast_type = AST_TYPE_LIST;
 }
 
+input ::= map(A). {
+    ASSERT(parser_out->ast_type == AST_TYPE_NONE)
+
+    parser_out->ast_list = A;
+    parser_out->ast_type = AST_TYPE_MAP;
+}
+
 list_contents(R) ::= value(A). {
     R = A;
 }
@@ -104,6 +116,34 @@ list_contents(R) ::= value(A) COMMA list_contents(N). {
     R = A;
 }
 
+map_contents(R) ::= value(A) COLON value(B). {
+    if (!A || !B) {
+        NCDConfig_free_list(A);
+        NCDConfig_free_list(B);
+        R = NULL;
+    } else {
+        ASSERT(!A->next)
+        ASSERT(!B->next)
+        A->next = B;
+        R = A;
+    }
+}
+
+map_contents(R) ::= value(A) COLON value(B) COMMA map_contents(N). {
+    if (!A || !B) {
+        NCDConfig_free_list(A);
+        NCDConfig_free_list(B);
+        NCDConfig_free_list(N);
+        R = NULL;
+    } else {
+        ASSERT(!A->next)
+        ASSERT(!B->next)
+        A->next = B;
+        B->next = N;
+        R = A;
+    }
+}
+
 list(R) ::= CURLY_OPEN CURLY_CLOSE. {
     R = NULL;
 }
@@ -112,6 +152,14 @@ list(R) ::= CURLY_OPEN list_contents(A) CURLY_CLOSE. {
     R = A;
 }
 
+map(R) ::= BRACKET_OPEN BRACKET_CLOSE. {
+    R = NULL;
+}
+
+map(R) ::= BRACKET_OPEN map_contents(A) BRACKET_CLOSE. {
+    R = A;
+}
+
 value(R) ::= STRING(A). {
     R = NCDConfig_make_list_string(A, NULL);
     if (!R) {
@@ -125,3 +173,10 @@ value(R) ::= list(A). {
         parser_out->out_of_memory = 1;
     }
 }
+
+value(R) ::= map(A). {
+    R = NCDConfig_make_list_maplist(A, NULL);
+    if (!R) {
+        parser_out->out_of_memory = 1;
+    }
+}

+ 281 - 46
ncd/modules/value.c

@@ -30,26 +30,32 @@
  * 
  * Synopsis:
  *   value(value)
- *   value value::get(string index)
- *   value value::getpath(list(string) path)
+ *   value value::get(what)
+ *   value value::getpath(list path)
  * 
  * Description:
  *   Value objects allow examining and manipulating values.
  * 
- *   value(value) constructs a value object from the given value.
+ *   value(value) constructs a new value object from the given value.
  * 
- *   value::get(index) constructs a value object from the list element
- *   of the value, which must be a list. This it *not* a copy, and the
- *   two value objects will share the same value data (more correctly, different
- *   portions of it).
+ *   value::get(what) constructs a value object for the element at position 'what'
+ *   (for a list), or the value corresponding to key 'what' (for a map). It is an
+ *   error if the base value is not a list or a map, the index is out of bounds of
+ *   the list, or the key does not exist in the map.
+ *   The resulting value object is NOT a copy, and shares (part of) the same
+ *   underlying value structure as the base value object. Deleting it will remove
+ *   it from the list or map it is part of.
  * 
  *   value::getpath(path) is like get(), except that it performs multiple
  *   consecutive resolutions. Also, if the path is an empty list, it performs
  *   no resulution at all.
  * 
  * Variables:
- *   type - type of the value; "string" or "list"
- *   length - length of the list (only if the value if a list)
+ *   (empty) - the value stored in the value object
+ *   type - type of the value; "string", "list" or "map"
+ *   length - number of elements in the list or map (only if the value if a list
+ *            or a map)
+ *   keys - a list of keys in the map (only if the value is a map)
  * 
  * Synopsis:
  *   value::delete()
@@ -57,8 +63,9 @@
  * Description:
  *   Deletes the underlying value data of this value object. After delection,
  *   the value object enters a deleted state, which will cause any operation
- *   on it to fail. Any other value objects which were sharing the deleted
- *   value data or portions of it will too enter deleted state.
+ *   on it to fail. Any other value objects which referred to the same value
+ *   or parts of it will too enter deleted state. If the value was an element
+ *   in a list or map, is is removed from it.
  */
 
 #include <stdlib.h>
@@ -71,6 +78,7 @@
 #include <misc/parse_number.h>
 #include <structure/LinkedList0.h>
 #include <structure/IndexedList.h>
+#include <structure/BCountAVL.h>
 #include <ncd/NCDModule.h>
 
 #include <generated/blog_channel_ncd_value.h>
@@ -93,6 +101,10 @@ struct value {
         struct {
             IndexedListNode list_contents_il_node;
         } list_parent;
+        struct {
+            NCDValue key;
+            BCountAVLNode map_contents_tree_node;
+        } map_parent;
     };
     
     int type;
@@ -103,9 +115,13 @@ struct value {
         struct {
             IndexedList list_contents_il;
         } list;
+        struct {
+            BCountAVL map_contents_tree;
+        } map;
     };
 };
 
+static int ncdvalue_comparator (void *unused, void *vv1, void *vv2);
 static const char * get_type_str (int type);
 static void value_cleanup (struct value *v);
 static void value_delete (struct value *v);
@@ -115,16 +131,31 @@ static size_t value_list_len (struct value *v);
 static struct value * value_list_at (struct value *v, size_t index);
 static int value_list_insert (NCDModuleInst *i, struct value *list, struct value *v, size_t index);
 static void value_list_remove (struct value *list, struct value *v);
+static struct value * value_init_map (NCDModuleInst *i);
+static size_t value_map_len (struct value *map);
+static struct value * value_map_at (struct value *map, size_t index);
+static struct value * value_map_find (struct value *map, NCDValue *key);
+static int value_map_insert (struct value *map, struct value *v, NCDValue key, NCDModuleInst *i);
+static void value_map_remove (struct value *map, struct value *v);
 static struct value * value_init_fromvalue (NCDModuleInst *i, NCDValue *value);
 static int value_to_value (NCDModuleInst *i, struct value *v, NCDValue *out_value);
-static struct value * value_get (NCDModuleInst *i, struct value *v, const char *index_str);
+static struct value * value_get (NCDModuleInst *i, struct value *v, NCDValue *what);
 static struct value * value_get_path (NCDModuleInst *i, struct value *v, NCDValue *path);
 
+static int ncdvalue_comparator (void *unused, void *vv1, void *vv2)
+{
+    NCDValue *v1 = vv1;
+    NCDValue *v2 = vv2;
+    
+    return NCDValue_Compare(v1, v2);
+}
+
 static const char * get_type_str (int type)
 {
     switch (type) {
         case NCDVALUE_STRING: return "string";
         case NCDVALUE_LIST: return "list";
+        case NCDVALUE_MAP: return "map";
     }
     ASSERT(0)
     return NULL;
@@ -149,6 +180,14 @@ static void value_cleanup (struct value *v)
             }
         } break;
         
+        case NCDVALUE_MAP: {
+            while (value_map_len(v) > 0) {
+                struct value *ev = value_map_at(v, 0);
+                value_map_remove(v, ev);
+                value_cleanup(ev);
+            }
+        } break;
+        
         default: ASSERT(0);
     }
     
@@ -158,8 +197,15 @@ static void value_cleanup (struct value *v)
 static void value_delete (struct value *v)
 {
     if (v->parent) {
-        ASSERT(v->parent->type == NCDVALUE_LIST)
-        value_list_remove(v->parent, v);
+        switch (v->parent->type) {
+            case NCDVALUE_LIST: {
+                value_list_remove(v->parent, v);
+            } break;
+            case NCDVALUE_MAP: {
+                value_map_remove(v->parent, v);
+            } break;
+            default: ASSERT(0);
+        }
     }
     
     LinkedList0Node *ln;
@@ -182,6 +228,13 @@ static void value_delete (struct value *v)
             }
         } break;
         
+        case NCDVALUE_MAP: {
+            while (value_map_len(v) > 0) {
+                struct value *ev = value_map_at(v, 0);
+                value_delete(ev);
+            }
+        } break;
+        
         default: ASSERT(0);
     }
     
@@ -277,6 +330,89 @@ static void value_list_remove (struct value *list, struct value *v)
     v->parent = NULL;
 }
 
+static struct value * value_init_map (NCDModuleInst *i)
+{
+    struct value *v = malloc(sizeof(*v));
+    if (!v) {
+        ModuleLog(i, BLOG_ERROR, "malloc failed");
+        return NULL;
+    }
+    
+    LinkedList0_Init(&v->refs_list);
+    v->parent = NULL;
+    v->type = NCDVALUE_MAP;
+    
+    BCountAVL_Init(&v->map.map_contents_tree, OFFSET_DIFF(struct value, map_parent.key, map_parent.map_contents_tree_node), ncdvalue_comparator, NULL);
+    
+    return v;
+}
+
+static size_t value_map_len (struct value *map)
+{
+    ASSERT(map->type == NCDVALUE_MAP)
+    
+    return BCountAVL_Count(&map->map.map_contents_tree);
+}
+
+static struct value * value_map_at (struct value *map, size_t index)
+{
+    ASSERT(map->type == NCDVALUE_MAP)
+    ASSERT(index < value_map_len(map))
+    
+    BCountAVLNode *tn = BCountAVL_GetAt(&map->map.map_contents_tree, index);
+    ASSERT(tn)
+    
+    struct value *e = UPPER_OBJECT(tn, struct value, map_parent.map_contents_tree_node);
+    ASSERT(e->parent == map)
+    
+    return e;
+}
+
+static struct value * value_map_find (struct value *map, NCDValue *key)
+{
+    ASSERT(map->type == NCDVALUE_MAP)
+    ASSERT(key)
+    
+    BCountAVLNode *tn = BCountAVL_LookupExact(&map->map.map_contents_tree, key);
+    if (!tn) {
+        return NULL;
+    }
+    
+    struct value *e = UPPER_OBJECT(tn, struct value, map_parent.map_contents_tree_node);
+    ASSERT(e->parent == map)
+    
+    return e;
+}
+
+static int value_map_insert (struct value *map, struct value *v, NCDValue key, NCDModuleInst *i)
+{
+    ASSERT(map->type == NCDVALUE_MAP)
+    ASSERT(!v->parent)
+    ASSERT(!value_map_find(map, &key))
+    
+    if (value_map_len(map) == SIZE_MAX) {
+        ModuleLog(i, BLOG_ERROR, "map has too many elements");
+        return 0;
+    }
+    
+    v->map_parent.key = key;
+    int res = BCountAVL_Insert(&map->map.map_contents_tree, &v->map_parent.map_contents_tree_node, NULL);
+    ASSERT(res)
+    v->parent = map;
+    
+    return 1;
+}
+
+static void value_map_remove (struct value *map, struct value *v)
+{
+    ASSERT(map->type == NCDVALUE_MAP)
+    ASSERT(v->parent == map)
+    
+    BCountAVL_Remove(&map->map.map_contents_tree, &v->map_parent.map_contents_tree_node);
+    NCDValue_Free(&v->map_parent.key);
+    v->parent = NULL;
+}
+
 static struct value * value_init_fromvalue (NCDModuleInst *i, NCDValue *value)
 {
     struct value *v;
@@ -306,6 +442,34 @@ static struct value * value_init_fromvalue (NCDModuleInst *i, NCDValue *value)
             }
         } break;
         
+        case NCDVALUE_MAP: {
+            if (!(v = value_init_map(i))) {
+                goto fail0;
+            }
+            
+            for (NCDValue *ekey = NCDValue_MapFirstKey(value); ekey; ekey = NCDValue_MapNextKey(value, ekey)) {
+                NCDValue *eval = NCDValue_MapKeyValue(value, ekey);
+                
+                NCDValue key;
+                if (!NCDValue_InitCopy(&key, ekey)) {
+                    BLog(BLOG_ERROR, "NCDValue_InitCopy failed");
+                    goto fail1;
+                }
+                
+                struct value *ev = value_init_fromvalue(i, eval);
+                if (!ev) {
+                    NCDValue_Free(&key);
+                    goto fail1;
+                }
+                
+                if (!value_map_insert(v, ev, key, i)) {
+                    NCDValue_Free(&key);
+                    value_cleanup(ev);
+                    goto fail1;
+                }
+            }
+        } break;
+        
         default: ASSERT(0);
     }
     
@@ -323,10 +487,8 @@ static int value_to_value (NCDModuleInst *i, struct value *v, NCDValue *out_valu
         case NCDVALUE_STRING: {
             if (!(NCDValue_InitString(out_value, v->string.string))) {
                 ModuleLog(i, BLOG_ERROR, "NCDValue_InitString failed");
-                return 0;
+                goto fail0;
             }
-            
-            return 1;
         } break;
         
         case NCDVALUE_LIST: {
@@ -344,22 +506,49 @@ static int value_to_value (NCDModuleInst *i, struct value *v, NCDValue *out_valu
                     goto fail1;
                 }
             }
+        } break;
+        
+        case NCDVALUE_MAP: {
+            NCDValue_InitMap(out_value);
             
-            return 1;
-            
-        fail1:
-            NCDValue_Free(out_value);
-            return 0;
+            for (size_t index = 0; index < value_map_len(v); index++) {
+                struct value *ev = value_map_at(v, index);
+                
+                NCDValue key;
+                NCDValue val;
+                
+                if (!NCDValue_InitCopy(&key, &ev->map_parent.key)) {
+                    ModuleLog(i, BLOG_ERROR, "NCDValue_InitCopy failed");
+                    goto fail1;
+                }
+                
+                if (!value_to_value(i, ev, &val)) {
+                    NCDValue_Free(&key);
+                    goto fail1;
+                }
+                
+                if (!NCDValue_MapInsert(out_value, key, val)) {
+                    ModuleLog(i, BLOG_ERROR, "NCDValue_MapInsert failed");
+                    NCDValue_Free(&key);
+                    NCDValue_Free(&val);
+                    goto fail1;
+                }
+            }
         } break;
         
         default: ASSERT(0);
     }
+    
+    return 1;
+    
+fail1:
+    NCDValue_Free(out_value);
+fail0:
+    return 0;
 }
 
-static struct value * value_get (NCDModuleInst *i, struct value *v, const char *index_str)
+static struct value * value_get (NCDModuleInst *i, struct value *v, NCDValue *what)
 {
-    ASSERT(index_str)
-    
     switch (v->type) {
         case NCDVALUE_STRING: {
             ModuleLog(i, BLOG_ERROR, "cannot resolve into a string");
@@ -367,20 +556,33 @@ static struct value * value_get (NCDModuleInst *i, struct value *v, const char *
         } break;
         
         case NCDVALUE_LIST: {
+            if (NCDValue_Type(what) != NCDVALUE_STRING) {
+                ModuleLog(i, BLOG_ERROR, "index is not a string (resolving into list)");
+                goto fail;
+            }
+            
             uintmax_t index;
-            if (!parse_unsigned_integer(index_str, &index)) {
-                ModuleLog(i, BLOG_ERROR, "index string is not a valid number (resolving into list)");
+            if (!parse_unsigned_integer(NCDValue_StringValue(what), &index)) {
+                ModuleLog(i, BLOG_ERROR, "index is not a valid number (resolving into list)");
                 goto fail;
             }
             
             if (index >= value_list_len(v)) {
-                ModuleLog(i, BLOG_ERROR, "index string is out of bounds (resolving into list)");
+                ModuleLog(i, BLOG_ERROR, "index is out of bounds (resolving into list)");
                 goto fail;
             }
             
             v = value_list_at(v, index);
         } break;
         
+        case NCDVALUE_MAP: {
+            v = value_map_find(v, what);
+            if (!v) {
+                ModuleLog(i, BLOG_ERROR, "key does not exist (resolving into map)");
+                goto fail;
+            }
+        } break;
+        
         default: ASSERT(0);
     }
     
@@ -395,12 +597,7 @@ static struct value * value_get_path (NCDModuleInst *i, struct value *v, NCDValu
     ASSERT(NCDValue_Type(path) == NCDVALUE_LIST)
     
     for (NCDValue *ev = NCDValue_ListFirst(path); ev; ev = NCDValue_ListNext(path, ev)) {
-        if (NCDValue_Type(ev) != NCDVALUE_STRING) {
-            ModuleLog(i, BLOG_ERROR, "path component is not a string");
-            goto fail;
-        }
-        
-        if (!(v = value_get(i, v, NCDValue_StringValue(ev)))) {
+        if (!(v = value_get(i, v, ev))) {
             goto fail;
         }
     }
@@ -464,7 +661,7 @@ static int func_getvar (void *vo, const char *name, NCDValue *out_value)
 {
     struct instance *o = vo;
     
-    if (strcmp(name, "type") && strcmp(name, "length") && strcmp(name, "")) {
+    if (strcmp(name, "type") && strcmp(name, "length") && strcmp(name, "keys") && strcmp(name, "")) {
         return 0;
     }
     
@@ -480,22 +677,64 @@ static int func_getvar (void *vo, const char *name, NCDValue *out_value)
         }
     }
     else if (!strcmp(name, "length")) {
-        if (o->v->type != NCDVALUE_LIST) {
-            ModuleLog(o->i, BLOG_ERROR, "value is not a list");
-            return 0;
+        size_t len;
+        switch (o->v->type) {
+            case NCDVALUE_LIST:
+                len = value_list_len(o->v);
+                break;
+            case NCDVALUE_MAP:
+                len = value_map_len(o->v);
+                break;
+            default:
+                ModuleLog(o->i, BLOG_ERROR, "value is not a list or map");
+                return 0;
         }
+        
         char str[64];
-        snprintf(str, sizeof(str), "%zu", value_list_len(o->v));
+        snprintf(str, sizeof(str), "%zu", len);
         if (!NCDValue_InitString(out_value, str)) {
             ModuleLog(o->i, BLOG_ERROR, "NCDValue_InitString failed");
             return 0;
         }
     }
+    else if (!strcmp(name, "keys")) {
+        if (o->v->type != NCDVALUE_MAP) {
+            ModuleLog(o->i, BLOG_ERROR, "value is not a map (reading keys variable)");
+            return 0;
+        }
+        
+        NCDValue_InitList(out_value);
+        
+        for (size_t i = 0; i < value_map_len(o->v); i++) {
+            struct value *ev = value_map_at(o->v, i);
+            
+            NCDValue key;
+            if (!NCDValue_InitCopy(&key, &ev->map_parent.key)) {
+                ModuleLog(o->i, BLOG_ERROR, "NCDValue_InitCopy failed");
+                goto map_fail1;
+            }
+            
+            if (!NCDValue_ListAppend(out_value, key)) {
+                ModuleLog(o->i, BLOG_ERROR, "NCDValue_ListAppend failed");
+                NCDValue_Free(&key);
+                goto map_fail1;
+            }
+        }
+        
+        return 1;
+        
+    map_fail1:
+        NCDValue_Free(out_value);
+        return 0;
+    }
     else if (!strcmp(name, "")) {
         if (!value_to_value(o->i, o->v, out_value)) {
             return 0;
         }
     }
+    else {
+        ASSERT(0);
+    }
     
     return 1;
 }
@@ -523,15 +762,11 @@ fail0:
 
 static void func_new_get (NCDModuleInst *i)
 {
-    NCDValue *index_arg;
-    if (!NCDValue_ListRead(i->args, 1, &index_arg)) {
+    NCDValue *what_arg;
+    if (!NCDValue_ListRead(i->args, 1, &what_arg)) {
         ModuleLog(i, BLOG_ERROR, "wrong arity");
         goto fail0;
     }
-    if (NCDValue_Type(index_arg) != NCDVALUE_STRING) {
-        ModuleLog(i, BLOG_ERROR, "wrong type");
-        goto fail0;
-    }
     
     struct instance *mo = ((NCDModuleInst *)i->method_user)->inst_user;
     
@@ -540,7 +775,7 @@ static void func_new_get (NCDModuleInst *i)
         goto fail0;
     }
     
-    struct value *v = value_get(i, mo->v, NCDValue_StringValue(index_arg));
+    struct value *v = value_get(i, mo->v, what_arg);
     if (!v) {
         goto fail0;
     }

+ 152 - 20
ncd/ncd.c

@@ -70,6 +70,7 @@
 #define ARG_VALUE_TYPE_STRING 1
 #define ARG_VALUE_TYPE_VARIABLE 2
 #define ARG_VALUE_TYPE_LIST 3
+#define ARG_VALUE_TYPE_MAP 4
 
 #define SSTATE_CHILD 1
 #define SSTATE_ADULT 2
@@ -87,6 +88,7 @@ struct arg_value {
         char *string;
         char **variable_names;
         LinkedList1 list;
+        LinkedList1 maplist;
     };
 };
 
@@ -95,6 +97,12 @@ struct arg_list_elem {
     struct arg_value value;
 };
 
+struct arg_map_elem {
+    LinkedList1Node maplist_node;
+    struct arg_value key;
+    struct arg_value val;
+};
+
 struct statement {
     char **object_names;
     char *method_name;
@@ -176,8 +184,12 @@ static int arg_value_init_string (struct arg_value *o, const char *string);
 static int arg_value_init_variable (struct arg_value *o, struct NCDConfig_strings *ast_names);
 static void arg_value_init_list (struct arg_value *o);
 static int arg_value_list_append (struct arg_value *o, struct arg_value v);
+static void arg_value_init_map (struct arg_value *o);
+static int arg_value_map_append (struct arg_value *o, struct arg_value key, struct arg_value val);
 static void arg_value_free (struct arg_value *o);
+static int build_arg_from_ast (struct arg_value *o, struct NCDConfig_list *ast);
 static int build_arg_list_from_ast_list (struct arg_value *o, struct NCDConfig_list *list);
+static int build_arg_map_from_ast_list (struct arg_value *o, struct NCDConfig_list *list);
 static char ** names_new (struct NCDConfig_strings *ast_names);
 static size_t names_count (char **names);
 static char * names_tostring (char **names);
@@ -646,6 +658,28 @@ int arg_value_list_append (struct arg_value *o, struct arg_value v)
     return 1;
 }
 
+void arg_value_init_map (struct arg_value *o)
+{
+    o->type = ARG_VALUE_TYPE_MAP;
+    LinkedList1_Init(&o->maplist);
+}
+
+int arg_value_map_append (struct arg_value *o, struct arg_value key, struct arg_value val)
+{
+    ASSERT(o->type == ARG_VALUE_TYPE_MAP)
+    
+    struct arg_map_elem *elem = malloc(sizeof(*elem));
+    if (!elem) {
+        BLog(BLOG_ERROR, "malloc failed");
+        return 0;
+    }
+    LinkedList1_Append(&o->maplist, &elem->maplist_node);
+    elem->key = key;
+    elem->val = val;
+    
+    return 1;
+}
+
 void arg_value_free (struct arg_value *o)
 {
     switch (o->type) {
@@ -666,8 +700,51 @@ void arg_value_free (struct arg_value *o)
             }
         } break;
         
+        case ARG_VALUE_TYPE_MAP: {
+            while (!LinkedList1_IsEmpty(&o->maplist)) {
+                struct arg_map_elem *elem = UPPER_OBJECT(LinkedList1_GetFirst(&o->maplist), struct arg_map_elem, maplist_node);
+                arg_value_free(&elem->key);
+                arg_value_free(&elem->val);
+                LinkedList1_Remove(&o->maplist, &elem->maplist_node);
+                free(elem);
+            }
+        } break;
+        
+        default: ASSERT(0);
+    }
+}
+
+int build_arg_from_ast (struct arg_value *o, struct NCDConfig_list *ast)
+{
+    switch (ast->type) {
+        case NCDCONFIG_ARG_STRING: {
+            if (!arg_value_init_string(o, ast->string)) {
+                return 0;
+            }
+        } break;
+        
+        case NCDCONFIG_ARG_VAR: {
+            if (!arg_value_init_variable(o, ast->var)) {
+                return 0;
+            }
+        } break;
+        
+        case NCDCONFIG_ARG_LIST: {
+            if (!build_arg_list_from_ast_list(o, ast->list)) {
+                return 0;
+            }
+        } break;
+        
+        case NCDCONFIG_ARG_MAPLIST: {
+            if (!build_arg_map_from_ast_list(o, ast->list)) {
+                return 0;
+            }
+        } break;
+        
         default: ASSERT(0);
     }
+    
+    return 1;
 }
 
 int build_arg_list_from_ast_list (struct arg_value *o, struct NCDConfig_list *list)
@@ -677,26 +754,8 @@ int build_arg_list_from_ast_list (struct arg_value *o, struct NCDConfig_list *li
     for (struct NCDConfig_list *c = list; c; c = c->next) {
         struct arg_value e;
         
-        switch (c->type) {
-            case NCDCONFIG_ARG_STRING: {
-                if (!arg_value_init_string(&e, c->string)) {
-                    goto fail;
-                }
-            } break;
-            
-            case NCDCONFIG_ARG_VAR: {
-                if (!arg_value_init_variable(&e, c->var)) {
-                    goto fail;
-                }
-            } break;
-            
-            case NCDCONFIG_ARG_LIST: {
-                if (!build_arg_list_from_ast_list(&e, c->list)) {
-                    goto fail;
-                }
-            } break;
-            
-            default: ASSERT(0);
+        if (!build_arg_from_ast(&e, c)) {
+            goto fail;
         }
         
         if (!arg_value_list_append(o, e)) {
@@ -712,6 +771,39 @@ fail:
     return 0;
 }
 
+int build_arg_map_from_ast_list (struct arg_value *o, struct NCDConfig_list *list)
+{
+    arg_value_init_map(o);
+    
+    for (struct NCDConfig_list *c = list; c; c = c->next->next) {
+        ASSERT(c->next)
+        
+        struct arg_value key;
+        struct arg_value val;
+        
+        if (!build_arg_from_ast(&key, c)) {
+            goto fail;
+        }
+        
+        if (!build_arg_from_ast(&val, c->next)) {
+            arg_value_free(&key);
+            goto fail;
+        }
+        
+        if (!arg_value_map_append(o, key, val)) {
+            arg_value_free(&key);
+            arg_value_free(&val);
+            goto fail;
+        }
+    }
+    
+    return 1;
+    
+fail:
+    arg_value_free(o);
+    return 0;
+}
+
 static char ** names_new (struct NCDConfig_strings *ast_names)
 {
     ASSERT(ast_names)
@@ -1461,6 +1553,46 @@ int process_statement_resolve_argument (struct process_statement *ps, struct arg
             return 0;
         } while (0); break;
         
+        case ARG_VALUE_TYPE_MAP: do {
+            NCDValue_InitMap(out);
+            
+            for (LinkedList1Node *n = LinkedList1_GetFirst(&arg->maplist); n; n = LinkedList1Node_Next(n)) {
+                struct arg_map_elem *elem = UPPER_OBJECT(n, struct arg_map_elem, maplist_node);
+                
+                NCDValue key;
+                NCDValue val;
+                
+                if (!process_statement_resolve_argument(ps, &elem->key, &key)) {
+                    goto map_fail;
+                }
+                
+                if (!process_statement_resolve_argument(ps, &elem->val, &val)) {
+                    NCDValue_Free(&key);
+                    goto map_fail;
+                }
+                
+                if (NCDValue_MapFindKey(out, &key)) {
+                    process_statement_log(ps, BLOG_ERROR, "duplicate map keys");
+                    NCDValue_Free(&key);
+                    NCDValue_Free(&val);
+                    goto map_fail;
+                }
+                
+                if (!NCDValue_MapInsert(out, key, val)) {
+                    process_statement_log(ps, BLOG_ERROR, "NCDValue_MapInsert failed");
+                    NCDValue_Free(&key);
+                    NCDValue_Free(&val);
+                    goto map_fail;
+                }
+            }
+            
+            break;
+            
+        map_fail:
+            NCDValue_Free(out);
+            return 0;
+        } while (0); break;
+        
         default: ASSERT(0);
     }