From bccbd4e8e32d13a8114dff165159edccd832eeef Mon Sep 17 00:00:00 2001 From: Nick Gasson Date: Sat, 30 Sep 2017 12:48:41 +0100 Subject: [PATCH] Add basic support for VHDL-2017 conditional analysis blocks --- src/lexer.l | 10 +- src/parse.c | 325 ++++++++++++++++++++++++++++++++++++------- src/token.h | 6 + test/parse/cond1.vhd | 37 +++++ test/test_parse.c | 30 ++++ 5 files changed, 359 insertions(+), 49 deletions(-) create mode 100644 test/parse/cond1.vhd diff --git a/src/lexer.l b/src/lexer.l index afee6c51..b574e1c6 100644 --- a/src/lexer.l +++ b/src/lexer.l @@ -1,7 +1,7 @@ /* -*- mode: c; c-basic-offset: 3 -*- */ /* - * Copyright (C) 2011-2015 Nick Gasson + * Copyright (C) 2011-2017 Nick Gasson * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -184,6 +184,8 @@ GUARDED ?i:guarded REVRANGE ?i:reverse_range PROTECTED ?i:protected CONTEXT ?i:context +ERROR ?i:error +WARNING ?i:warning %% @@ -287,6 +289,12 @@ CONTEXT ?i:context {REVRANGE} { TOKEN(tREVRANGE); } {PROTECTED} { TOKEN_00(tPROTECTED); } {CONTEXT} { TOKEN_08(tCONTEXT); } +`{IF} { TOKEN(tCONDIF); } +`{ELSE} { TOKEN(tCONDELSE); } +`{ELSIF} { TOKEN(tCONDELSIF); } +`{END} { TOKEN(tCONDEND); } +`{ERROR} { TOKEN(tCONDERROR); } +`{WARNING} { TOKEN(tCONDWARN); } "(" { TOKEN(tLPAREN); } ")" { TOKEN(tRPAREN); } diff --git a/src/parse.c b/src/parse.c index 1cc01e3c..55710ac4 100644 --- a/src/parse.c +++ b/src/parse.c @@ -47,31 +47,39 @@ typedef struct { int depth; } look_params_t; -static const char *perm_linebuf = NULL; -static ident_t perm_file_name = NULL; -static int n_token_next_start = 0; -static int n_row = 0; -static bool last_was_newline = true; -static loc_t start_loc; -static loc_t last_loc; -static const char *read_ptr; -static const char *file_start; -static size_t file_sz; -static int n_errors = 0; -static const char *hint_str = NULL; -static int n_correct = 0; -static tokenq_t *tokenq; -static int tokenq_sz; -static int tokenq_head; -static int tokenq_tail; -static yylval_t last_lval; -static token_t opt_hist[8]; -static int nopt_hist = 0; +typedef struct cond_state cond_state_t; + +struct cond_state { + cond_state_t *next; + bool result; + loc_t loc; +}; + +static const char *perm_linebuf = NULL; +static ident_t perm_file_name = NULL; +static int n_token_next_start = 0; +static int n_row = 0; +static bool last_was_newline = true; +static loc_t start_loc; +static loc_t last_loc; +static const char *read_ptr; +static const char *file_start; +static size_t file_sz; +static int n_errors = 0; +static const char *hint_str = NULL; +static int n_correct = 0; +static tokenq_t *tokenq; +static int tokenq_sz; +static int tokenq_head; +static int tokenq_tail; +static yylval_t last_lval; +static token_t opt_hist[8]; +static int nopt_hist = 0; +static cond_state_t *cond_state = NULL; loc_t yylloc; int yylex(void); -#define F(list) list, ARRAY_LEN(list) #define scan(...) _scan(1, __VA_ARGS__, -1) #define expect(...) _expect(1, __VA_ARGS__, -1) #define one_of(...) _one_of(1, __VA_ARGS__, -1) @@ -124,6 +132,11 @@ static tree_t p_subprogram_specification(void); static tree_t p_name(void); static void p_block_configuration(tree_t unit); static tree_t p_protected_type_body(void); +static bool p_cond_analysis_expr(void); + +static const char *token_str(token_t tok); +static bool consume(token_t tok); +static bool optional(token_t tok); static void _pop_state(const state_t *s) { @@ -166,7 +179,8 @@ static const char *token_str(token_t tok) "record", "new", "shared", "and", "or", "nand", "nor", "xor", "xnor", "=", "/=", "<", "<=", ">", ">=", "+", "-", "&", "**", "/", "sll", "srl", "sla", "sra", "rol", "ror", "mod", "rem", "abs", "not", "*", "guarded", - "reverse_range", "protected", "context" + "reverse_range", "protected", "context", "`if", "`else", "`elsif", "`end", + "`error", "`warning" }; if ((size_t)tok >= ARRAY_LEN(token_strs)) @@ -175,46 +189,140 @@ static const char *token_str(token_t tok) return token_strs[tok]; } -static token_t peek_nth(int n) +static token_t conditional_yylex(void) { - const int orign = n; - const int have = (tokenq_head - tokenq_tail) & (tokenq_sz - 1); + const token_t token = yylex(); + +#if 0 + printf("%s %s\n", (cond_state ? (cond_state->result ? "1" : "0") : "-"), + token_str(token)); +#endif + + switch (token) { + case tCONDIF: + { + BEGIN("conditional analysis directive"); + + cond_state_t *new = xmalloc(sizeof(cond_state_t)); + new->loc = yylloc; + new->result = p_cond_analysis_expr(); + new->next = cond_state; + + consume(tTHEN); - if (have < n) { - n -= have; + new->loc.last_column = yylloc.last_column; + new->loc.last_line = yylloc.last_line; + + cond_state = new; + return conditional_yylex(); + } - while (n--) { - int next = (tokenq_head + 1) & (tokenq_sz - 1); - if (unlikely(next == tokenq_tail)) { - const int newsz = tokenq_sz * 2; - tokenq_t *new = xmalloc(newsz * sizeof(tokenq_t)); + case tCONDELSE: + { + BEGIN("conditional analysis directive"); - tokenq_t *p = new; - for (int i = tokenq_tail; i != tokenq_head; - i = (i + 1) & (tokenq_sz - 1)) - *p++ = tokenq[i]; + if (cond_state == NULL) + parse_error(&yylloc, "unexpected $yellow$%s$$ outside conditional " + "analysis block", token_str(token)); + else + cond_state->result = !(cond_state->result); - free(tokenq); + return conditional_yylex(); + } - tokenq = new; - tokenq_sz = newsz; - tokenq_head = p - new; - tokenq_tail = 0; + case tCONDEND: + { + BEGIN("conditional analysis directive"); - next = (tokenq_head + 1) & (tokenq_sz - 1); + if (cond_state == NULL) + parse_error(&yylloc, "unexpected $yellow$%s$$ outside conditional " + "analysis block", token_str(token)); + else { + cond_state_t *old = cond_state; + cond_state = cond_state->next; + free(old); } - extern yylval_t yylval; + optional(tIF); + + return conditional_yylex(); + } + + case tCONDERROR: + case tCONDWARN: + { + if (cond_state == NULL || cond_state->result) { + BEGIN("conditional analysis directive"); + + loc_t loc = yylloc; + if (consume(tSTRING)) { + loc.last_column = yylloc.last_column; + loc.last_line = yylloc.last_line; + + if (token == tCONDWARN) + warn_at(&loc, "%s", last_lval.s); + else + parse_error(&loc, "%s", last_lval.s); + + free(last_lval.s); + } + } + + return conditional_yylex(); + } + + case tEOF: + if (cond_state != NULL) { + parse_error(&(cond_state->loc), "unterminated conditional " + "analysis block"); + n_correct = 0; + } + return tEOF; + + default: + if (cond_state == NULL || cond_state->result) + return token; + else + return conditional_yylex(); + } +} + +static token_t peek_nth(int n) +{ + while (((tokenq_head - tokenq_tail) & (tokenq_sz - 1)) < n) { + // Calling conditional_yylex may recursively call this function + const token_t token = conditional_yylex(); + + int next = (tokenq_head + 1) & (tokenq_sz - 1); + if (unlikely(next == tokenq_tail)) { + const int newsz = tokenq_sz * 2; + tokenq_t *new = xmalloc(newsz * sizeof(tokenq_t)); + + tokenq_t *p = new; + for (int i = tokenq_tail; i != tokenq_head; + i = (i + 1) & (tokenq_sz - 1)) + *p++ = tokenq[i]; + + free(tokenq); - tokenq[tokenq_head].token = yylex(); - tokenq[tokenq_head].lval = yylval; - tokenq[tokenq_head].loc = yylloc; + tokenq = new; + tokenq_sz = newsz; + tokenq_head = p - new; + tokenq_tail = 0; - tokenq_head = next; + next = (tokenq_head + 1) & (tokenq_sz - 1); } + + extern yylval_t yylval; + + tokenq[tokenq_head].token = token; + tokenq[tokenq_head].lval = yylval; + tokenq[tokenq_head].loc = yylloc; + + tokenq_head = next; } - const int pos = (tokenq_tail + orign - 1) & (tokenq_sz - 1); + const int pos = (tokenq_tail + n - 1) & (tokenq_sz - 1); return tokenq[pos].token; } @@ -570,6 +678,127 @@ static void set_delay_mechanism(tree_t t, tree_t reject) tree_set_reject(t, reject); } +static const char *get_cond_analysis_identifier(const char *name) +{ + if (strcmp(name, "VHDL_VERSION") == 0) + return standard_text(standard()); + else if (strcmp(name, "TOOL_TYPE") == 0) + return "SIMULATION"; + else if (strcmp(name, "TOOL_VENDOR") == 0) + return PACKAGE_URL; + else if (strcmp(name, "TOOL_NAME") == 0) + return PACKAGE_NAME; + else if (strcmp(name, "TOOL_EDITION") == 0) + return ""; + else if (strcmp(name, "TOOL_VERSION") == 0) + return PACKAGE_VERSION; + else + return NULL; +} + +static bool p_cond_analysis_relation(void) +{ + // ( conditional_analysis_expression ) + // | not ( conditional_analysis_expression ) + // | conditional_analysis_identifier = string_literal + // | conditional_analysis_identifier /= string_literal + // | conditional_analysis_identifier < string_literal + // | conditional_analysis_identifier <= string_literal + // | conditional_analysis_identifier > string_literal + // | conditional_analysis_identifier >= string_literal + + BEGIN("conditional analysis relation"); + + bool result = false; + switch (one_of(tLPAREN, tNOT, tID)) { + case tLPAREN: + result = p_cond_analysis_expr(); + consume(tRPAREN); + break; + + case tNOT: + result = !p_cond_analysis_expr(); + break; + + case tID: + { + char *name = last_lval.s; + token_t rel = one_of(tEQ, tNEQ, tLT, tLE, tGT, tGE); + + if (consume(tSTRING)) { + const char *value = get_cond_analysis_identifier(name); + if (value == NULL) + parse_error(CURRENT_LOC, "undefined conditional analysis " + "identifier %s", name); + else { + char *cmp = last_lval.s + 1; + cmp[strlen(cmp) - 1] = '\0'; + + switch (rel) { + case tEQ: + result = strcmp(value, cmp) == 0; + break; + case tNEQ: + result = strcmp(value, cmp) != 0; + break; + case tLT: + result = strcmp(value, cmp) < 0; + break; + case tLE: + result = strcmp(value, cmp) <= 0; + break; + case tGT: + result = strcmp(value, cmp) > 0; + break; + case tGE: + result = strcmp(value, cmp) >= 0; + break; + default: + break; + } + } + + free(last_lval.s); + } + + free(name); + } + break; + } + + return result; +} + +static bool p_cond_analysis_expr(void) +{ + // conditional_analysis_relation + // | conditional_analysis_relation { and conditional_analysis_relation } + // | conditional_analysis_relation { or conditional_analysis_relation } + // | conditional_analysis_relation { xor conditional_analysis_relation } + // | conditioanl_analysis_relation { xnor conditional_analysis_relation } + + BEGIN("conditional analysis expression"); + + const bool lhs = p_cond_analysis_relation(); + + switch (peek()) { + case tAND: + consume(tAND); + return p_cond_analysis_relation() && lhs; + case tOR: + consume(tOR); + return p_cond_analysis_relation() || lhs; + case tXOR: + consume(tXOR); + return p_cond_analysis_relation() ^ lhs; + case tXNOR: + consume(tXNOR); + return !(p_cond_analysis_relation() ^ lhs); + default: + return lhs; + } +} + static ident_t p_identifier(void) { // basic_identifier | extended_identifier diff --git a/src/token.h b/src/token.h index 0448497c..36f24f95 100644 --- a/src/token.h +++ b/src/token.h @@ -155,6 +155,12 @@ typedef enum { tREVRANGE, tPROTECTED, tCONTEXT, + tCONDIF, + tCONDELSE, + tCONDELSIF, + tCONDEND, + tCONDERROR, + tCONDWARN } token_t; #endif diff --git a/test/parse/cond1.vhd b/test/parse/cond1.vhd new file mode 100644 index 00000000..8db96c27 --- /dev/null +++ b/test/parse/cond1.vhd @@ -0,0 +1,37 @@ +package cond1 is + + `if TOOL_NAME = "false" then + `error "Should not be here" + constant d : integer := 1; + `else + constant c : integer := 1; + `end if + + `warning "this is a warning" + + `if TOOL_NAME = "nvc" then + `warning "Using nvc" + `end if + + `if not (TOOL_TYPE = "SIMULATION") then + `error "Should not be here" + `end if + + `if TOOL_TYPE /= "SYNTHESIS" and TOOL_NAME = "nvc" then + `warning "correct" + `end if + + `if VHDL_VERSION = "1993" then + `warning "VHDL version is correct" + `end if + +end package; + +package cond2 is + + `if FOO = "bar" then + `end + + `if TOOL_NAME = "nvc" then + -- Unterminated +end package diff --git a/test/test_parse.c b/test/test_parse.c index adc92f7a..5ab56a77 100644 --- a/test/test_parse.c +++ b/test/test_parse.c @@ -2656,6 +2656,35 @@ START_TEST(test_guarded) } END_TEST +START_TEST(test_cond1) +{ + input_from_file(TESTDIR "/parse/cond1.vhd"); + + const error_t expect[] = { + { 10, "\"this is a warning\"" }, + { 13, "\"Using nvc\"" }, + { 21, "\"correct\"" }, + { 25, "\"VHDL version is correct\"" }, + { 32, "undefined conditional analysis identifier FOO" }, + { 35, "unterminated conditional analysis block" }, + { -1, NULL } + }; + expect_errors(expect); + + tree_t p = parse(); + fail_if(p == NULL); + + fail_unless(parse_errors() == 0); + + fail_unless(tree_kind(p) == T_PACKAGE); + fail_unless(tree_decls(p) == 1); + fail_unless(tree_ident(tree_decl(p, 0)) == ident_new("C")); + + fail_if(parse() != NULL); + fail_unless(parse_errors() == 2); +} +END_TEST + int main(void) { Suite *s = suite_create("parse"); @@ -2697,6 +2726,7 @@ int main(void) tcase_add_test(tc_core, test_context); tcase_add_test(tc_core, test_issue222); tcase_add_test(tc_core, test_guarded); + tcase_add_test(tc_core, test_cond1); suite_add_tcase(s, tc_core); return nvc_run_test(s); -- 2.39.2