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/541/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,29 @@ 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
+    /* the error_message is currently only set for parse errors */
96
+    char *error_message;
97
+    bool needs_tree_render;
98
+};
99
+
100
+/**
101
+ * Parses and executes the given command. If a yajl_gen is passed, a json reply
102
+ * will be generated in the format specified by the ipc protocol. Pass NULL if
103
+ * no json reply is required.
104
+ *
105
+ * Returns a CommandResult struct with useful information about the result of
106
+ * the command. Free with command_result_free()
107
+ */
108
+CommandResult *parse_command(const char *input, yajl_gen gen);
109
+
110
+/**
111
+ * Frees a CommandResult
112
+ */
113
+void command_result_free(CommandResult *result);

b/include/config_directives.h

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

b/include/config_parser.h

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

b/src/assignments.c

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

b/src/commands.c

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

b/src/commands_parser.c

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

b/src/config_parser.c

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

b/src/ipc.c

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

b/src/key_press.c

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