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 |
} |