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