1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3  *
4  * Copyright (c) 2019 Tobias Kortkamp <tobik@FreeBSD.org>
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  */
28 
29 #include "config.h"
30 
31 #include <inttypes.h>
32 #include <stdbool.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 
37 #include <libias/array.h>
38 #include <libias/flow.h>
39 #include <libias/mempool.h>
40 
41 #include "ast.h"
42 #include "parser.h"
43 #include "parser/edits.h"
44 #include "rules.h"
45 
46 struct WalkerData {
47 	struct Parser *parser;
48 	struct Mempool *pool;
49 };
50 
51 // Prototypes
52 static bool is_candidate(struct AST *);
53 static bool has_eol_comment(struct AST *);
54 static void merge_variables(struct Array *, struct Array *);
55 static void process_siblings(struct Array *, struct Array *);
56 static enum ASTWalkState refactor_collapse_adjacent_variables_walker(struct AST *, struct WalkerData *, struct Array *);
57 
58 bool
is_candidate(struct AST * node)59 is_candidate(struct AST *node)
60 {
61 	if (node->type != AST_VARIABLE) {
62 		return false;
63 	}
64 
65 	switch (node->variable.modifier) {
66 	case AST_VARIABLE_MODIFIER_APPEND:
67 	case AST_VARIABLE_MODIFIER_ASSIGN:
68 		return true;
69 	default:
70 		return false;
71 	}
72 }
73 
74 bool
has_eol_comment(struct AST * node)75 has_eol_comment(struct AST *node)
76 {
77 	return node->variable.comment && strlen(node->variable.comment) > 0;
78 }
79 
80 void
merge_variables(struct Array * nodelist,struct Array * group)81 merge_variables(struct Array *nodelist, struct Array *group)
82 {
83 	if (array_len(group) < 2) {
84 		return;
85 	}
86 
87 	SCOPE_MEMPOOL(pool);
88 
89 	struct AST *first = array_get(group, 0);
90 	struct AST *last = array_get(group, array_len(group) - 1);
91 	ARRAY_FOREACH_SLICE(group, 1, -1, struct AST *, node) {
92 		ARRAY_FOREACH(node->variable.words, const char *, word) {
93 			array_append(first->variable.words, word);
94 		}
95 	}
96 	first->edited = true;
97 	first->line_end = last->line_end;
98 
99 	struct Array *newnodelist = mempool_array(pool);
100 	ARRAY_FOREACH(nodelist, struct AST *, node) {
101 		if (node->type == AST_VARIABLE) {
102 			if (array_find(group, node, NULL, NULL) < 1) {
103 				array_append(newnodelist, node);
104 			}
105 		} else {
106 			array_append(newnodelist, node);
107 		}
108 	}
109 	array_truncate(nodelist);
110 	ARRAY_JOIN(nodelist, newnodelist);
111 }
112 
113 void
process_siblings(struct Array * nodelist,struct Array * siblings)114 process_siblings(struct Array *nodelist, struct Array *siblings)
115 {
116 	SCOPE_MEMPOOL(pool);
117 
118 	struct Array *group = mempool_array(pool);
119 	const char *name = NULL;
120 	ARRAY_FOREACH(siblings, struct AST *, node) {
121 		unless (name) {
122 			name = node->variable.name;
123 		}
124 		if (!is_candidate(node) || strcmp(name, node->variable.name) != 0 || has_eol_comment(node)) {
125 			merge_variables(nodelist, group);
126 			group = mempool_array(pool);
127 			name = NULL;
128 		} else {
129 			array_append(group, node);
130 		}
131 	}
132 	merge_variables(nodelist, group);
133 
134 	array_truncate(siblings);
135 }
136 
137 enum ASTWalkState
refactor_collapse_adjacent_variables_walker(struct AST * node,struct WalkerData * this,struct Array * last_siblings)138 refactor_collapse_adjacent_variables_walker(struct AST *node, struct WalkerData *this, struct Array *last_siblings)
139 {
140 	SCOPE_MEMPOOL(pool);
141 	struct Array *siblings = mempool_array(pool);
142 
143 	switch (node->type) {
144 	case AST_ROOT:
145 		ARRAY_FOREACH(node->root.body, struct AST *, child) {
146 			AST_WALK_RECUR(refactor_collapse_adjacent_variables_walker(child, this, siblings));
147 		}
148 		process_siblings(node->root.body, siblings);
149 		break;
150 	case AST_DELETED:
151 		break;
152 	case AST_FOR:
153 		ARRAY_FOREACH(node->forexpr.body, struct AST *, child) {
154 			AST_WALK_RECUR(refactor_collapse_adjacent_variables_walker(child, this, siblings));
155 		}
156 		process_siblings(node->forexpr.body, siblings);
157 		break;
158 	case AST_IF:
159 		ARRAY_FOREACH(node->ifexpr.body, struct AST *, child) {
160 			AST_WALK_RECUR(refactor_collapse_adjacent_variables_walker(child, this, siblings));
161 		}
162 		process_siblings(node->ifexpr.body, siblings);
163 
164 		ARRAY_FOREACH(node->ifexpr.orelse, struct AST *, child) {
165 			AST_WALK_RECUR(refactor_collapse_adjacent_variables_walker(child, this, siblings));
166 		}
167 		process_siblings(node->ifexpr.orelse, siblings);
168 		break;
169 	case AST_INCLUDE:
170 		ARRAY_FOREACH(node->include.body, struct AST *, child) {
171 			AST_WALK_RECUR(refactor_collapse_adjacent_variables_walker(child, this, siblings));
172 		}
173 		break;
174 	case AST_TARGET:
175 		ARRAY_FOREACH(node->target.body, struct AST *, child) {
176 			AST_WALK_RECUR(refactor_collapse_adjacent_variables_walker(child, this, siblings));
177 		}
178 		process_siblings(node->target.body, siblings);
179 		break;
180 	case AST_COMMENT:
181 	case AST_TARGET_COMMAND:
182 	case AST_VARIABLE:
183 	case AST_EXPR:
184 		array_append(last_siblings, node);
185 		break;
186 	}
187 
188 	return AST_WALK_CONTINUE;
189 }
190 
PARSER_EDIT(refactor_collapse_adjacent_variables)191 PARSER_EDIT(refactor_collapse_adjacent_variables)
192 {
193 	SCOPE_MEMPOOL(pool);
194 
195 	if (userdata != NULL) {
196 		parser_set_error(parser, PARSER_ERROR_INVALID_ARGUMENT, NULL);
197 		return;
198 	}
199 
200 	refactor_collapse_adjacent_variables_walker(root, &(struct WalkerData){
201 		.parser = parser,
202 		.pool = pool,
203 	}, mempool_array(pool));
204 }
205