-
Notifications
You must be signed in to change notification settings - Fork 0
/
parser.c
304 lines (258 loc) · 5.71 KB
/
parser.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
/**
* Parser to parse a minimal makefile. This file is provided to students along
* with its header-file parser.h to solve the mmake laboration in the course C
* Programming and Unix (5DV088).
*
* @file parse.h
* @author Elias Åström, Fredrik Peteri
* @date 2020-09-04
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <ctype.h>
#include "parser.h"
#define MAX_RULES 256
#define MAX_LINE 1024
#define MAX_PREREQ 32
#define MAX_CMD 32
struct makefile {
struct rule *rules;
};
struct rule {
char *target;
char **prereq;
char **cmd;
rule *next;
};
/**
* Check if line is blank.
*/
static bool is_blank_line(const char *s)
{
for (size_t i = 0; s[i] != '\0'; i++) {
if (!isspace(s[i]))
return false;
}
return true;
}
/**
* Parse a word and update p to point to the first character after the word.
* The word is delimited by whitespace and any character in delim. The
* returned string should be freed using free.
*/
static char *parse_word(char **p, char *delim)
{
size_t n = 0;
while (!isspace((*p)[n]) && strchr(delim, (*p)[n]) == NULL)
n++;
if (n == 0)
return NULL;
char *path = strndup(*p, n);
*p += n;
return path;
}
/**
* Fills buf with the next line from fp. Returns buf if a line was read and
* NULL otherwise.
*/
static char *next_line(char buf[MAX_LINE], FILE *fp)
{
do {
if (fgets(buf, MAX_LINE, fp) == NULL)
return NULL;
} while (is_blank_line(buf));
return buf;
}
/**
* Duplicate an array of strings.
*
* @param n Size of array to duplicate.
* @param a Array to duplicate.
* @return NULL-terminated array which should be freed using free.
*/
static char **dupe_str_array(size_t n, char **a)
{
char **ret = malloc((n + 1) * sizeof *ret);
for (size_t i = 0; i < n; i++)
ret[i] = a[i];
ret[n] = NULL;
return ret;
}
/**
* Advance pointer to the next character which is not a space, stops at
* newline.
*/
static void skipwhite(char **p)
{
while (isspace(**p) && **p != '\n')
(*p)++;
}
/**
* Check that the character pointed to by p is c, and increment p if it is.
*/
static bool expect(char **p, char c)
{
if (**p != c)
return false;
(*p)++;
return true;
}
/**
* Parse a rule.
*
* @param fp File to read from.
* @param err Pointer to flag which gets set to true on error.
* @return A parsed rule or NULL.
*/
static rule *parse_rule(FILE *fp, bool *err)
{
char buf[MAX_LINE];
char *p;
// read line with target and prerequisites
if ((p = next_line(buf, fp)) == NULL)
return NULL;
// line cannot begin with whitespace
if (isspace(*p))
goto err0;
char *target = parse_word(&p, ":");
skipwhite(&p);
if (!expect(&p, ':'))
goto err1;
skipwhite(&p);
// parse prerequisites
char *prereq[MAX_PREREQ];
size_t n_prereq = 0;
while (n_prereq < MAX_PREREQ
&& (prereq[n_prereq] = parse_word(&p, "")) != NULL) {
n_prereq++;
skipwhite(&p);
}
if (!expect(&p, '\n'))
goto err2;
// read line with command
if ((p = next_line(buf, fp)) == NULL)
goto err2;
// command has to begin with tab
if (!expect(&p, '\t'))
goto err2;
skipwhite(&p);
// parse command
char *cmd[MAX_CMD];
size_t n_cmd = 0;
while (n_cmd < MAX_CMD && (cmd[n_cmd] = parse_word(&p, "")) != NULL) {
n_cmd++;
skipwhite(&p);
}
// create rule
rule *r = malloc(sizeof *r);
r->target = target;
r->prereq = dupe_str_array(n_prereq, prereq);
r->cmd = dupe_str_array(n_cmd, cmd);
return r;
err2:
for (size_t i = 0; i < n_prereq; i++)
free(prereq[i]);
err1:
free(target);
err0:
*err = true;
return NULL;
}
/**
* Parse a makefile.
*
* @param path Path to makefile to parse.
* @return The makefile.
*/
makefile *parse_makefile(FILE *fp)
{
makefile *m = malloc(sizeof *m);
rule **tailp = &m->rules;
bool err = false;
while ((*tailp = parse_rule(fp, &err)) != NULL)
tailp = &(*tailp)->next;
*tailp = NULL;
if (m->rules == NULL || err) {
makefile_del(m);
return NULL;
}
return m;
}
/**
* Get the default target for a makefile. The default target is the target
* from the first rule.
*
* @param make The makefile.
* @return Name of the default target for the makefile.
*/
const char *makefile_default_target(makefile *m)
{
return m->rules->target;
}
/**
* Get the rule for building a specific target in a makefile.
*
* @param make The makefile.
* @param target Name of a target.
* @return The rule for building the target.
*/
rule *makefile_rule(makefile *m, const char *target)
{
for (rule *i = m->rules; i != NULL; i = i->next)
if (strcmp(i->target, target) == 0)
return i;
return NULL;
}
/**
* Get the prerequisites for a rule.
*
* @param rule The rule.
* @return Array containing the prerequisites for the rule. The array is
* terminated with NULL.
*/
const char **rule_prereq(rule *rule)
{
return (const char **)rule->prereq;
}
/**
* Get the command for a rule.
*
* @param rule The rule.
* @return Array containing the arguments for the command used to build
* the rule. The first argument is the name of the command. The
* array is terminated with NULL.
*/
char **rule_cmd(rule *rule)
{
return rule->cmd;
}
/**
* Recursively delete a list of rules.
*/
static void del_rules(struct rule *rules)
{
if (rules == NULL)
return;
free(rules->target);
for (size_t i = 0; rules->prereq[i] != NULL; i++)
free(rules->prereq[i]);
free(rules->prereq);
for (size_t i = 0; rules->cmd[i] != NULL; i++)
free(rules->cmd[i]);
free(rules->cmd);
del_rules(rules->next);
free(rules);
}
/**
* Free the memory of a makefile. This will also delete the rules from the
* makefile returned by makefile_rule.
*
* @param make Makefile to delete.
*/
void makefile_del(makefile *make)
{
del_rules(make->rules);
free(make);
}