Merge pull request #452 from cr1901/master
[yosys.git] / frontends / verilog / preproc.cc
1 /*
2 * yosys -- Yosys Open SYnthesis Suite
3 *
4 * Copyright (C) 2012 Clifford Wolf <clifford@clifford.at>
5 *
6 * Permission to use, copy, modify, and/or distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 *
18 * ---
19 *
20 * The Verilog frontend.
21 *
22 * This frontend is using the AST frontend library (see frontends/ast/).
23 * Thus this frontend does not generate RTLIL code directly but creates an
24 * AST directly from the Verilog parse tree and then passes this AST to
25 * the AST frontend library.
26 *
27 * ---
28 *
29 * Ad-hoc implementation of a Verilog preprocessor. The directives `define,
30 * `include, `ifdef, `ifndef, `else and `endif are handled here. All other
31 * directives are handled by the lexer (see lexer.l).
32 *
33 */
34
35 #include "verilog_frontend.h"
36 #include "kernel/log.h"
37 #include <stdarg.h>
38 #include <stdio.h>
39 #include <string.h>
40
41 YOSYS_NAMESPACE_BEGIN
42 using namespace VERILOG_FRONTEND;
43
44 static std::list<std::string> output_code;
45 static std::list<std::string> input_buffer;
46 static size_t input_buffer_charp;
47
48 static void return_char(char ch)
49 {
50 if (input_buffer_charp == 0)
51 input_buffer.push_front(std::string() + ch);
52 else
53 input_buffer.front()[--input_buffer_charp] = ch;
54 }
55
56 static void insert_input(std::string str)
57 {
58 if (input_buffer_charp != 0) {
59 input_buffer.front() = input_buffer.front().substr(input_buffer_charp);
60 input_buffer_charp = 0;
61 }
62 input_buffer.push_front(str);
63 }
64
65 static char next_char()
66 {
67 if (input_buffer.empty())
68 return 0;
69
70 log_assert(input_buffer_charp <= input_buffer.front().size());
71 if (input_buffer_charp == input_buffer.front().size()) {
72 input_buffer_charp = 0;
73 input_buffer.pop_front();
74 return next_char();
75 }
76
77 char ch = input_buffer.front()[input_buffer_charp++];
78 return ch == '\r' ? next_char() : ch;
79 }
80
81 static std::string skip_spaces()
82 {
83 std::string spaces;
84 while (1) {
85 char ch = next_char();
86 if (ch == 0)
87 break;
88 if (ch != ' ' && ch != '\t') {
89 return_char(ch);
90 break;
91 }
92 spaces += ch;
93 }
94 return spaces;
95 }
96
97 static std::string next_token(bool pass_newline = false)
98 {
99 std::string token;
100
101 char ch = next_char();
102 if (ch == 0)
103 return token;
104
105 token += ch;
106 if (ch == '\n') {
107 if (pass_newline) {
108 output_code.push_back(token);
109 return "";
110 }
111 return token;
112 }
113
114 if (ch == ' ' || ch == '\t')
115 {
116 while ((ch = next_char()) != 0) {
117 if (ch != ' ' && ch != '\t') {
118 return_char(ch);
119 break;
120 }
121 token += ch;
122 }
123 }
124 else if (ch == '"')
125 {
126 while ((ch = next_char()) != 0) {
127 token += ch;
128 if (ch == '"')
129 break;
130 if (ch == '\\') {
131 if ((ch = next_char()) != 0)
132 token += ch;
133 }
134 }
135 if (token == "\"\"" && (ch = next_char()) != 0) {
136 if (ch == '"')
137 token += ch;
138 else
139 return_char(ch);
140 }
141 }
142 else if (ch == '/')
143 {
144 if ((ch = next_char()) != 0) {
145 if (ch == '/') {
146 token += '*';
147 char last_ch = 0;
148 while ((ch = next_char()) != 0) {
149 if (ch == '\n') {
150 return_char(ch);
151 break;
152 }
153 if (last_ch != '*' || ch != '/') {
154 token += ch;
155 last_ch = ch;
156 }
157 }
158 token += " */";
159 }
160 else if (ch == '*') {
161 token += '*';
162 int newline_count = 0;
163 char last_ch = 0;
164 while ((ch = next_char()) != 0) {
165 if (ch == '\n') {
166 newline_count++;
167 token += ' ';
168 } else
169 token += ch;
170 if (last_ch == '*' && ch == '/')
171 break;
172 last_ch = ch;
173 }
174 while (newline_count-- > 0)
175 return_char('\n');
176 }
177 else
178 return_char(ch);
179 }
180 }
181 else
182 {
183 const char *ok = "abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ$0123456789";
184 if (ch == '`' || strchr(ok, ch) != NULL)
185 {
186 ch = next_char();
187 if (ch == '"') {
188 token += ch;
189 } else do {
190 if (strchr(ok, ch) == NULL) {
191 return_char(ch);
192 break;
193 }
194 token += ch;
195 } while ((ch = next_char()) != 0);
196 }
197 }
198 return token;
199 }
200
201 static void input_file(std::istream &f, std::string filename)
202 {
203 char buffer[513];
204 int rc;
205
206 insert_input("");
207 auto it = input_buffer.begin();
208
209 input_buffer.insert(it, "`file_push \"" + filename + "\"\n");
210 while ((rc = readsome(f, buffer, sizeof(buffer)-1)) > 0) {
211 buffer[rc] = 0;
212 input_buffer.insert(it, buffer);
213 }
214 input_buffer.insert(it, "\n`file_pop\n");
215 }
216
217
218 static bool try_expand_macro(std::set<std::string> &defines_with_args,
219 std::map<std::string, std::string> &defines_map,
220 std::string &tok
221 )
222 {
223 if (tok == "`\"") {
224 std::string literal("\"");
225 // Expand string literal
226 while (!input_buffer.empty()) {
227 std::string ntok = next_token();
228 if (ntok == "`\"") {
229 insert_input(literal+"\"");
230 return true;
231 } else if (!try_expand_macro(defines_with_args, defines_map, ntok)) {
232 literal += ntok;
233 }
234 }
235 return false; // error - unmatched `"
236 } else if (tok.size() > 1 && tok[0] == '`' && defines_map.count(tok.substr(1)) > 0) {
237 std::string name = tok.substr(1);
238 // printf("expand: >>%s<< -> >>%s<<\n", name.c_str(), defines_map[name].c_str());
239 std::string skipped_spaces = skip_spaces();
240 tok = next_token(false);
241 if (tok == "(" && defines_with_args.count(name) > 0) {
242 int level = 1;
243 std::vector<std::string> args;
244 args.push_back(std::string());
245 while (1)
246 {
247 tok = next_token(true);
248 if (tok == ")" || tok == "}" || tok == "]")
249 level--;
250 if (level == 0)
251 break;
252 if (level == 1 && tok == ",")
253 args.push_back(std::string());
254 else
255 args.back() += tok;
256 if (tok == "(" || tok == "{" || tok == "[")
257 level++;
258 }
259 for (int i = 0; i < GetSize(args); i++)
260 defines_map[stringf("macro_%s_arg%d", name.c_str(), i+1)] = args[i];
261 } else {
262 insert_input(tok);
263 insert_input(skipped_spaces);
264 }
265 insert_input(defines_map[name]);
266 return true;
267 } else return false;
268 }
269
270 std::string frontend_verilog_preproc(std::istream &f, std::string filename, const std::map<std::string, std::string> &pre_defines_map,
271 dict<std::string, std::pair<std::string, bool>> &global_defines_cache, const std::list<std::string> &include_dirs)
272 {
273 std::set<std::string> defines_with_args;
274 std::map<std::string, std::string> defines_map(pre_defines_map);
275 std::vector<std::string> filename_stack;
276 int ifdef_fail_level = 0;
277 bool in_elseif = false;
278
279 output_code.clear();
280 input_buffer.clear();
281 input_buffer_charp = 0;
282
283 input_file(f, filename);
284
285 defines_map["YOSYS"] = "1";
286 defines_map[formal_mode ? "FORMAL" : "SYNTHESIS"] = "1";
287
288 for (auto &it : pre_defines_map)
289 defines_map[it.first] = it.second;
290
291 for (auto &it : global_defines_cache) {
292 if (it.second.second)
293 defines_with_args.insert(it.first);
294 defines_map[it.first] = it.second.first;
295 }
296
297 while (!input_buffer.empty())
298 {
299 std::string tok = next_token();
300 // printf("token: >>%s<<\n", tok != "\n" ? tok.c_str() : "NEWLINE");
301
302 if (tok == "`endif") {
303 if (ifdef_fail_level > 0)
304 ifdef_fail_level--;
305 if (ifdef_fail_level == 0)
306 in_elseif = false;
307 continue;
308 }
309
310 if (tok == "`else") {
311 if (ifdef_fail_level == 0)
312 ifdef_fail_level = 1;
313 else if (ifdef_fail_level == 1 && !in_elseif)
314 ifdef_fail_level = 0;
315 continue;
316 }
317
318 if (tok == "`elsif") {
319 skip_spaces();
320 std::string name = next_token(true);
321 if (ifdef_fail_level == 0)
322 ifdef_fail_level = 1, in_elseif = true;
323 else if (ifdef_fail_level == 1 && defines_map.count(name) != 0)
324 ifdef_fail_level = 0, in_elseif = true;
325 continue;
326 }
327
328 if (tok == "`ifdef") {
329 skip_spaces();
330 std::string name = next_token(true);
331 if (ifdef_fail_level > 0 || defines_map.count(name) == 0)
332 ifdef_fail_level++;
333 continue;
334 }
335
336 if (tok == "`ifndef") {
337 skip_spaces();
338 std::string name = next_token(true);
339 if (ifdef_fail_level > 0 || defines_map.count(name) != 0)
340 ifdef_fail_level++;
341 continue;
342 }
343
344 if (ifdef_fail_level > 0) {
345 if (tok == "\n")
346 output_code.push_back(tok);
347 continue;
348 }
349
350 if (tok == "`include") {
351 skip_spaces();
352 std::string fn = next_token(true);
353 while (try_expand_macro(defines_with_args, defines_map, fn)) {
354 fn = next_token();
355 }
356 while (1) {
357 size_t pos = fn.find('"');
358 if (pos == std::string::npos)
359 break;
360 if (pos == 0)
361 fn = fn.substr(1);
362 else
363 fn = fn.substr(0, pos) + fn.substr(pos+1);
364 }
365 std::ifstream ff;
366 ff.clear();
367 std::string fixed_fn = fn;
368 ff.open(fixed_fn.c_str());
369
370 bool filename_path_sep_found;
371 bool fn_relative;
372 #ifdef _WIN32
373 // Both forward and backslash are acceptable separators on Windows.
374 filename_path_sep_found = (filename.find_first_of("/\\") != std::string::npos);
375 // Easier just to invert the check for an absolute path (e.g. C:\ or C:/)
376 fn_relative = !(fn[1] == ':' && (fn[2] == '/' || fn[2] == '\\'));
377 #else
378 filename_path_sep_found = (filename.find('/') != std::string::npos);
379 fn_relative = (fn[0] != '/');
380 #endif
381
382 if (ff.fail() && fn.size() > 0 && fn_relative && filename_path_sep_found) {
383 // if the include file was not found, it is not given with an absolute path, and the
384 // currently read file is given with a path, then try again relative to its directory
385 ff.clear();
386 #ifdef _WIN32
387 fixed_fn = filename.substr(0, filename.find_last_of("/\\")+1) + fn;
388 #else
389 fixed_fn = filename.substr(0, filename.rfind('/')+1) + fn;
390 #endif
391 ff.open(fixed_fn);
392 }
393 if (ff.fail() && fn.size() > 0 && fn_relative) {
394 // if the include file was not found and it is not given with an absolute path, then
395 // search it in the include path
396 for (auto incdir : include_dirs) {
397 ff.clear();
398 fixed_fn = incdir + '/' + fn;
399 ff.open(fixed_fn);
400 if (!ff.fail()) break;
401 }
402 }
403 if (ff.fail())
404 output_code.push_back("`file_notfound " + fn);
405 else
406 input_file(ff, fixed_fn);
407 continue;
408 }
409
410 if (tok == "`file_push") {
411 skip_spaces();
412 std::string fn = next_token(true);
413 if (!fn.empty() && fn.front() == '"' && fn.back() == '"')
414 fn = fn.substr(1, fn.size()-2);
415 output_code.push_back(tok + " \"" + fn + "\"");
416 filename_stack.push_back(filename);
417 filename = fn;
418 continue;
419 }
420
421 if (tok == "`file_pop") {
422 output_code.push_back(tok);
423 filename = filename_stack.back();
424 filename_stack.pop_back();
425 continue;
426 }
427
428 if (tok == "`define") {
429 std::string name, value;
430 std::map<std::string, int> args;
431 skip_spaces();
432 name = next_token(true);
433 bool here_doc_mode = false;
434 int newline_count = 0;
435 int state = 0;
436 if (skip_spaces() != "")
437 state = 3;
438 while (!tok.empty()) {
439 tok = next_token();
440 if (tok == "\"\"\"") {
441 here_doc_mode = !here_doc_mode;
442 continue;
443 }
444 if (state == 0 && tok == "(") {
445 state = 1;
446 skip_spaces();
447 } else
448 if (state == 1) {
449 if (tok == ")")
450 state = 2;
451 else if (tok != ",") {
452 int arg_idx = args.size()+1;
453 args[tok] = arg_idx;
454 }
455 skip_spaces();
456 } else {
457 if (state != 2)
458 state = 3;
459 if (tok == "\n") {
460 if (here_doc_mode) {
461 value += " ";
462 newline_count++;
463 } else {
464 return_char('\n');
465 break;
466 }
467 } else
468 if (tok == "\\") {
469 char ch = next_char();
470 if (ch == '\n') {
471 value += " ";
472 newline_count++;
473 } else {
474 value += std::string("\\");
475 return_char(ch);
476 }
477 } else
478 if (args.count(tok) > 0)
479 value += stringf("`macro_%s_arg%d", name.c_str(), args.at(tok));
480 else
481 value += tok;
482 }
483 }
484 while (newline_count-- > 0)
485 return_char('\n');
486 // printf("define: >>%s<< -> >>%s<<\n", name.c_str(), value.c_str());
487 defines_map[name] = value;
488 if (state == 2)
489 defines_with_args.insert(name);
490 else
491 defines_with_args.erase(name);
492 global_defines_cache[name] = std::pair<std::string, bool>(value, state == 2);
493 continue;
494 }
495
496 if (tok == "`undef") {
497 std::string name;
498 skip_spaces();
499 name = next_token(true);
500 // printf("undef: >>%s<<\n", name.c_str());
501 defines_map.erase(name);
502 defines_with_args.erase(name);
503 global_defines_cache.erase(name);
504 continue;
505 }
506
507 if (tok == "`timescale") {
508 skip_spaces();
509 while (!tok.empty() && tok != "\n")
510 tok = next_token(true);
511 if (tok == "\n")
512 return_char('\n');
513 continue;
514 }
515
516 if (tok == "`resetall") {
517 defines_map.clear();
518 defines_with_args.clear();
519 global_defines_cache.clear();
520 continue;
521 }
522
523 if (try_expand_macro(defines_with_args, defines_map, tok))
524 continue;
525
526 output_code.push_back(tok);
527 }
528
529 std::string output;
530 for (auto &str : output_code)
531 output += str;
532
533 output_code.clear();
534 input_buffer.clear();
535 input_buffer_charp = 0;
536
537 return output;
538 }
539
540 YOSYS_NAMESPACE_END