i3 - improved tiling WM


Refactor parse_command

Patch status: needinfo

Patch by Tony Crisci

Long description:

Change parse_command to make running commands easier and more efficient.

Caller is now responsible for allocating the yajl_gen and can pass NULL
if generating json is not required.

The CommandResult used as a return value has been typedeffed, contains
more information about the result of the command, and no longer contains
parsing state or a yajl gen.

The CommandResult used during command parsing itself has been renamed to
CommandResultIR to show that it is an intermediate representation of the
result used during parsing. The ConfigResult has been renamed similarly
for consistency.

To apply this patch, use:
curl http://cr.i3wm.org/patch/538/raw.patch | git am

b/generate-command-parser.pl

36
@@ -131,7 +131,7 @@ close($enumfh);
37
 
38
 # Third step: Generate the call function.
39
 open(my $callfh, '>', "GENERATED_${prefix}_call.h");
40
-my $resultname = uc(substr($prefix, 0, 1)) . substr($prefix, 1) . 'Result';
41
+my $resultname = uc(substr($prefix, 0, 1)) . substr($prefix, 1) . 'ResultIR';
42
 say $callfh "static void GENERATED_call(const int call_identifier, struct $resultname *result) {";
43
 say $callfh '    switch (call_identifier) {';
44
 my $call_id = 0;

b/include/commands.h

49
@@ -12,7 +12,7 @@
50
 #include "commands_parser.h"
51
 
52
 /** The beginning of the prototype for every cmd_ function. */
53
-#define I3_CMD Match *current_match, struct CommandResult *cmd_output
54
+#define I3_CMD Match *current_match, struct CommandResultIR *cmd_output
55
 
56
 /**
57
  * Initializes the specified 'Match' data structure and the initial state of

b/include/commands_parser.h

62
@@ -11,16 +11,11 @@
63
 
64
 #include <yajl/yajl_gen.h>
65
 
66
-/*
67
- * Holds the result of a call to any command. When calling
68
- * parse_command("floating enable, border none"), the parser will internally
69
- * use a struct CommandResult when calling cmd_floating and cmd_border.
70
- * parse_command will also return another struct CommandResult, whose
71
- * json_output is set to a map of individual json_outputs and whose
72
- * needs_tree_trender is true if any individual needs_tree_render was true.
73
- *
74
+/**
75
+ * An intermediate representation of the result of a command used during
76
+ * command parsing
77
  */
78
-struct CommandResult {
79
+struct CommandResultIR {
80
     /* The JSON generator to append a reply to. */
81
     yajl_gen json_gen;
82
 
83
@@ -33,4 +28,28 @@ struct CommandResult {
84
     bool needs_tree_render;
85
 };
86
 
87
-struct CommandResult *parse_command(const char *input);
88
+typedef struct CommandResult CommandResult;
89
+
90
+/**
91
+ * A struct that contains useful information about the result of a command
92
+ */
93
+struct CommandResult {
94
+    bool parse_error;
95
+    char *error_message; /* the error_message is currently only set for parse errors */
96
+    bool needs_tree_render;
97
+};
98
+
99
+/**
100
+ * Parses and executes the given command. If a yajl_gen is passed, a json reply
101
+ * will be generated in the format specified by the ipc protocol. Pass NULL if
102
+ * no json reply is required.
103
+ *
104
+ * Returns a CommandResult struct with useful information about the result of
105
+ * the command. Free with command_result_free()
106
+ */
107
+CommandResult *parse_command(const char *input, yajl_gen gen);
108
+
109
+/**
110
+ * Frees a CommandResult
111
+ */
112
+void command_result_free(CommandResult *result);

b/include/config_directives.h

117
@@ -18,7 +18,7 @@
118
 uint32_t modifiers_from_str(const char *str);
119
 
120
 /** The beginning of the prototype for every cfg_ function. */
121
-#define I3_CFG Match *current_match, struct ConfigResult *result
122
+#define I3_CFG Match *current_match, struct ConfigResultIR *result
123
 
124
 /* Defines a configuration function, that is, anything that can be called by
125
  * using 'call cfg_foo()' in parser-specs/.*.spec. Useful so that we don’t need

b/include/config_parser.h

130
@@ -18,7 +18,7 @@ extern pid_t config_error_nagbar_pid;
131
  * will be useful in the future when we implement a config parsing IPC command.
132
  *
133
  */
134
-struct ConfigResult {
135
+struct ConfigResultIR {
136
     /* The JSON generator to append a reply to. */
137
     yajl_gen json_gen;
138
 
139
@@ -28,7 +28,7 @@ struct ConfigResult {
140
     int next_state;
141
 };
142
 
143
-struct ConfigResult *parse_config(const char *input, struct context *context);
144
+struct ConfigResultIR *parse_config(const char *input, struct context *context);
145
 
146
 /**
147
  * Parses the given file by first replacing the variables, then calling

b/src/assignments.c

152
@@ -45,13 +45,13 @@ void run_assignments(i3Window *window) {
153
             DLOG("execute command %s\n", current->dest.command);
154
             char *full_command;
155
             sasprintf(&full_command, "[id=\"%d\"] %s", window->id, current->dest.command);
156
-            struct CommandResult *command_output = parse_command(full_command);
157
+            CommandResult *command_output = parse_command(full_command, NULL);
158
             free(full_command);
159
 
160
             if (command_output->needs_tree_render)
161
                 needs_tree_render = true;
162
 
163
-            yajl_gen_free(command_output->json_gen);
164
+            command_result_free(command_output);
165
         }
166
 
167
         /* Store that we ran this assignment to not execute it again */

b/src/commands.c

172
@@ -16,22 +16,29 @@
173
 #include "shmlog.h"
174
 
175
 // Macros to make the YAJL API a bit easier to use.
176
-#define y(x, ...) yajl_gen_ ## x (cmd_output->json_gen, ##__VA_ARGS__)
177
-#define ystr(str) yajl_gen_string(cmd_output->json_gen, (unsigned char*)str, strlen(str))
178
-#define ysuccess(success) do { \
179
-    y(map_open); \
180
-    ystr("success"); \
181
-    y(bool, success); \
182
-    y(map_close); \
183
-} while (0)
184
-#define yerror(message) do { \
185
-    y(map_open); \
186
-    ystr("success"); \
187
-    y(bool, false); \
188
-    ystr("error"); \
189
-    ystr(message); \
190
-    y(map_close); \
191
-} while (0)
192
+#define y(x, ...) if (cmd_output->json_gen != NULL) yajl_gen_ ## x (cmd_output->json_gen, ##__VA_ARGS__);
193
+
194
+#define ystr(str) if (cmd_output->json_gen != NULL) yajl_gen_string(cmd_output->json_gen, (unsigned char*)str, strlen(str));
195
+
196
+#define ysuccess(success) if (cmd_output->json_gen != NULL) { \
197
+    do { \
198
+        y(map_open); \
199
+        ystr("success"); \
200
+        y(bool, success); \
201
+        y(map_close); \
202
+    } while (0); \
203
+}
204
+
205
+#define yerror(message) if (cmd_output->json_gen != NULL) { \
206
+    do { \
207
+        y(map_open); \
208
+        ystr("success"); \
209
+        y(bool, false); \
210
+        ystr("error"); \
211
+        ystr(message); \
212
+        y(map_close); \
213
+    } while (0); \
214
+}
215
 
216
 /** When the command did not include match criteria (!), we use the currently
217
  * focused container. Do not confuse this case with a command which included
218
@@ -95,7 +102,7 @@ static Output *get_output_of_con(Con *con) {
219
  * and return true, signaling that no further workspace switching should occur in the calling function.
220
  *
221
  */
222
-static bool maybe_back_and_forth(struct CommandResult *cmd_output, char *name) {
223
+static bool maybe_back_and_forth(struct CommandResultIR *cmd_output, char *name) {
224
     Con *ws = con_get_workspace(focused);
225
 
226
     /* If we switched to a different workspace, do nothing */

b/src/commands_parser.c

231
@@ -35,8 +35,8 @@
232
 #include "all.h"
233
 
234
 // Macros to make the YAJL API a bit easier to use.
235
-#define y(x, ...) yajl_gen_ ## x (command_output.json_gen, ##__VA_ARGS__)
236
-#define ystr(str) yajl_gen_string(command_output.json_gen, (unsigned char*)str, strlen(str))
237
+#define y(x, ...) if (command_output.json_gen != NULL) yajl_gen_ ## x (command_output.json_gen, ##__VA_ARGS__)
238
+#define ystr(str) if (command_output.json_gen != NULL) yajl_gen_string(command_output.json_gen, (unsigned char*)str, strlen(str))
239
 
240
 /*******************************************************************************
241
  * The data structures used for parsing. Essentially the current state and a
242
@@ -179,8 +179,8 @@ static cmdp_state state;
243
 #ifndef TEST_PARSER
244
 static Match current_match;
245
 #endif
246
-static struct CommandResult subcommand_output;
247
-static struct CommandResult command_output;
248
+static struct CommandResultIR subcommand_output;
249
+static struct CommandResultIR command_output;
250
 
251
 #include "GENERATED_command_call.h"
252
 
253
@@ -205,18 +205,24 @@ static void next_state(const cmdp_token *token) {
254
     }
255
 }
256
 
257
-struct CommandResult *parse_command(const char *input) {
258
+/*
259
+ * Parses and executes the given command. If a yajl_gen is passed, a json reply
260
+ * will be generated in the format specified by the ipc protocol. Pass NULL if
261
+ * no json reply is required.
262
+ *
263
+ * Returns a CommandResult struct with useful information about the result of
264
+ * the command. Free with command_result_free()
265
+ */
266
+CommandResult *parse_command(const char *input, yajl_gen gen) {
267
     DLOG("COMMAND: *%s*\n", input);
268
     state = INITIAL;
269
+    CommandResult *result = scalloc(sizeof(CommandResult));
270
 
271
-/* A YAJL JSON generator used for formatting replies. */
272
-#if YAJL_MAJOR >= 2
273
-    command_output.json_gen = yajl_gen_alloc(NULL);
274
-#else
275
-    command_output.json_gen = yajl_gen_alloc(NULL, NULL);
276
-#endif
277
+    /* A YAJL JSON generator used for formatting replies. */
278
+    command_output.json_gen = (gen != NULL ? gen : NULL);
279
 
280
     y(array_open);
281
+
282
     command_output.needs_tree_render = false;
283
 
284
     const char *walk = input;
285
@@ -382,6 +388,9 @@ struct CommandResult *parse_command(const char *input) {
286
             ELOG("Your command: %s\n", input);
287
             ELOG("              %s\n", position);
288
 
289
+            result->parse_error = true;
290
+            result->error_message = errormessage;
291
+
292
             /* Format this error message as a JSON reply. */
293
             y(map_open);
294
             ystr("success");
295
@@ -400,7 +409,6 @@ struct CommandResult *parse_command(const char *input) {
296
             y(map_close);
297
 
298
             free(position);
299
-            free(errormessage);
300
             clear_stack();
301
             break;
302
         }
303
@@ -408,7 +416,16 @@ struct CommandResult *parse_command(const char *input) {
304
 
305
     y(array_close);
306
 
307
-    return &command_output;
308
+    result->needs_tree_render = command_output.needs_tree_render;
309
+    return result;
310
+}
311
+
312
+void command_result_free(CommandResult *result) {
313
+    if (result == NULL)
314
+        return;
315
+
316
+    FREE(result->error_message);
317
+    FREE(result);
318
 }
319
 
320
 /*******************************************************************************
321
@@ -446,6 +463,10 @@ int main(int argc, char *argv[]) {
322
         fprintf(stderr, "Syntax: %s <command>\n", argv[0]);
323
         return 1;
324
     }
325
-    parse_command(argv[1]);
326
+    yajl_gen gen = yajl_gen_alloc(NULL);
327
+
328
+    parse_command(argv[1], gen);
329
+
330
+    yajl_gen_free(gen);
331
 }
332
 #endif

b/src/config_parser.c

337
@@ -232,8 +232,8 @@ static void clear_criteria(void *unused_criteria) {
338
 
339
 static cmdp_state state;
340
 static Match current_match;
341
-static struct ConfigResult subcommand_output;
342
-static struct ConfigResult command_output;
343
+static struct ConfigResultIR subcommand_output;
344
+static struct ConfigResultIR command_output;
345
 
346
 /* A list which contains the states that lead to the current state, e.g.
347
  * INITIAL, WORKSPACE_LAYOUT.
348
@@ -304,7 +304,7 @@ static char *single_line(const char *start) {
349
     return result;
350
 }
351
 
352
-struct ConfigResult *parse_config(const char *input, struct context *context) {
353
+struct ConfigResultIR *parse_config(const char *input, struct context *context) {
354
     /* Dump the entire config file into the debug log. We cannot just use
355
      * DLOG("%s", input); because one log message must not exceed 4 KiB. */
356
     const char *dumpwalk = input;
357
@@ -1004,7 +1004,7 @@ void parse_file(const char *f) {
358
     context = scalloc(sizeof(struct context));
359
     context->filename = f;
360
 
361
-    struct ConfigResult *config_output = parse_config(new, context);
362
+    struct ConfigResultIR *config_output = parse_config(new, context);
363
     yajl_gen_free(config_output->json_gen);
364
 
365
     check_for_duplicate_bindings(context);

b/src/ipc.c

370
@@ -117,20 +117,24 @@ IPC_HANDLER(command) {
371
     char *command = scalloc(message_size + 1);
372
     strncpy(command, (const char*)message, message_size);
373
     LOG("IPC: received: *%s*\n", command);
374
-    struct CommandResult *command_output = parse_command((const char*)command);
375
+    yajl_gen gen = yajl_gen_alloc(NULL);
376
+
377
+    CommandResult *command_output = parse_command((const char*)command, gen);
378
     free(command);
379
 
380
     if (command_output->needs_tree_render)
381
         tree_render();
382
 
383
+    command_result_free(command_output);
384
+
385
     const unsigned char *reply;
386
     ylength length;
387
-    yajl_gen_get_buf(command_output->json_gen, &reply, &length);
388
+    yajl_gen_get_buf(gen, &reply, &length);
389
 
390
     ipc_send_message(fd, length, I3_IPC_REPLY_TYPE_COMMAND,
391
                      (const uint8_t*)reply);
392
 
393
-    yajl_gen_free(command_output->json_gen);
394
+    yajl_gen_free(gen);
395
 }
396
 
397
 static void dump_rect(yajl_gen gen, const char *name, Rect r) {

b/src/key_press.c

402
@@ -76,9 +76,12 @@ void handle_key_press(xcb_key_press_event_t *event) {
403
     if (bind == NULL)
404
         return;
405
 
406
+    yajl_gen gen = yajl_gen_alloc(NULL);
407
+
408
     char *command_copy = sstrdup(bind->command);
409
-    struct CommandResult *command_output = parse_command(command_copy);
410
+    CommandResult *command_output = parse_command(command_copy, gen);
411
     free(command_copy);
412
+    command_result_free(command_output);
413
 
414
     if (command_output->needs_tree_render)
415
         tree_render();
416
@@ -95,7 +98,7 @@ void handle_key_press(xcb_key_press_event_t *event) {
417
 
418
     yajl_handle handle = yajl_alloc(&command_error_callbacks, &parse_conf, NULL, NULL);
419
 #endif
420
-    yajl_gen_get_buf(command_output->json_gen, &reply, &length);
421
+    yajl_gen_get_buf(gen, &reply, &length);
422
 
423
     current_nesting_level = 0;
424
     parse_error_key = false;
425
@@ -127,5 +130,5 @@ void handle_key_press(xcb_key_press_event_t *event) {
426
 
427
     yajl_free(handle);
428
 
429
-    yajl_gen_free(command_output->json_gen);
430
+    yajl_gen_free(gen);
431
 }